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
This commit is contained in:
Morgan Pretty 2022-04-27 10:48:54 +10:00
parent 94742c80ec
commit 3baeb981d9
40 changed files with 519 additions and 205 deletions

View File

@ -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)

View File

@ -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 = "<group>"; };
FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacyModels.swift; sourceTree = "<group>"; };
FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; };
FD17D7A327F40F8100122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
FD17D7A327F40F8100122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
FD17D7A627F41AF000122BE0 /* SSKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKLegacyModels.swift; sourceTree = "<group>"; };
FD17D7A927F41BF500122BE0 /* SnodeSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeSet.swift; sourceTree = "<group>"; };
FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessageInfo.swift; sourceTree = "<group>"; };
@ -1836,7 +1841,7 @@
FD17D7D727F658E200122BE0 /* SSKDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKDestination.swift; sourceTree = "<group>"; };
FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessage.swift; sourceTree = "<group>"; };
FD17D7E427F6A09900122BE0 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = "<group>"; };
FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUKLegacyModels.swift; sourceTree = "<group>"; };
FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingManagerRemovalMigration.swift; sourceTree = "<group>"; };
FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = "<group>"; };
@ -1846,6 +1851,9 @@
FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; };
FD6A7A682818BE7300035AC1 /* RetrieveDefaultOpenGroupRoomsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveDefaultOpenGroupRoomsJob.swift; sourceTree = "<group>"; };
FD6A7A6A2818C17C00035AC1 /* UpdateProfilePictureJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateProfilePictureJob.swift; sourceTree = "<group>"; };
FD6A7A6C2818C61500035AC1 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = "<group>"; };
FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = "<group>"; };
FD705A8D278CE29800F16121 /* String+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppExtensionContext.swift; sourceTree = "<group>"; };
@ -1855,6 +1863,8 @@
FD859EFF27C4691300510D0C /* MockDataGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataGenerator.swift; sourceTree = "<group>"; };
FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; };
FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = "<group>"; };
FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = "<group>"; };
FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = "<group>"; };
FDA8EAFD280E8B78002B68E5 /* FailedMessagesJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessagesJob.swift; sourceTree = "<group>"; };
FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAttachmentDownloadsJob.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 = "<group>";
@ -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 = "<group>";
@ -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 = "<group>";
};
FD9004102818ABB000ABAAF6 /* JobRunner */ = {
isa = PBXGroup;
children = (
FDF0B7432804EF1B004C14C5 /* JobRunner.swift */,
FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */,
);
path = JobRunner;
sourceTree = "<group>";
};
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 */,

View File

@ -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) {

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
}
}

View File

@ -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

View File

@ -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) }
)
}
}

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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

View File

@ -89,10 +89,7 @@ public final class MessageSender : NSObject {
let (promise, seal) = Promise<Void>.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

View File

@ -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
)
)
)
}
}
}

View File

@ -95,7 +95,9 @@ public final class Poller : NSObject {
private func poll(_ snode: Snode, seal longTermSeal: Resolver<Void>) -> Promise<Void> {
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<Void> 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
)
)
)
}
}
}

View File

@ -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() { } }

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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<String>] = [:]
// TODO: Move into the top read block???
Storage.read { transaction in
// Extract the received message hashes
transaction.enumerateKeysAndObjects(inCollection: Legacy.receivedMessagesCollection) { key, object, _ in

View File

@ -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()
}
}

View File

@ -258,7 +258,9 @@ public final class SnodeAPI : NSObject {
public static func getSnodePool() -> Promise<Set<Snode>> {
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)
}

View File

@ -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
]
]
)

View File

@ -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()

View File

@ -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)
}
}
}

View File

@ -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(

View File

@ -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<SessionThread> {
request(for: Job.thread)
}
public var interaction: QueryInterfaceRequest<Interaction> {
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<Job> {
let query: QueryInterfaceRequest<Job> = 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<T: Encodable>(details: T) -> Job? {
func with<T: Encodable>(details: T) -> Job? {
guard let detailsData: Data = try? JSONEncoder().encode(details) else { return nil }
return Job(

View File

@ -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) }

View File

@ -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<String>()
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<Set<Int64>> = Atomic([])
private static var perSessionJobsCompleted: Atomic<Set<Int64>> = 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