From 3baeb981d9e2e1a5b3983a1f345344aacb77e664 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 27 Apr 2022 10:48:54 +1000 Subject: [PATCH] Further work on the JobRunner Moved the JobRunner into SessionUtilitiesKit so it can be used by SessionSnodeKit Exposed a 'sharedLokiProject' value on UserDefaults to remove the hard-coded group name used everywhere Added "blocking" job support for 'OnLaunch' and 'OnActive' jobs to the JobRunner (will retry until it succeeds) Added the UpdateProfilePicture and RetrieveDefaultOpenGroupRooms jobs --- ...Configuration.swift => Configuration.swift | 2 + Session.xcodeproj/project.pbxproj | 64 ++++++--- Session/Home/HomeVC.swift | 6 - Session/Meta/AppDelegate.swift | 8 +- Session/Meta/MainAppContext.m | 5 +- Session/Notifications/SyncPushTokensJob.swift | 10 +- .../_001_InitialSetupMigration.swift | 24 ---- .../Migrations/_002_SetupStandardJobs.swift | 24 ++-- .../Database/Models/Interaction.swift | 5 + .../Database/Models/SessionThread.swift | 11 ++ .../Jobs/Types/AttachmentDownloadJob.swift | 2 +- .../Jobs/Types/DisappearingMessagesJob.swift | 19 +-- .../Types/FailedAttachmentDownloadsJob.swift | 2 +- .../Jobs/Types/FailedMessagesJob.swift | 2 +- .../Jobs/Types/MessageReceiveJob.swift | 2 +- .../Jobs/Types/MessageSendJob.swift | 2 +- .../Jobs/Types/NotifyPushServerJob.swift | 2 +- .../RetrieveDefaultOpenGroupRoomsJob.swift | 30 ++++ .../Jobs/Types/SendReadReceiptsJob.swift | 2 +- .../Jobs/Types/UpdateProfilePictureJob.swift | 46 ++++++ .../Open Groups/OpenGroupAPIV2+ObjC.swift | 5 - .../MessageReceiver+Decryption.swift | 2 +- .../MessageReceiver+Handling.swift | 12 +- .../Sending & Receiving/MessageSender.swift | 5 +- .../Pollers/ClosedGroupPoller.swift | 32 +++-- .../Sending & Receiving/Pollers/Poller.swift | 35 +++-- .../NotificationServiceExtension.swift | 6 +- SessionSnodeKit/Configuration.swift | 7 +- .../Migrations/_002_SetupStandardJobs.swift | 20 +++ ...on.swift => _003_YDBToGRDBMigration.swift} | 3 +- SessionSnodeKit/GetSnodePoolJob.swift | 24 ++++ SessionSnodeKit/SnodeAPI.swift | 5 +- SessionUtilitiesKit/Configuration.swift | 5 +- .../_001_InitialSetupMigration.swift | 24 ++++ .../Migrations/_002_SetupStandardJobs.swift | 21 +++ ...on.swift => _003_YDBToGRDBMigration.swift} | 3 +- .../Database/Models/Job.swift | 113 +++++++++++---- .../General/SNUserDefaults.swift | 3 + .../JobRunner}/JobRunner.swift | 131 ++++++++++++++---- .../JobRunner}/JobRunnerError.swift | 0 40 files changed, 519 insertions(+), 205 deletions(-) rename SessionMessagingKit/Configuration.swift => Configuration.swift (89%) create mode 100644 SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift create mode 100644 SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift create mode 100644 SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift rename SessionSnodeKit/Database/Migrations/{_002_YDBToGRDBMigration.swift => _003_YDBToGRDBMigration.swift} (98%) create mode 100644 SessionSnodeKit/GetSnodePoolJob.swift create mode 100644 SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift rename SessionUtilitiesKit/Database/Migrations/{_002_YDBToGRDBMigration.swift => _003_YDBToGRDBMigration.swift} (96%) rename {SessionMessagingKit => SessionUtilitiesKit}/Database/Models/Job.swift (64%) rename {SessionMessagingKit/Jobs => SessionUtilitiesKit/JobRunner}/JobRunner.swift (80%) rename {SessionMessagingKit/Jobs => SessionUtilitiesKit/JobRunner}/JobRunnerError.swift (100%) diff --git a/SessionMessagingKit/Configuration.swift b/Configuration.swift similarity index 89% rename from SessionMessagingKit/Configuration.swift rename to Configuration.swift index cb62884f2..cf8f34141 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/Configuration.swift @@ -33,6 +33,8 @@ public enum SNMessagingKit { // Just to make the external API nice JobRunner.add(executor: DisappearingMessagesJob.self, for: .disappearingMessages) JobRunner.add(executor: FailedMessagesJob.self, for: .failedMessages) JobRunner.add(executor: FailedAttachmentDownloadsJob.self, for: .failedAttachmentDownloads) + JobRunner.add(executor: UpdateProfilePictureJob.self, for: .updateProfilePicture) + JobRunner.add(executor: RetrieveDefaultOpenGroupRoomsJob.self, for: .retrieveDefaultOpenGroupRooms) JobRunner.add(executor: MessageSendJob.self, for: .messageSend) JobRunner.add(executor: MessageReceiveJob.self, for: .messageReceive) JobRunner.add(executor: NotifyPushServerJob.self, for: .notifyPushServer) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index aaea0791e..2a1143247 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -751,7 +751,7 @@ FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; FD17D7A127F40D2500122BE0 /* GRDBStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */; }; FD17D7A227F40F0500122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */; }; - FD17D7A427F40F8100122BE0 /* _002_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A327F40F8100122BE0 /* _002_YDBToGRDBMigration.swift */; }; + FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD17D7A727F41AF000122BE0 /* SSKLegacyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A627F41AF000122BE0 /* SSKLegacyModels.swift */; }; FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7A927F41BF500122BE0 /* SnodeSet.swift */; }; FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */; }; @@ -772,7 +772,7 @@ FD17D7D827F658E200122BE0 /* SSKDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7D727F658E200122BE0 /* SSKDestination.swift */; }; FD17D7E127F67BD400122BE0 /* SnodeReceivedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */; }; FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E427F6A09900122BE0 /* Identity.swift */; }; - FD17D7E727F6A16700122BE0 /* _002_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */; }; + FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */; }; FD17D7EA27F6A1C600122BE0 /* SUKLegacyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */; }; FD28A4F227E990E800FF65E7 /* BlockingManagerRemovalMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */; }; FD28A4F427EA79F800FF65E7 /* BlockListUIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */; }; @@ -781,6 +781,9 @@ FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */; }; FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; }; FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */; }; + FD6A7A692818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */; }; + FD6A7A6B2818C17C00035AC1 /* UpdateProfilePictureJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */; }; + FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */; }; FD705A8C278CDB5600F16121 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */; }; FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8D278CE29800F16121 /* String+Localization.swift */; }; FD705A90278CEBBC00F16121 /* ShareAppExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */; }; @@ -790,6 +793,11 @@ FD859F0027C4691300510D0C /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFF27C4691300510D0C /* MockDataGenerator.swift */; }; FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; }; FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; }; + FD90040F2818AB6D00ABAAF6 /* GetSnodePoolJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */; }; + FD9004122818ABDC00ABAAF6 /* Job.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73F280402C4004C14C5 /* Job.swift */; }; + FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; + FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; + FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; FDA8EAFE280E8B78002B68E5 /* FailedMessagesJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFD280E8B78002B68E5 /* FailedMessagesJob.swift */; }; FDA8EB00280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */; }; FDA8EB09280E90FB002B68E5 /* AppSetup.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF287255B6D85007E1867 /* AppSetup.m */; }; @@ -798,12 +806,9 @@ FDC4389E27BA2B8A00C60D73 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; }; FDCDB8DE2810F73B00352A0C /* Differentiable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DD2810F73B00352A0C /* Differentiable+Utilities.swift */; }; FDCDB8E02811007F00352A0C /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDCDB8DF2811007F00352A0C /* HomeViewModel.swift */; }; - FDE77F69280F9EDA002CFC5D /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; }; FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; }; - FDF0B740280402C4004C14C5 /* Job.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73F280402C4004C14C5 /* Job.swift */; }; FDF0B7422804EA4F004C14C5 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7412804EA4F004C14C5 /* _002_SetupStandardJobs.swift */; }; - FDF0B7442804EF1B004C14C5 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FDF0B7472804F0CE004C14C5 /* DisappearingMessagesJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7462804F0CE004C14C5 /* DisappearingMessagesJob.swift */; }; FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74828060D13004C14C5 /* QuotedReplyModel.swift */; }; FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B74A28061F7A004C14C5 /* InteractionAttachment.swift */; }; @@ -1815,7 +1820,7 @@ FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacyModels.swift; sourceTree = ""; }; FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; - FD17D7A327F40F8100122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D7A627F41AF000122BE0 /* SSKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKLegacyModels.swift; sourceTree = ""; }; FD17D7A927F41BF500122BE0 /* SnodeSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeSet.swift; sourceTree = ""; }; FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessageInfo.swift; sourceTree = ""; }; @@ -1836,7 +1841,7 @@ FD17D7D727F658E200122BE0 /* SSKDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKDestination.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 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUKLegacyModels.swift; sourceTree = ""; }; FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingManagerRemovalMigration.swift; sourceTree = ""; }; FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = ""; }; @@ -1846,6 +1851,9 @@ FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = ""; }; FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.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 = ""; }; + FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = ""; }; FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = ""; }; FD705A8D278CE29800F16121 /* String+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = ""; }; FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppExtensionContext.swift; sourceTree = ""; }; @@ -1855,6 +1863,8 @@ FD859EFF27C4691300510D0C /* MockDataGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataGenerator.swift; sourceTree = ""; }; FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = ""; }; FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.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 = ""; }; FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; FDA8EAFD280E8B78002B68E5 /* FailedMessagesJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessagesJob.swift; sourceTree = ""; }; FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAttachmentDownloadsJob.swift; sourceTree = ""; }; @@ -2853,8 +2863,6 @@ isa = PBXGroup; children = ( FDF0B7452804F0A8004C14C5 /* Types */, - FDF0B7432804EF1B004C14C5 /* JobRunner.swift */, - FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */, C352A2F425574B4700338F3E /* LegacyJob.swift */, C352A3922557883D00338F3E /* JobDelegate.swift */, C352A3882557876500338F3E /* JobQueue.swift */, @@ -3272,6 +3280,7 @@ C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */, C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */, C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */, + FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */, C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */, C3C2A5CD255385F300C340D1 /* Utilities */, ); @@ -3306,6 +3315,7 @@ B8A582AC258C653C00AFD84C /* Crypto */, B8A582AB258C64E800AFD84C /* Database */, B8A582B0258C66C900AFD84C /* General */, + FD9004102818ABB000ABAAF6 /* JobRunner */, B8A582AF258C665E00AFD84C /* Media */, B8A582B9258C696200AFD84C /* Messaging */, B8A582AE258C65D000AFD84C /* Networking */, @@ -3331,7 +3341,6 @@ C3C2A7802553AA6300C340D1 /* Protos */, C3C2A70A25539DF900C340D1 /* Meta */, C32C5BB9256DC7C4003C73A2 /* To Do */, - C3BBE0752554CDA60050F1E3 /* Configuration.swift */, C3BBE07F2554CDD70050F1E3 /* Storage.swift */, C32C5BCB256DC818003C73A2 /* Database */, C300A5BB2554AFFB00555489 /* Messages */, @@ -3520,6 +3529,7 @@ C33FD9AC255A548A00E217F9 /* SignalUtilitiesKit */, C331FF1C2558F9D300070591 /* SessionUIKit */, C3C2A6F125539DE700C340D1 /* SessionMessagingKit */, + C3BBE0752554CDA60050F1E3 /* Configuration.swift */, C3C2A5A0255385C100C340D1 /* SessionSnodeKit */, C3C2A67A255388CC00C340D1 /* SessionUtilitiesKit */, D221A08C169C9E5E00537ABF /* Frameworks */, @@ -3648,7 +3658,6 @@ FD09799A27FFC82D00936362 /* Quote.swift */, FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */, FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */, - FDF0B73F280402C4004C14C5 /* Job.swift */, ); path = Models; sourceTree = ""; @@ -3686,7 +3695,8 @@ isa = PBXGroup; children = ( FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */, - FD17D7A327F40F8100122BE0 /* _002_YDBToGRDBMigration.swift */, + FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */, + FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */, ); path = Migrations; sourceTree = ""; @@ -3744,7 +3754,8 @@ isa = PBXGroup; children = ( FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */, - FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */, + FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */, + FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */, ); path = Migrations; sourceTree = ""; @@ -3753,6 +3764,7 @@ isa = PBXGroup; children = ( FD17D7E427F6A09900122BE0 /* Identity.swift */, + FDF0B73F280402C4004C14C5 /* Job.swift */, FD17D7CC27F546FF00122BE0 /* Setting.swift */, ); path = Models; @@ -3801,12 +3813,23 @@ path = Views; sourceTree = ""; }; + FD9004102818ABB000ABAAF6 /* JobRunner */ = { + isa = PBXGroup; + children = ( + FDF0B7432804EF1B004C14C5 /* JobRunner.swift */, + FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */, + ); + path = JobRunner; + sourceTree = ""; + }; FDF0B7452804F0A8004C14C5 /* Types */ = { isa = PBXGroup; children = ( FDF0B7462804F0CE004C14C5 /* DisappearingMessagesJob.swift */, FDA8EAFD280E8B78002B68E5 /* FailedMessagesJob.swift */, FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */, + FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */, + FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */, C352A2FE25574B6300338F3E /* MessageSendJob.swift */, C352A31225574F5200338F3E /* MessageReceiveJob.swift */, C352A32E2557549C00338F3E /* NotifyPushServerJob.swift */, @@ -4792,7 +4815,7 @@ C3C2A5C7255385EE00C340D1 /* SnodeAPI.swift in Sources */, C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */, FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */, - FD17D7A427F40F8100122BE0 /* _002_YDBToGRDBMigration.swift in Sources */, + FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, C3C2A5DC2553860B00C340D1 /* Promise+Threading.swift in Sources */, C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */, FD17D7D227F5797A00122BE0 /* SSKEndpoint.swift in Sources */, @@ -4802,9 +4825,11 @@ FD17D7A727F41AF000122BE0 /* SSKLegacyModels.swift in Sources */, C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */, FD17D7D827F658E200122BE0 /* SSKDestination.swift in Sources */, + FD6A7A6D2818C61500035AC1 /* _002_SetupStandardJobs.swift in Sources */, FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */, FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */, C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */, + FD90040F2818AB6D00ABAAF6 /* GetSnodePoolJob.swift in Sources */, FD17D7D427F6584600122BE0 /* SSKError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4832,6 +4857,7 @@ B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */, B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, FD09796727F6B0B600936362 /* Sodium+Conversion.swift in Sources */, + FD9004122818ABDC00ABAAF6 /* Job.swift in Sources */, FDF0B74D280664E9004C14C5 /* PersistableRecord+Utilities.swift in Sources */, FD09797927FAB7E800936362 /* ImageFormat.swift in Sources */, C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */, @@ -4839,6 +4865,7 @@ C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */, C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */, FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */, + FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */, C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */, C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */, @@ -4886,16 +4913,18 @@ C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */, FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */, FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */, + FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.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 */, FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */, + FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */, FD09797227FAA2F500936362 /* Optional+Utilities.swift in Sources */, C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */, C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */, - FD17D7E727F6A16700122BE0 /* _002_YDBToGRDBMigration.swift in Sources */, + FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4909,7 +4938,6 @@ C3A3A156256E1B91004D228D /* ProtoUtils.m in Sources */, FD09799927FFC1A300936362 /* Attachment.swift in Sources */, C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */, - FDE77F69280F9EDA002CFC5D /* JobRunnerError.swift in Sources */, FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */, C352A32F2557549C00338F3E /* NotifyPushServerJob.swift in Sources */, 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */, @@ -4919,6 +4947,7 @@ FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */, + FD6A7A6B2818C17C00035AC1 /* UpdateProfilePictureJob.swift in Sources */, C352A3932557883D00338F3E /* JobDelegate.swift in Sources */, C32C5B84256DC54F003C73A2 /* SSKEnvironment.m in Sources */, C3A3A108256E1A5C004D228D /* OWSIncomingMessageFinder.m in Sources */, @@ -4937,7 +4966,6 @@ C32C5CA4256DD1DC003C73A2 /* TSAccountManager.m in Sources */, C352A3892557876500338F3E /* JobQueue.swift in Sources */, C3BBE0B52554F0E10050F1E3 /* ProofOfWork.swift in Sources */, - FDF0B740280402C4004C14C5 /* Job.swift in Sources */, C32C59C1256DB41F003C73A2 /* TSGroupThread.m in Sources */, C3A3A08F256E1728004D228D /* FullTextSearchFinder.swift in Sources */, FDF0B7472804F0CE004C14C5 /* DisappearingMessagesJob.swift in Sources */, @@ -4990,6 +5018,7 @@ C32C5E15256DDC78003C73A2 /* SSKPreferences.swift in Sources */, C32C5D9C256DD6DC003C73A2 /* OWSOutgoingReceiptManager.m in Sources */, C32C5C4F256DCC36003C73A2 /* Storage+OpenGroups.swift in Sources */, + FD6A7A692818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift in Sources */, C3DA9C0725AE7396008F7C7E /* ConfigurationMessage.swift in Sources */, B8856CEE256F1054001CE70E /* OWSAudioPlayer.m in Sources */, FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */, @@ -5018,7 +5047,6 @@ C3A3A0EC256E1949004D228D /* OWSRecipientIdentity.m in Sources */, FDF0B74B28061F7A004C14C5 /* InteractionAttachment.swift in Sources */, B8F5F56525EC8453003BF8D4 /* Notification+Contacts.swift in Sources */, - FDF0B7442804EF1B004C14C5 /* JobRunner.swift in Sources */, C32C5AB2256DBE8F003C73A2 /* TSMessage.m in Sources */, FD09796E27FA6D0000936362 /* Contact.swift in Sources */, C3A3A0FE256E1A3C004D228D /* TSDatabaseSecondaryIndexes.m in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index f2750a931..5adcfecde 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -165,16 +165,10 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve } } - // Re-populate snode pool if needed - SnodeAPI.getSnodePool().retainUntilComplete() - // Onion request path countries cache DispatchQueue.global(qos: .utility).sync { let _ = IP2Country.shared.populateCacheIfNeeded() } - - // Get default open group rooms if needed - OpenGroupAPIV2.getDefaultRoomsIfNeeded() } override func viewWillAppear(_ animated: Bool) { diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index dcbc72f48..2683b0d5c 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -162,9 +162,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func applicationDidBecomeActive(_ application: UIApplication) { guard !CurrentAppContext().isRunningTests else { return } - // FIXME: We should move this somewhere to prevent typos from breaking it - let sharedUserDefaults: UserDefaults? = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") - sharedUserDefaults?[.isMainAppActive] = true + UserDefaults.sharedLokiProject?[.isMainAppActive] = true ensureRootViewController() adapt(appMode: AppModeManager.getAppModeOrSystemDefault()) @@ -186,8 +184,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func applicationWillResignActive(_ application: UIApplication) { clearAllNotificationsAndRestoreBadgeCount() - let sharedUserDefaults: UserDefaults? = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") - sharedUserDefaults?[.isMainAppActive] = false + UserDefaults.sharedLokiProject?[.isMainAppActive] = false DDLog.flushLog() } @@ -258,7 +255,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD enableBackgroundRefreshIfNecessary() JobRunner.appDidBecomeActive() - SnodeAPI.getSnodePool().retainUntilComplete() startPollersIfNeeded() if CurrentAppContext().isMainApp { diff --git a/Session/Meta/MainAppContext.m b/Session/Meta/MainAppContext.m index b5e82f87d..d1b703476 100644 --- a/Session/Meta/MainAppContext.m +++ b/Session/Meta/MainAppContext.m @@ -226,9 +226,8 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic - (void)setMainAppBadgeNumber:(NSInteger)value { [[UIApplication sharedApplication] setApplicationIconBadgeNumber:value]; - NSUserDefaults *sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.loki-project.loki-messenger"]; - [sharedUserDefaults setInteger:value forKey:@"currentBadgeNumber"]; - [sharedUserDefaults synchronize]; + [[NSUserDefaults sharedLokiProject] setInteger:value forKey:@"currentBadgeNumber"]; + [[NSUserDefaults sharedLokiProject] synchronize]; } - (nullable UIViewController *)frontmostViewController diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index fabce3628..e6496f801 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -8,7 +8,7 @@ import SessionMessagingKit import SessionUtilitiesKit public enum SyncPushTokensJob: JobExecutor { - public static let maxFailureCount: UInt = 0 + public static let maxFailureCount: Int = -1 public static let requiresThreadId: Bool = false public static let requiresInteractionId: Bool = false @@ -18,12 +18,8 @@ public enum SyncPushTokensJob: JobExecutor { failure: @escaping (Job, Error?, Bool) -> (), deferred: @escaping (Job) -> () ) { - // Don't schedule run when inactive or not in main app - var isMainAppActive = false - if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") { - isMainAppActive = sharedUserDefaults[.isMainAppActive] - } - guard isMainAppActive else { + // Don't run when inactive or not in main app + guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { deferred(job) // Don't need to do anything if it's not the main app return } diff --git a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift index bb4cc6518..af8637bfa 100644 --- a/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionMessagingKit/Database/Migrations/_001_InitialSetupMigration.swift @@ -278,29 +278,5 @@ enum _001_InitialSetupMigration: Migration { t.uniqueKey([.threadId, .sentTimestampMs, .serverHash, .openGroupMessageServerId]) } - - try db.create(table: Job.self) { t in - t.column(.id, .integer) - .notNull() - .primaryKey(autoincrement: true) - t.column(.failureCount, .integer) - .notNull() - .defaults(to: 0) - t.column(.variant, .integer) - .notNull() - .indexed() // Quicker querying - t.column(.behaviour, .integer).notNull() // TODO: Indexed??? - t.column(.nextRunTimestamp, .double) - .notNull() // TODO: Should this just be nullable??? (or do we want to fetch by this?) - .indexed() // Quicker querying - .defaults(to: 0) - t.column(.threadId, .text) - .indexed() // Quicker querying - .references(SessionThread.self, onDelete: .cascade) // Delete if thread deleted - t.column(.interactionId, .text) - .indexed() // Quicker querying - .references(Interaction.self, onDelete: .cascade) // Delete if interaction deleted - t.column(.details, .blob) - } } } diff --git a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift index 000b36cf0..35dc37a4f 100644 --- a/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift +++ b/SessionMessagingKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -17,32 +17,28 @@ enum _002_SetupStandardJobs: Migration { try autoreleasepool { // TODO: Add additional jobs from the AppDelegate _ = try Job( - failureCount: 0, variant: .disappearingMessages, - behaviour: .recurringOnLaunch, - nextRunTimestamp: 0 + behaviour: .recurringOnLaunchBlockingOncePerSession ).inserted(db) _ = try Job( - failureCount: 0, variant: .failedMessages, - behaviour: .recurringOnLaunch, - nextRunTimestamp: 0 + behaviour: .recurringOnLaunchBlocking ).inserted(db) _ = try Job( - failureCount: 0, variant: .failedAttachmentDownloads, - behaviour: .recurringOnLaunch, - nextRunTimestamp: 0 + behaviour: .recurringOnLaunchBlocking ).inserted(db) - // Note: This job exists in the 'Session' target but that doesn't have it's own migrations _ = try Job( - failureCount: 0, - variant: .syncPushTokens, - behaviour: .recurringOnLaunch, - nextRunTimestamp: 0 + variant: .updateProfilePicture, + behaviour: .recurringOnActive + ).inserted(db) + + _ = try Job( + variant: .retrieveDefaultOpenGroupRooms, + behaviour: .recurringOnActive ).inserted(db) } } diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index deb9d7c72..ad45f5482 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -334,6 +334,11 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu } } + // Delete any jobs associated to this interaction + try Job + .filter(Job.Columns.interactionId == id) + .deleteAll(db) + return try performDelete(db) } } diff --git a/SessionMessagingKit/Database/Models/SessionThread.swift b/SessionMessagingKit/Database/Models/SessionThread.swift index 068f41e29..82a3dd92b 100644 --- a/SessionMessagingKit/Database/Models/SessionThread.swift +++ b/SessionMessagingKit/Database/Models/SessionThread.swift @@ -120,6 +120,17 @@ public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, self.notificationSound = notificationSound self.mutedUntilTimestamp = mutedUntilTimestamp } + + // MARK: - Custom Database Interaction + + public func delete(_ db: Database) throws -> Bool { + // Delete any jobs associated to this thread + try Job + .filter(Job.Columns.threadId == id) + .deleteAll(db) + + return try performDelete(db) + } } // MARK: - GRDB Interactions diff --git a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift index 990b90273..1a238112f 100644 --- a/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift +++ b/SessionMessagingKit/Jobs/Types/AttachmentDownloadJob.swift @@ -7,7 +7,7 @@ import SessionSnodeKit import SignalCoreKit public enum AttachmentDownloadJob: JobExecutor { - public static var maxFailureCount: UInt = 10 + public static var maxFailureCount: Int = 10 public static var requiresThreadId: Bool = true public static let requiresInteractionId: Bool = true diff --git a/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift b/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift index acc59cab1..a54b52ab5 100644 --- a/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift +++ b/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift @@ -5,7 +5,7 @@ import GRDB import SessionUtilitiesKit public enum DisappearingMessagesJob: JobExecutor { - public static let maxFailureCount: UInt = 0 + public static let maxFailureCount: Int = -1 public static let requiresThreadId: Bool = false public static let requiresInteractionId: Bool = false @@ -25,8 +25,15 @@ public enum DisappearingMessagesJob: JobExecutor { .filter(sql: "(\(Interaction.Columns.expiresStartedAtMs) + (\(Interaction.Columns.expiresInSeconds) * 1000) <= \(timestampNowMs)") .deleteAll(db) - // Update the next run timestamp for the DisappearingMessagesJob + // Update the next run timestamp for the DisappearingMessagesJob (if the call + // to 'updateNextRunIfNeeded' returns 'nil' then it doesn't need to re-run so + // should have it's 'nextRunTimestamp' cleared) return updateNextRunIfNeeded(db) + .defaulting( + to: try job + .with(nextRunTimestamp: 0) + .saved(db) + ) } success(updatedJob ?? job, false) @@ -40,12 +47,8 @@ public enum DisappearingMessagesJob: JobExecutor { public extension DisappearingMessagesJob { @discardableResult static func updateNextRunIfNeeded(_ db: Database) -> Job? { - // Don't schedule run when inactive or not in main app - var isMainAppActive = false - if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") { - isMainAppActive = sharedUserDefaults[.isMainAppActive] - } - guard isMainAppActive else { return nil } + // Don't run when inactive or not in main app + guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { return nil } // If there is another expiring message then update the job to run 1 second after it's meant to expire let nextExpirationTimestampMs: Double? = try? Double diff --git a/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift b/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift index 2c10422da..de2b02743 100644 --- a/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift +++ b/SessionMessagingKit/Jobs/Types/FailedAttachmentDownloadsJob.swift @@ -6,7 +6,7 @@ import SignalCoreKit import SessionUtilitiesKit public enum FailedAttachmentDownloadsJob: JobExecutor { - public static let maxFailureCount: UInt = 0 + public static let maxFailureCount: Int = -1 public static let requiresThreadId: Bool = false public static let requiresInteractionId: Bool = false diff --git a/SessionMessagingKit/Jobs/Types/FailedMessagesJob.swift b/SessionMessagingKit/Jobs/Types/FailedMessagesJob.swift index 30104c176..b72e37e05 100644 --- a/SessionMessagingKit/Jobs/Types/FailedMessagesJob.swift +++ b/SessionMessagingKit/Jobs/Types/FailedMessagesJob.swift @@ -6,7 +6,7 @@ import SignalCoreKit import SessionUtilitiesKit public enum FailedMessagesJob: JobExecutor { - public static let maxFailureCount: UInt = 0 + public static let maxFailureCount: Int = -1 public static let requiresThreadId: Bool = false public static let requiresInteractionId: Bool = false diff --git a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift index f95d476d3..f0993cb8b 100644 --- a/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageReceiveJob.swift @@ -5,7 +5,7 @@ import PromiseKit import SessionUtilitiesKit public enum MessageReceiveJob: JobExecutor { - public static var maxFailureCount: UInt = 10 + public static var maxFailureCount: Int = 10 public static var requiresThreadId: Bool = true public static let requiresInteractionId: Bool = false diff --git a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift index 2f6dc5cc3..11744912c 100644 --- a/SessionMessagingKit/Jobs/Types/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/Types/MessageSendJob.swift @@ -7,7 +7,7 @@ import SessionUtilitiesKit import SessionSnodeKit public enum MessageSendJob: JobExecutor { - public static var maxFailureCount: UInt = 10 + public static var maxFailureCount: Int = 10 public static var requiresThreadId: Bool = true public static let requiresInteractionId: Bool = false // Some messages don't have interactions diff --git a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift index 95f00bf27..4ade85d97 100644 --- a/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift +++ b/SessionMessagingKit/Jobs/Types/NotifyPushServerJob.swift @@ -6,7 +6,7 @@ import SessionSnodeKit import SessionUtilitiesKit public enum NotifyPushServerJob: JobExecutor { - public static var maxFailureCount: UInt = 20 + public static var maxFailureCount: Int = 20 public static var requiresThreadId: Bool = false public static let requiresInteractionId: Bool = false diff --git a/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift new file mode 100644 index 000000000..d9a9b4c2c --- /dev/null +++ b/SessionMessagingKit/Jobs/Types/RetrieveDefaultOpenGroupRoomsJob.swift @@ -0,0 +1,30 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SignalCoreKit +import SessionUtilitiesKit + +public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor { + public static let maxFailureCount: Int = -1 + public static let requiresThreadId: Bool = false + public static let requiresInteractionId: Bool = false + + public static func run( + _ job: Job, + success: @escaping (Job, Bool) -> (), + 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 { + deferred(job) // Don't need to do anything if it's not the main app + return + } + + OpenGroupAPIV2.getDefaultRoomsIfNeeded() + .done { _ in success(job, false) } + .catch { error in failure(job, error, false) } + .retainUntilComplete() + } +} diff --git a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift index 9f6d8dfd1..b2f2d443a 100644 --- a/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift +++ b/SessionMessagingKit/Jobs/Types/SendReadReceiptsJob.swift @@ -6,7 +6,7 @@ import PromiseKit import SessionUtilitiesKit public enum SendReadReceiptsJob: JobExecutor { - public static let maxFailureCount: UInt = 0 + public static let maxFailureCount: Int = -1 public static let requiresThreadId: Bool = false public static let requiresInteractionId: Bool = false private static let minRunFrequency: TimeInterval = 3 diff --git a/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift new file mode 100644 index 000000000..60e8d39d3 --- /dev/null +++ b/SessionMessagingKit/Jobs/Types/UpdateProfilePictureJob.swift @@ -0,0 +1,46 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SignalCoreKit +import SessionUtilitiesKit + +public enum UpdateProfilePictureJob: JobExecutor { + public static let maxFailureCount: Int = -1 + public static let requiresThreadId: Bool = false + public static let requiresInteractionId: Bool = false + + public static func run( + _ job: Job, + success: @escaping (Job, Bool) -> (), + 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 { + deferred(job) // Don't need to do anything if it's not the main app + return + } + + // Only re-upload the profile picture if enough time has passed since the last upload + guard + let lastProfilePictureUpload: Date = UserDefaults.standard[.lastProfilePictureUpload], + Date().timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60) + else { + deferred(job) + return + } + + // Note: The user defaults flag is updated in ProfileManager + let profile: Profile = Profile.fetchOrCreateCurrentUser() + let profilePicture: UIImage? = ProfileManager.profileAvatar(id: profile.id) + + ProfileManager.updateLocal( + profileName: profile.name, + avatarImage: profilePicture, + requiredSync: true, + success: { success(job, false) }, + failure: { error in failure(job, error, false) } + ) + } +} diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift b/SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift index 11686edc8..466db6303 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift @@ -11,9 +11,4 @@ extension OpenGroupAPIV2 { public static func objc_isUserModerator(_ publicKey: String, for room: String, on server: String) -> Bool { return isUserModerator(publicKey, for: room, on: server) } - - @objc(getDefaultRoomsIfNeeded) - public static func objc_getDefaultRoomsIfNeeded() { - getDefaultRoomsIfNeeded() - } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift index 5791f5e85..297f27a91 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift @@ -13,7 +13,7 @@ extension MessageReceiver { /// /// **Note:** This is a slightly optimised version of the `decryptWithSessionProtocol` function which just skips /// the validation (handled when the job actually runs) and doesn't throw - internal static func extractSenderPublicKey(_ db: Database, from envelope: SNProtoEnvelope) -> String? { + public static func extractSenderPublicKey(_ db: Database, from envelope: SNProtoEnvelope) -> String? { guard let ciphertext: Data = envelope.content, let userX25519KeyPair: Box.KeyPair = Identity.fetchUserKeyPair(db) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 7746af472..c70f5bd22 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -29,11 +29,7 @@ extension MessageReceiver { default: fatalError() } - var isMainAppActive = false - if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") { - isMainAppActive = sharedUserDefaults[.isMainAppActive] - } - guard isMainAppActive else { return } + guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { return } // Touch the thread to update the home screen preview let storage = SNMessagingKitConfiguration.shared.storage @@ -399,11 +395,7 @@ extension MessageReceiver { // Note: `message.sentTimestamp` is in ms let messageSentTimestamp: TimeInterval = TimeInterval((message.sentTimestamp ?? 0) / 1000) - - var isMainAppActive = false - if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") { - isMainAppActive = sharedUserDefaults[.isMainAppActive] - } + let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) // Parse & persist attachments diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 8f2246a54..a16110c55 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -89,10 +89,7 @@ public final class MessageSender : NSObject { let (promise, seal) = Promise.pending() let userPublicKey: String = getUserHexEncodedPublicKey(db) - var isMainAppActive = false - if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") { - isMainAppActive = sharedUserDefaults[.isMainAppActive] - } + let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) // Set the timestamp, sender and recipient if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index bcaba688b..c1f601365 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -124,31 +124,39 @@ public final class ClosedGroupPoller : NSObject { SNLog("Received \(messages.count) new message(s) in closed group with public key: \(groupPublicKey).") GRDBStorage.shared.write { db in + var jobDetailMessages: [MessageReceiveJob.Details.MessageInfo] = [] + messages.forEach { message in guard let envelope = SNProtoEnvelope.from(message) else { return } do { - JobRunner.add( - db, - job: Job( - variant: .messageReceive, - behaviour: .runOnce, - threadId: groupPublicKey, - details: MessageReceiveJob.Details( - data: try envelope.serializedData(), - serverHash: message.info.hash, - isBackgroundPoll: false - ) + jobDetailMessages.append( + MessageReceiveJob.Details.MessageInfo( + data: try envelope.serializedData(), + serverHash: message.info.hash ) ) // Persist the received message after the MessageReceiveJob is created - try message.info.save(db) + _ = try message.info.saved(db) } catch { SNLog("Failed to deserialize envelope due to error: \(error).") } } + + JobRunner.add( + db, + job: Job( + variant: .messageReceive, + behaviour: .runOnce, + threadId: groupPublicKey, + details: MessageReceiveJob.Details( + messages: jobDetailMessages, + isBackgroundPoll: false + ) + ) + ) } } } diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index 3ff6d224f..9012b6115 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -95,7 +95,9 @@ public final class Poller : NSObject { private func poll(_ snode: Snode, seal longTermSeal: Resolver) -> Promise { guard isPolling else { return Promise { $0.fulfill(()) } } + let userPublicKey = getUserHexEncodedPublicKey() + return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey) .then(on: Threading.pollerQueue) { [weak self] rawResponse -> Promise in guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } @@ -106,6 +108,8 @@ public final class Poller : NSObject { SNLog("Received \(messages.count) new message(s).") GRDBStorage.shared.write { db in + var threadMessages: [String: [MessageReceiveJob.Details.MessageInfo]] = [:] + messages.forEach { message in guard let envelope = SNProtoEnvelope.from(message) else { return } @@ -117,27 +121,36 @@ public final class Poller : NSObject { } do { - JobRunner.add( - db, - job: Job( - variant: .messageReceive, - behaviour: .runOnce, - threadId: threadId, - details: MessageReceiveJob.Details( + threadMessages[threadId ?? ""] = (threadMessages[threadId ?? ""] ?? []) + .appending( + MessageReceiveJob.Details.MessageInfo( data: try envelope.serializedData(), - serverHash: message.info.hash, - isBackgroundPoll: false + serverHash: message.info.hash ) ) - ) // Persist the received message after the MessageReceiveJob is created - try message.info.save(db) + _ = try message.info.saved(db) } catch { SNLog("Failed to deserialize envelope due to error: \(error).") } } + + threadMessages.forEach { threadId, threadMessages in + JobRunner.add( + db, + job: Job( + variant: .messageReceive, + behaviour: .runOnce, + threadId: threadId, + details: MessageReceiveJob.Details( + messages: threadMessages, + isBackgroundPoll: false + ) + ) + ) + } } } diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 0524b6a7f..a0cdf0845 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -19,11 +19,9 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension self.notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent // Abort if the main app is running - var isMainAppActive = false - if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") { - isMainAppActive = sharedUserDefaults.bool(forKey: "isMainAppActive") + guard !(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { + return self.completeSilenty() } - guard !isMainAppActive else { return self.completeSilenty() } // Perform main setup DispatchQueue.main.sync { self.setUpIfNecessary() { } } diff --git a/SessionSnodeKit/Configuration.swift b/SessionSnodeKit/Configuration.swift index 7d4b0a333..24df07e8e 100644 --- a/SessionSnodeKit/Configuration.swift +++ b/SessionSnodeKit/Configuration.swift @@ -13,15 +13,18 @@ public enum SNSnodeKit { // Just to make the external API nice identifier: .snodeKit, migrations: [ [ - _001_InitialSetupMigration.self + _001_InitialSetupMigration.self, + _002_SetupStandardJobs.self ], [ - _002_YDBToGRDBMigration.self + _003_YDBToGRDBMigration.self ] ] ) } public static func configure() { + // Configure the job executors + JobRunner.add(executor: GetSnodePoolJob.self, for: .getSnodePool) } } diff --git a/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift new file mode 100644 index 000000000..5f7965c6b --- /dev/null +++ b/SessionSnodeKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -0,0 +1,20 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SessionUtilitiesKit + +/// This migration sets up the standard jobs, since we want these jobs to run before any "once-off" jobs we do this migration +/// before running the `YDBToGRDBMigration` +enum _002_SetupStandardJobs: Migration { + static let identifier: String = "SetupStandardJobs" + + static func migrate(_ db: Database) throws { + try autoreleasepool { + _ = try Job( + variant: .getSnodePool, + behaviour: .recurringOnActiveBlocking + ).inserted(db) + } + } +} diff --git a/SessionSnodeKit/Database/Migrations/_002_YDBToGRDBMigration.swift b/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift similarity index 98% rename from SessionSnodeKit/Database/Migrations/_002_YDBToGRDBMigration.swift rename to SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift index 444c5b03f..6f254aff0 100644 --- a/SessionSnodeKit/Database/Migrations/_002_YDBToGRDBMigration.swift +++ b/SessionSnodeKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -4,7 +4,7 @@ import Foundation import GRDB import SessionUtilitiesKit -enum _002_YDBToGRDBMigration: Migration { +enum _003_YDBToGRDBMigration: Migration { static let identifier: String = "YDBToGRDBMigration" static func migrate(_ db: Database) throws { @@ -103,6 +103,7 @@ enum _002_YDBToGRDBMigration: Migration { var lastMessageResults: [String: (hash: String, json: JSON)] = [:] var receivedMessageResults: [String: Set] = [:] + // TODO: Move into the top read block??? Storage.read { transaction in // Extract the received message hashes transaction.enumerateKeysAndObjects(inCollection: Legacy.receivedMessagesCollection) { key, object, _ in diff --git a/SessionSnodeKit/GetSnodePoolJob.swift b/SessionSnodeKit/GetSnodePoolJob.swift new file mode 100644 index 000000000..eeb9f7fe2 --- /dev/null +++ b/SessionSnodeKit/GetSnodePoolJob.swift @@ -0,0 +1,24 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import SignalCoreKit +import SessionUtilitiesKit + +public enum GetSnodePoolJob: JobExecutor { + public static let maxFailureCount: Int = -1 + public static let requiresThreadId: Bool = false + public static let requiresInteractionId: Bool = false + + public static func run( + _ job: Job, + success: @escaping (Job, Bool) -> (), + failure: @escaping (Job, Error?, Bool) -> (), + deferred: @escaping (Job) -> () + ) { + SnodeAPI.getSnodePool() + .done { _ in success(job, false) } + .catch { error in failure(job, error, false) } + .retainUntilComplete() + } +} diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index fe2305425..b64ec0594 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -258,7 +258,9 @@ public final class SnodeAPI : NSObject { public static func getSnodePool() -> Promise> { loadSnodePoolIfNeeded() let now = Date() - let hasSnodePoolExpired = given(GRDBStorage.shared[.lastSnodePoolRefreshDate]) { now.timeIntervalSince($0) > 2 * 60 * 60 } ?? true + let hasSnodePoolExpired = given(GRDBStorage.shared[.lastSnodePoolRefreshDate]) { + now.timeIntervalSince($0) > 2 * 60 * 60 + }.defaulting(to: true) let snodePool = SnodeAPI.snodePool let hasInsufficientSnodes = (snodePool.count < minSnodePoolCount) @@ -441,6 +443,7 @@ public final class SnodeAPI : NSObject { // "pubkey_ed25519" : ed25519PublicKey, // "signature" : signature.toBase64()! ] + return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters) } diff --git a/SessionUtilitiesKit/Configuration.swift b/SessionUtilitiesKit/Configuration.swift index cfc16ae73..c102292d2 100644 --- a/SessionUtilitiesKit/Configuration.swift +++ b/SessionUtilitiesKit/Configuration.swift @@ -18,11 +18,12 @@ public enum SNUtilitiesKit { // Just to make the external API nice identifier: .utilitiesKit, migrations: [ [ - // Intentionally including the '_002_YDBToGRDBMigration' in the first migration + // Intentionally including the '_003_YDBToGRDBMigration' in the first migration // set to ensure the 'Identity' data is migrated before any other migrations are // run (some need access to the users publicKey) _001_InitialSetupMigration.self, - _002_YDBToGRDBMigration.self + _002_SetupStandardJobs.self, + _003_YDBToGRDBMigration.self ] ] ) diff --git a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift index b67b006d9..858ccd70a 100644 --- a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift +++ b/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift @@ -15,6 +15,30 @@ enum _001_InitialSetupMigration: Migration { t.column(.data, .blob).notNull() } + try db.create(table: Job.self) { t in + t.column(.id, .integer) + .notNull() + .primaryKey(autoincrement: true) + t.column(.failureCount, .integer) + .notNull() + .defaults(to: 0) + t.column(.variant, .integer) + .notNull() + .indexed() // Quicker querying + t.column(.behaviour, .integer) + .notNull() + .indexed() // Quicker querying + t.column(.nextRunTimestamp, .double) + .notNull() + .indexed() // Quicker querying + .defaults(to: 0) + t.column(.threadId, .text) + .indexed() // Quicker querying + t.column(.interactionId, .text) + .indexed() // Quicker querying + t.column(.details, .blob) + } + try db.create(table: Setting.self) { t in t.column(.key, .text) .notNull() diff --git a/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift b/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift new file mode 100644 index 000000000..473c9ff9a --- /dev/null +++ b/SessionUtilitiesKit/Database/Migrations/_002_SetupStandardJobs.swift @@ -0,0 +1,21 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +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` +enum _002_SetupStandardJobs: Migration { + static let identifier: String = "SetupStandardJobs" + + static func migrate(_ db: Database) throws { + try autoreleasepool { + // Note: This job exists in the 'Session' target but that doesn't have it's own migrations + _ = try Job( + variant: .syncPushTokens, + behaviour: .recurringOnLaunch + ).inserted(db) + } + } +} diff --git a/SessionUtilitiesKit/Database/Migrations/_002_YDBToGRDBMigration.swift b/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift similarity index 96% rename from SessionUtilitiesKit/Database/Migrations/_002_YDBToGRDBMigration.swift rename to SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift index 61065617b..55de4d025 100644 --- a/SessionUtilitiesKit/Database/Migrations/_002_YDBToGRDBMigration.swift +++ b/SessionUtilitiesKit/Database/Migrations/_003_YDBToGRDBMigration.swift @@ -3,7 +3,7 @@ import Foundation import GRDB -enum _002_YDBToGRDBMigration: Migration { +enum _003_YDBToGRDBMigration: Migration { static let identifier: String = "YDBToGRDBMigration" static func migrate(_ db: Database) throws { @@ -65,6 +65,7 @@ enum _002_YDBToGRDBMigration: Migration { throw GRDBStorageError.migrationFailed } + print("RAWR publicKey \(userX25519KeyPair.publicKey.toHexString())") try autoreleasepool { // Insert the data into GRDB try Identity( diff --git a/SessionMessagingKit/Database/Models/Job.swift b/SessionUtilitiesKit/Database/Models/Job.swift similarity index 64% rename from SessionMessagingKit/Database/Models/Job.swift rename to SessionUtilitiesKit/Database/Models/Job.swift index a6159af22..ae9410138 100644 --- a/SessionMessagingKit/Database/Models/Job.swift +++ b/SessionUtilitiesKit/Database/Models/Job.swift @@ -2,21 +2,9 @@ import Foundation import GRDB -import SessionUtilitiesKit -import SwiftProtobuf public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible { public static var databaseTableName: String { "job" } - internal static let threadForeignKey = ForeignKey( - [Columns.threadId], - to: [SessionThread.Columns.id] - ) - internal static let interactionForeignKey = ForeignKey( - [Columns.interactionId], - to: [Interaction.Columns.id] - ) - internal static let thread = hasOne(SessionThread.self, using: Job.threadForeignKey) - internal static let interaction = hasOne(Interaction.self, using: Job.interactionForeignKey) public typealias Columns = CodingKeys public enum CodingKeys: String, CodingKey, ColumnExpression { @@ -35,13 +23,31 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer /// at the timestamp of the next disappearing message case disappearingMessages + /// This is a recurring job that ensures the app retrieves a service node pool on active + /// + /// **Note:** This is a blocking job so it will run before any other jobs and prevent them from + /// running until it's complete + case getSnodePool + + /// This is a recurring job that checks if the user needs to update their profile picture on launch, and if so + /// attempt to download the latest + case updateProfilePicture + + /// This is a recurring job that ensures the app fetches the default open group rooms on launch + case retrieveDefaultOpenGroupRooms /// This is a recurring job that runs on launch and flags any messages marked as 'sending' to /// be in their 'failed' state + /// + /// **Note:** This is a blocking job so it will run before any other jobs and prevent them from + /// running until it's complete case failedMessages = 1000 /// This is a recurring job that runs on launch and flags any attachments marked as 'uploading' to /// be in their 'failed' state + /// + /// **Note:** This is a blocking job so it will run before any other jobs and prevent them from + /// running until it's complete case failedAttachmentDownloads /// This is a recurring job that runs on return from background and registeres and uploads the @@ -84,11 +90,26 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer /// the future) in order to be run again case recurring - /// This job will run once each launch + /// This job will run once each launch and may run again during the same session if `nextRunTimestamp` + /// gets set case recurringOnLaunch - /// This job will run once each whenever the app becomes active (launch and return from background) + /// This job will run once each launch and may run again during the same session if `nextRunTimestamp` + /// gets set, it also must complete before any other jobs can run + case recurringOnLaunchBlocking + + /// This job will run once each launch and may run again during the same session if `nextRunTimestamp` + /// gets set, it also must complete before any other jobs can run + case recurringOnLaunchBlockingOncePerSession + + /// This job will run once each whenever the app becomes active (launch and return from background) and + /// may run again during the same session if `nextRunTimestamp` gets set case recurringOnActive + + /// This job will run once each whenever the app becomes active (launch and return from background) and + /// may run again during the same session if `nextRunTimestamp` gets set, it also must complete before + /// any other jobs can run + case recurringOnActiveBlocking } /// The `id` value is auto incremented by the database, if the `Job` hasn't been inserted into @@ -122,16 +143,6 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer /// JSON encoded data required for the job public let details: Data? - // MARK: - Relationships - - public var thread: QueryInterfaceRequest { - request(for: Job.thread) - } - - public var interaction: QueryInterfaceRequest { - request(for: Job.interaction) - } - // MARK: - Initialization fileprivate init( @@ -201,26 +212,72 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer } } +// MARK: - GRDB Interactions + +extension Job { + internal static func filterPendingJobs(excludeFutureJobs: Bool = true) -> QueryInterfaceRequest { + let query: QueryInterfaceRequest = Job + .filter( + // TODO: Should this include other behaviours? (what happens if one of the other types fails???? Just leave it until the next launch/active???) Set a 'failureCount' and use that to determine if it should run? (reset on success) + // Retrieve all 'runOnce' and 'recurring' jobs + [ + Job.Behaviour.runOnce, + Job.Behaviour.recurring + ].contains(Job.Columns.behaviour) || ( + // Retrieve any 'recurringOnLaunch' and 'recurringOnActive' jobs that have a + // 'nextRunTimestamp' + [ + Job.Behaviour.recurringOnLaunch, + Job.Behaviour.recurringOnLaunchBlocking, + Job.Behaviour.recurringOnActive, + Job.Behaviour.recurringOnActiveBlocking + ].contains(Job.Columns.behaviour) && + Job.Columns.nextRunTimestamp > 0 + ) + ) + .order(Job.Columns.nextRunTimestamp) + .order(Job.Columns.id) + + guard excludeFutureJobs else { + return query + } + + return query + .filter(Job.Columns.nextRunTimestamp <= Date().timeIntervalSince1970) + } +} + // MARK: - Convenience public extension Job { - internal func with( + var isBlocking: Bool { + switch self.behaviour { + case .recurringOnLaunchBlocking, + .recurringOnLaunchBlockingOncePerSession, + .recurringOnActiveBlocking: + return true + + default: return false + } + } + + func with( failureCount: UInt = 0, - nextRunTimestamp: TimeInterval? + nextRunTimestamp: TimeInterval ) -> Job { return Job( id: id, failureCount: failureCount, variant: variant, behaviour: behaviour, - nextRunTimestamp: (nextRunTimestamp ?? self.nextRunTimestamp), + nextRunTimestamp: nextRunTimestamp, threadId: threadId, interactionId: interactionId, details: details ) } - internal func with(details: T) -> Job? { + func with(details: T) -> Job? { guard let detailsData: Data = try? JSONEncoder().encode(details) else { return nil } return Job( diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index c2f361957..6e09a5711 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -34,6 +34,9 @@ public enum SNUserDefaults { } public extension UserDefaults { + @objc static var sharedLokiProject: UserDefaults? { + UserDefaults(suiteName: "group.com.loki-project.loki-messenger") + } subscript(bool: SNUserDefaults.Bool) -> Bool { get { return self.bool(forKey: bool.rawValue) } diff --git a/SessionMessagingKit/Jobs/JobRunner.swift b/SessionUtilitiesKit/JobRunner/JobRunner.swift similarity index 80% rename from SessionMessagingKit/Jobs/JobRunner.swift rename to SessionUtilitiesKit/JobRunner/JobRunner.swift index 0f9d154b3..39d6e896b 100644 --- a/SessionMessagingKit/Jobs/JobRunner.swift +++ b/SessionUtilitiesKit/JobRunner/JobRunner.swift @@ -3,10 +3,12 @@ import Foundation import GRDB import SignalCoreKit -import SessionUtilitiesKit public protocol JobExecutor { - static var maxFailureCount: UInt { get } + /// The maximum number of times the job can fail before it fails permanently + /// + /// **Note:** A value of `-1` means it will retry indefinitely + static var maxFailureCount: Int { get } static var requiresThreadId: Bool { get } static var requiresInteractionId: Bool { get } @@ -57,8 +59,8 @@ public final class JobRunner { } } - // TODO: Could this be a bottleneck? (single serial queue to process all these jobs? Group by thread?) - // TODO: Multi-thread support + // TODO: Could this be a bottleneck? (single serial queue to process all these jobs? Group by thread?). + // TODO: Multi-thread support. private static let queueKey: DispatchSpecificKey = DispatchSpecificKey() private static let queueContext: String = "JobRunner" private static let internalQueue: DispatchQueue = { @@ -74,6 +76,7 @@ public final class JobRunner { private static var jobQueue: Atomic<[Job]> = Atomic([]) private static var jobsCurrentlyRunning: Atomic> = Atomic([]) + private static var perSessionJobsCompleted: Atomic> = Atomic([]) // MARK: - Configuration @@ -182,27 +185,64 @@ public final class JobRunner { .filter( [ Job.Behaviour.recurringOnLaunch, + Job.Behaviour.recurringOnLaunchBlocking, + Job.Behaviour.recurringOnLaunchBlockingOncePerSession, Job.Behaviour.runOnceNextLaunch ].contains(Job.Columns.behaviour) ) + .order(Job.Columns.id) .fetchAll(db) } guard let jobsToRun: [Job] = maybeJobsToRun else { return } - jobQueue.mutate { $0.append(contentsOf: jobsToRun) } + jobQueue.mutate { + // Insert any blocking jobs after any existing blocking jobs then add + // the remaining jobs to the end of the queue + let lastBlockingIndex = $0.lastIndex(where: { $0.isBlocking }) + .defaulting(to: $0.startIndex.advanced(by: -1)) + .advanced(by: 1) + + $0.insert( + contentsOf: jobsToRun.filter { $0.isBlocking }, + at: lastBlockingIndex + ) + $0.append( + contentsOf: jobsToRun.filter { !$0.isBlocking } + ) + } } public static func appDidBecomeActive() { let maybeJobsToRun: [Job]? = GRDBStorage.shared.read { db in try Job - .filter(Job.Columns.behaviour == Job.Behaviour.recurringOnActive) + .filter( + [ + Job.Behaviour.recurringOnActive, + Job.Behaviour.recurringOnActiveBlocking + ].contains(Job.Columns.behaviour) + ) + .order(Job.Columns.id) .fetchAll(db) } guard let jobsToRun: [Job] = maybeJobsToRun else { return } - jobQueue.mutate { $0.append(contentsOf: jobsToRun) } + jobQueue.mutate { + // Insert any blocking jobs after any existing blocking jobs then add + // the remaining jobs to the end of the queue + let lastBlockingIndex = $0.lastIndex(where: { $0.isBlocking }) + .defaulting(to: $0.startIndex.advanced(by: -1)) + .advanced(by: 1) + + $0.insert( + contentsOf: jobsToRun.filter { $0.isBlocking }, + at: lastBlockingIndex + ) + $0.append( + contentsOf: jobsToRun.filter { !$0.isBlocking } + ) + } // Start the job runner if needed if !isRunning.wrappedValue { @@ -228,21 +268,14 @@ public final class JobRunner { guard DispatchQueue.getSpecific(key: queueKey) == queueContext else { internalQueue.async { start() - } + }// TODO: Want to have multiple threads for this (attachment download should be separate - do we even use attachment upload anymore???) return } // Get any pending jobs let maybeJobsToRun: [Job]? = GRDBStorage.shared.read { db in - try Job - .filter( - [ - Job.Behaviour.runOnce, - Job.Behaviour.recurring - ].contains(Job.Columns.behaviour) - ) - .filter(Job.Columns.nextRunTimestamp <= Date().timeIntervalSince1970) - .order(Job.Columns.nextRunTimestamp) + try Job// TODO: Test this + .filterPendingJobs() .fetchAll(db) } @@ -300,6 +333,12 @@ public final class JobRunner { return } + // If the 'nextRunTimestamp' for the job is in the future then don't run it yet + guard nextJob.nextRunTimestamp <= Date().timeIntervalSince1970 else { + handleJobDeferred(nextJob) + return + } + // Update the state to indicate it's running // // Note: We need to store 'numJobsRemaining' in it's own variable because @@ -324,21 +363,17 @@ public final class JobRunner { try TimeInterval .fetchOne( db, - Job + Job// TODO: Test this works as expected + .filterPendingJobs(excludeFutureJobs: false) .select(Job.Columns.nextRunTimestamp) - .filter( - [ - Job.Behaviour.runOnce, - Job.Behaviour.recurring - ].contains(Job.Columns.behaviour) - ) - .order(Job.Columns.nextRunTimestamp) ) } + guard let nextJobTimestamp: TimeInterval = nextJobTimestamp else { return } // If the next job isn't scheduled in the future then just restart the JobRunner immediately let secondsUntilNextJob: TimeInterval = (nextJobTimestamp - Date().timeIntervalSince1970) + guard secondsUntilNextJob > 0 else { SNLog("[JobRunner] Restarting immediately for job scheduled \(Int(ceil(abs(secondsUntilNextJob)))) second\(Int(ceil(abs(secondsUntilNextJob))) == 1 ? "" : "s")) ago") @@ -378,6 +413,9 @@ public final class JobRunner { .saved(db) } + case .recurringOnLaunchBlockingOncePerSession: + perSessionJobsCompleted.mutate { $0 = $0.inserting(job.id) } + default: break } @@ -393,17 +431,48 @@ public final class JobRunner { guard GRDBStorage.shared.read({ db in try Job.exists(db, id: job.id ?? -1) }) == true else { SNLog("[JobRunner] \(job.variant) job canceled") jobsCurrentlyRunning.mutate { $0 = $0.removing(job.id) } - runNextJob() + + internalQueue.async { + runNextJob() + } return } + switch job.behaviour { + // If a "blocking" job failed then rerun it immediately + case .recurringOnLaunchBlocking, .recurringOnActiveBlocking: + SNLog("[JobRunner] blocking \(job.variant) job failed; retrying immediately") + jobQueue.mutate({ $0.insert(job, at: 0) }) + + internalQueue.async { + runNextJob() + } + return + + // For "blocking once per session" jobs only rerun it immediately if it hasn't already + // run this session + case .recurringOnLaunchBlockingOncePerSession: + guard !perSessionJobsCompleted.wrappedValue.contains(job.id ?? -1) else { break } + + SNLog("[JobRunner] blocking \(job.variant) job failed; retrying immediately") + perSessionJobsCompleted.mutate { $0 = $0.inserting(job.id) } + jobQueue.mutate({ $0.insert(job, at: 0) }) + + internalQueue.async { + runNextJob() + } + return + + default: break + } + GRDBStorage.shared.write { db in - // Check if the job has a 'maxFailureCount' (a value of '0' means it will always retry) - let maxFailureCount: UInt = (executorMap.wrappedValue[job.variant]?.maxFailureCount ?? 0) + // Get the max failure count for the job (a value of '-1' means it will retry indefinitely) + let maxFailureCount: Int = (executorMap.wrappedValue[job.variant]?.maxFailureCount ?? 0) guard !permanentFailure && - maxFailureCount > 0 && + maxFailureCount >= 0 && job.failureCount + 1 < maxFailureCount else { // If the job permanently failed or we have performed all of our retry attempts @@ -422,7 +491,9 @@ public final class JobRunner { } jobsCurrentlyRunning.mutate { $0 = $0.removing(job.id) } - runNextJob() + internalQueue.async { + runNextJob() + } } /// This function is called when a job neither succeeds or fails (this should only occur if the job has specific logic that makes it dependant diff --git a/SessionMessagingKit/Jobs/JobRunnerError.swift b/SessionUtilitiesKit/JobRunner/JobRunnerError.swift similarity index 100% rename from SessionMessagingKit/Jobs/JobRunnerError.swift rename to SessionUtilitiesKit/JobRunner/JobRunnerError.swift