Started laying the ground work for unit testing the JobRunnner
Starting injecting dependencies for the JobRunner Turned the JobRunner into a singleton instance
This commit is contained in:
parent
65e7009b0a
commit
a7af1ca768
|
@ -825,6 +825,7 @@
|
||||||
FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; };
|
FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */; };
|
||||||
FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; };
|
FDD250702837199200198BDA /* GarbageCollectionJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */; };
|
||||||
FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; };
|
FDD250722837234B00198BDA /* MediaGalleryNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */; };
|
||||||
|
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; };
|
||||||
FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; };
|
FDE77F6B280FEB28002CFC5D /* ControlMessageProcessRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F6A280FEB28002CFC5D /* ControlMessageProcessRecord.swift */; };
|
||||||
FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; };
|
FDED2E3C282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDED2E3B282E1B5D00B2CD2A /* UICollectionView+ReusableView.swift */; };
|
||||||
FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; };
|
FDF0B73C27FFD3D6004C14C5 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B73B27FFD3D6004C14C5 /* LinkPreview.swift */; };
|
||||||
|
@ -1902,6 +1903,7 @@
|
||||||
FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = "<group>"; };
|
FDD2506D283711D600198BDA /* DifferenceKit+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DifferenceKit+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = "<group>"; };
|
FDD2506F2837199200198BDA /* GarbageCollectionJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageCollectionJob.swift; sourceTree = "<group>"; };
|
||||||
FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = "<group>"; };
|
FDD250712837234B00198BDA /* MediaGalleryNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGalleryNavigationController.swift; sourceTree = "<group>"; };
|
||||||
|
FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = "<group>"; };
|
||||||
FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = "<group>"; };
|
FDE7214F287E50D50093DF33 /* ProtoWrappers.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = ProtoWrappers.py; sourceTree = "<group>"; };
|
||||||
FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = "<group>"; };
|
FDE72150287E50D50093DF33 /* LintLocalizableStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LintLocalizableStrings.swift; sourceTree = "<group>"; };
|
||||||
FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = "<group>"; };
|
FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerError.swift; sourceTree = "<group>"; };
|
||||||
|
@ -3908,6 +3910,7 @@
|
||||||
children = (
|
children = (
|
||||||
FD37EA1228AB3F60003AE748 /* Database */,
|
FD37EA1228AB3F60003AE748 /* Database */,
|
||||||
FD83B9B927CF20A5005E1583 /* General */,
|
FD83B9B927CF20A5005E1583 /* General */,
|
||||||
|
FDDF074829DAB35200E5E8B5 /* JobRunner */,
|
||||||
);
|
);
|
||||||
path = SessionUtilitiesKitTests;
|
path = SessionUtilitiesKitTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4112,6 +4115,14 @@
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
FDDF074829DAB35200E5E8B5 /* JobRunner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */,
|
||||||
|
);
|
||||||
|
path = JobRunner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
FDE7214E287E50D50093DF33 /* Scripts */ = {
|
FDE7214E287E50D50093DF33 /* Scripts */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -5804,6 +5815,7 @@
|
||||||
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
||||||
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
|
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
|
||||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */,
|
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */,
|
||||||
|
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */,
|
||||||
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -16,16 +16,17 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
// Don't run when inactive or not in main app or if the user doesn't exist yet
|
// Don't run when inactive or not in main app or if the user doesn't exist yet
|
||||||
guard
|
guard
|
||||||
(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false),
|
(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false),
|
||||||
Identity.userExists()
|
Identity.userExists()
|
||||||
else {
|
else {
|
||||||
deferred(job) // Don't need to do anything if it's not the main app
|
deferred(job, dependencies) // Don't need to do anything if it's not the main app
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
// the main thread then swap to it
|
// the main thread then swap to it
|
||||||
guard Thread.isMainThread else {
|
guard Thread.isMainThread else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
run(job, queue: queue, success: success, failure: failure, deferred: deferred)
|
run(job, queue: queue, success: success, failure: failure, deferred: deferred, dependencies: dependencies)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -61,7 +62,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
!UIApplication.shared.isRegisteredForRemoteNotifications ||
|
!UIApplication.shared.isRegisteredForRemoteNotifications ||
|
||||||
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
|
Date().timeIntervalSince(lastPushNotificationSync) >= SyncPushTokensJob.maxFrequency
|
||||||
else {
|
else {
|
||||||
deferred(job) // Don't need to do anything if push notifications are already registered
|
deferred(job, dependencies) // Don't need to do anything if push notifications are already registered
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +91,9 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ensure(on: queue) { success(job, false) } // We want to complete this job regardless of success or failure
|
.ensure(on: queue) {
|
||||||
|
success(job, false, dependencies) // We want to complete this job regardless of success or failure
|
||||||
|
}
|
||||||
.retainUntilComplete()
|
.retainUntilComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +110,9 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
SyncPushTokensJob.run(
|
SyncPushTokensJob.run(
|
||||||
job,
|
job,
|
||||||
queue: DispatchQueue.global(qos: .default),
|
queue: DispatchQueue.global(qos: .default),
|
||||||
success: { _, _ in },
|
success: { _, _, _ in },
|
||||||
failure: { _, _, _ in },
|
failure: { _, _, _, _ in },
|
||||||
deferred: { _ in }
|
deferred: { _, _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,9 +171,9 @@ public final class BackgroundPoller {
|
||||||
MessageReceiveJob.run(
|
MessageReceiveJob.run(
|
||||||
job,
|
job,
|
||||||
queue: DispatchQueue.main,
|
queue: DispatchQueue.main,
|
||||||
success: { _, _ in seal.fulfill(()) },
|
success: { _, _, _ in seal.fulfill(()) },
|
||||||
failure: { _, _, _ in seal.fulfill(()) },
|
failure: { _, _, _, _ in seal.fulfill(()) },
|
||||||
deferred: { _ in seal.fulfill(()) }
|
deferred: { _, _ in seal.fulfill(()) }
|
||||||
)
|
)
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
|
|
|
@ -258,10 +258,10 @@ public extension Profile {
|
||||||
///
|
///
|
||||||
/// **Note:** This method intentionally does **not** save the newly created Profile,
|
/// **Note:** This method intentionally does **not** save the newly created Profile,
|
||||||
/// it will need to be explicitly saved after calling
|
/// it will need to be explicitly saved after calling
|
||||||
static func fetchOrCreateCurrentUser() -> Profile {
|
static func fetchOrCreateCurrentUser(dependencies: Dependencies = Dependencies()) -> Profile {
|
||||||
var userPublicKey: String = ""
|
var userPublicKey: String = ""
|
||||||
|
|
||||||
let exisingProfile: Profile? = Storage.shared.read { db in
|
let exisingProfile: Profile? = dependencies.storage.read { db in
|
||||||
userPublicKey = getUserHexEncodedPublicKey(db)
|
userPublicKey = getUserHexEncodedPublicKey(db)
|
||||||
|
|
||||||
return try Profile.fetchOne(db, id: userPublicKey)
|
return try Profile.fetchOne(db, id: userPublicKey)
|
||||||
|
|
|
@ -14,9 +14,10 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let threadId: String = job.threadId,
|
let threadId: String = job.threadId,
|
||||||
|
@ -25,7 +26,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
let attachment: Attachment = Storage.shared
|
let attachment: Attachment = Storage.shared
|
||||||
.read({ db in try Attachment.fetchOne(db, id: details.attachmentId) })
|
.read({ db in try Attachment.fetchOne(db, id: details.attachmentId) })
|
||||||
else {
|
else {
|
||||||
failure(job, JobRunnerError.missingRequiredDetails, false)
|
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
// an AttachmentDownloadJob to get created for an attachment which has already been
|
// an AttachmentDownloadJob to get created for an attachment which has already been
|
||||||
// downloaded/uploaded so in those cases just succeed immediately
|
// downloaded/uploaded so in those cases just succeed immediately
|
||||||
guard attachment.state != .downloaded && attachment.state != .uploaded else {
|
guard attachment.state != .downloaded && attachment.state != .uploaded else {
|
||||||
success(job, false)
|
success(job, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
// if an attachment ends up stuck in a "downloading" state incorrectly
|
// if an attachment ends up stuck in a "downloading" state incorrectly
|
||||||
guard attachment.state != .downloading else {
|
guard attachment.state != .downloading else {
|
||||||
let otherCurrentJobAttachmentIds: Set<String> = JobRunner
|
let otherCurrentJobAttachmentIds: Set<String> = JobRunner
|
||||||
.defailsForCurrentlyRunningJobs(of: .attachmentDownload)
|
.detailsForCurrentlyRunningJobs(of: .attachmentDownload)
|
||||||
.filter { key, _ in key != job.id }
|
.filter { key, _ in key != job.id }
|
||||||
.values
|
.values
|
||||||
.compactMap { data -> String? in
|
.compactMap { data -> String? in
|
||||||
|
@ -57,7 +58,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
// then we should update the state of the attachment to be failed to avoid having attachments
|
// then we should update the state of the attachment to be failed to avoid having attachments
|
||||||
// appear in an endlessly downloading state
|
// appear in an endlessly downloading state
|
||||||
if !otherCurrentJobAttachmentIds.contains(attachment.id) {
|
if !otherCurrentJobAttachmentIds.contains(attachment.id) {
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
_ = try Attachment
|
_ = try Attachment
|
||||||
.filter(id: attachment.id)
|
.filter(id: attachment.id)
|
||||||
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedDownload))
|
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedDownload))
|
||||||
|
@ -70,12 +71,12 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
// If there is another current job then just fail this one permanently, otherwise let it
|
// If there is another current job then just fail this one permanently, otherwise let it
|
||||||
// retry (if there are more retry attempts available) and in the next retry it's state should
|
// retry (if there are more retry attempts available) and in the next retry it's state should
|
||||||
// be 'failedDownload' so we won't get stuck in a loop
|
// be 'failedDownload' so we won't get stuck in a loop
|
||||||
failure(job, nil, otherCurrentJobAttachmentIds.contains(attachment.id))
|
failure(job, nil, otherCurrentJobAttachmentIds.contains(attachment.id), dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update to the 'downloading' state (no need to update the 'attachment' instance)
|
// Update to the 'downloading' state (no need to update the 'attachment' instance)
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
try Attachment
|
try Attachment
|
||||||
.filter(id: attachment.id)
|
.filter(id: attachment.id)
|
||||||
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.downloading))
|
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.downloading))
|
||||||
|
@ -141,7 +142,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
///
|
///
|
||||||
/// **Note:** We **MUST** use the `'with()` function here as it will update the
|
/// **Note:** We **MUST** use the `'with()` function here as it will update the
|
||||||
/// `isValid` and `duration` values based on the downloaded data and the state
|
/// `isValid` and `duration` values based on the downloaded data and the state
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
_ = try attachment
|
_ = try attachment
|
||||||
.with(
|
.with(
|
||||||
state: .downloaded,
|
state: .downloaded,
|
||||||
|
@ -154,7 +155,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
.saved(db)
|
.saved(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
success(job, false)
|
success(job, false, dependencies)
|
||||||
}
|
}
|
||||||
.catch(on: queue) { error in
|
.catch(on: queue) { error in
|
||||||
OWSFileSystem.deleteFile(temporaryFileUrl.path)
|
OWSFileSystem.deleteFile(temporaryFileUrl.path)
|
||||||
|
@ -188,14 +189,14 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
///
|
///
|
||||||
/// **Note:** We **MUST** use the `'with()` function here as it will update the
|
/// **Note:** We **MUST** use the `'with()` function here as it will update the
|
||||||
/// `isValid` and `duration` values based on the downloaded data and the state
|
/// `isValid` and `duration` values based on the downloaded data and the state
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
_ = try Attachment
|
_ = try Attachment
|
||||||
.filter(id: attachment.id)
|
.filter(id: attachment.id)
|
||||||
.updateAll(db, Attachment.Columns.state.set(to: targetState))
|
.updateAll(db, Attachment.Columns.state.set(to: targetState))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trigger the failure and provide the `permanentFailure` value defined above
|
/// Trigger the failure and provide the `permanentFailure` value defined above
|
||||||
failure(job, error, permanentFailure)
|
failure(job, error, permanentFailure, dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,17 @@ public enum AttachmentUploadJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let threadId: String = job.threadId,
|
let threadId: String = job.threadId,
|
||||||
let interactionId: Int64 = job.interactionId,
|
let interactionId: Int64 = job.interactionId,
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData),
|
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData),
|
||||||
let (attachment, openGroup): (Attachment, OpenGroup?) = Storage.shared.read({ db in
|
let (attachment, openGroup): (Attachment, OpenGroup?) = dependencies.storage.read({ db in
|
||||||
guard let attachment: Attachment = try Attachment.fetchOne(db, id: details.attachmentId) else {
|
guard let attachment: Attachment = try Attachment.fetchOne(db, id: details.attachmentId) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -31,20 +32,20 @@ public enum AttachmentUploadJob: JobExecutor {
|
||||||
return (attachment, try OpenGroup.fetchOne(db, id: threadId))
|
return (attachment, try OpenGroup.fetchOne(db, id: threadId))
|
||||||
})
|
})
|
||||||
else {
|
else {
|
||||||
failure(job, JobRunnerError.missingRequiredDetails, false)
|
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the original interaction no longer exists then don't bother uploading the attachment (ie. the
|
// If the original interaction no longer exists then don't bother uploading the attachment (ie. the
|
||||||
// message was deleted before it even got sent)
|
// message was deleted before it even got sent)
|
||||||
guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
|
guard dependencies.storage.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
|
||||||
failure(job, StorageError.objectNotFound, true)
|
failure(job, StorageError.objectNotFound, true, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the attachment is still pending download the hold off on running this job
|
// If the attachment is still pending download the hold off on running this job
|
||||||
guard attachment.state != .pendingDownload && attachment.state != .downloading else {
|
guard attachment.state != .pendingDownload && attachment.state != .downloading else {
|
||||||
deferred(job)
|
deferred(job, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +72,8 @@ public enum AttachmentUploadJob: JobExecutor {
|
||||||
.map { response -> String in response.id }
|
.map { response -> String in response.id }
|
||||||
},
|
},
|
||||||
encrypt: (openGroup == nil),
|
encrypt: (openGroup == nil),
|
||||||
success: { _ in success(job, false) },
|
success: { _ in success(job, false, dependencies) },
|
||||||
failure: { error in failure(job, error, false) }
|
failure: { error in failure(job, error, false, dependencies) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,16 @@ public enum DisappearingMessagesJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
// The 'backgroundTask' gets captured and cleared within the 'completion' block
|
// The 'backgroundTask' gets captured and cleared within the 'completion' block
|
||||||
let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
||||||
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function)
|
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function)
|
||||||
|
|
||||||
let updatedJob: Job? = Storage.shared.write { db in
|
let updatedJob: Job? = dependencies.storage.write { db in
|
||||||
_ = try Interaction
|
_ = try Interaction
|
||||||
.filter(Interaction.Columns.expiresStartedAtMs != nil)
|
.filter(Interaction.Columns.expiresStartedAtMs != nil)
|
||||||
.filter((Interaction.Columns.expiresStartedAtMs + (Interaction.Columns.expiresInSeconds * 1000)) <= timestampNowMs)
|
.filter((Interaction.Columns.expiresStartedAtMs + (Interaction.Columns.expiresInSeconds * 1000)) <= timestampNowMs)
|
||||||
|
@ -35,7 +36,7 @@ public enum DisappearingMessagesJob: JobExecutor {
|
||||||
.saved(db)
|
.saved(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
success(updatedJob ?? job, false)
|
success(updatedJob ?? job, false, dependencies)
|
||||||
|
|
||||||
// The 'if' is only there to prevent the "variable never read" warning from showing
|
// The 'if' is only there to prevent the "variable never read" warning from showing
|
||||||
if backgroundTask != nil { backgroundTask = nil }
|
if backgroundTask != nil { backgroundTask = nil }
|
||||||
|
|
|
@ -13,12 +13,13 @@ public enum FailedAttachmentDownloadsJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
// Update all 'sending' message states to 'failed'
|
// Update all 'sending' message states to 'failed'
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
let changeCount: Int = try Attachment
|
let changeCount: Int = try Attachment
|
||||||
.filter(Attachment.Columns.state == Attachment.State.downloading)
|
.filter(Attachment.Columns.state == Attachment.State.downloading)
|
||||||
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedDownload))
|
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedDownload))
|
||||||
|
@ -26,6 +27,6 @@ public enum FailedAttachmentDownloadsJob: JobExecutor {
|
||||||
Logger.debug("Marked \(changeCount) attachments as failed")
|
Logger.debug("Marked \(changeCount) attachments as failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
success(job, false)
|
success(job, false, dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,13 @@ public enum FailedMessageSendsJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
// Update all 'sending' message states to 'failed'
|
// Update all 'sending' message states to 'failed'
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
let sendChangeCount: Int = try RecipientState
|
let sendChangeCount: Int = try RecipientState
|
||||||
.filter(RecipientState.Columns.state == RecipientState.State.sending)
|
.filter(RecipientState.Columns.state == RecipientState.State.sending)
|
||||||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.failed))
|
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.failed))
|
||||||
|
@ -33,6 +34,6 @@ public enum FailedMessageSendsJob: JobExecutor {
|
||||||
SNLog("Marked \(changeCount) message\(changeCount == 1 ? "" : "s") as failed (\(attachmentChangeCount) upload\(attachmentChangeCount == 1 ? "" : "s") cancelled)")
|
SNLog("Marked \(changeCount) message\(changeCount == 1 ? "" : "s") as failed (\(attachmentChangeCount) upload\(attachmentChangeCount == 1 ? "" : "s") cancelled)")
|
||||||
}
|
}
|
||||||
|
|
||||||
success(job, false)
|
success(job, false, dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,10 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
/// Determine what types of data we want to collect (if we didn't provide any then assume we want to collect everything)
|
/// Determine what types of data we want to collect (if we didn't provide any then assume we want to collect everything)
|
||||||
///
|
///
|
||||||
|
@ -57,7 +58,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
return typesToCollect.asSet()
|
return typesToCollect.asSet()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
Storage.shared.writeAsync(
|
dependencies.storage.writeAsync(
|
||||||
updates: { db in
|
updates: { db in
|
||||||
/// Remove any typing indicators
|
/// Remove any typing indicators
|
||||||
if finalTypesToCollect.contains(.threadTypingIndicators) {
|
if finalTypesToCollect.contains(.threadTypingIndicators) {
|
||||||
|
@ -339,7 +340,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
|
|
||||||
// If we couldn't get the file lists then fail (invalid state and don't want to delete all attachment/profile files)
|
// If we couldn't get the file lists then fail (invalid state and don't want to delete all attachment/profile files)
|
||||||
guard let fileInfo: FileInfo = maybeFileInfo else {
|
guard let fileInfo: FileInfo = maybeFileInfo else {
|
||||||
failure(job, StorageError.generic, false)
|
failure(job, StorageError.generic, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +415,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
|
|
||||||
// Report a single file deletion as a job failure (even if other content was successfully removed)
|
// Report a single file deletion as a job failure (even if other content was successfully removed)
|
||||||
guard deletionErrors.isEmpty else {
|
guard deletionErrors.isEmpty else {
|
||||||
failure(job, (deletionErrors.first ?? StorageError.generic), false)
|
failure(job, (deletionErrors.first ?? StorageError.generic), false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,7 +425,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
UserDefaults.standard[.lastGarbageCollection] = Date()
|
UserDefaults.standard[.lastGarbageCollection] = Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
success(job, false)
|
success(job, false, dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,22 +13,23 @@ public enum MessageReceiveJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||||
else {
|
else {
|
||||||
failure(job, JobRunnerError.missingRequiredDetails, false)
|
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedJob: Job = job
|
var updatedJob: Job = job
|
||||||
var leastSevereError: Error?
|
var leastSevereError: Error?
|
||||||
|
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
var remainingMessagesToProcess: [Details.MessageInfo] = []
|
var remainingMessagesToProcess: [Details.MessageInfo] = []
|
||||||
|
|
||||||
for messageInfo in details.messages {
|
for messageInfo in details.messages {
|
||||||
|
@ -86,13 +87,13 @@ public enum MessageReceiveJob: JobExecutor {
|
||||||
// Handle the result
|
// Handle the result
|
||||||
switch leastSevereError {
|
switch leastSevereError {
|
||||||
case let error as MessageReceiverError where !error.isRetryable:
|
case let error as MessageReceiverError where !error.isRetryable:
|
||||||
failure(updatedJob, error, true)
|
failure(updatedJob, error, true, dependencies)
|
||||||
|
|
||||||
case .some(let error):
|
case .some(let error):
|
||||||
failure(updatedJob, error, false)
|
failure(updatedJob, error, false, dependencies)
|
||||||
|
|
||||||
case .none:
|
case .none:
|
||||||
success(updatedJob, false)
|
success(updatedJob, false, dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,16 @@ public enum MessageSendJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||||
else {
|
else {
|
||||||
failure(job, JobRunnerError.missingRequiredDetails, false)
|
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,14 +37,14 @@ public enum MessageSendJob: JobExecutor {
|
||||||
let jobId: Int64 = job.id,
|
let jobId: Int64 = job.id,
|
||||||
let interactionId: Int64 = job.interactionId
|
let interactionId: Int64 = job.interactionId
|
||||||
else {
|
else {
|
||||||
failure(job, JobRunnerError.missingRequiredDetails, false)
|
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the original interaction no longer exists then don't bother sending the message (ie. the
|
// If the original interaction no longer exists then don't bother sending the message (ie. the
|
||||||
// message was deleted before it even got sent)
|
// message was deleted before it even got sent)
|
||||||
guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
|
guard dependencies.storage.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
|
||||||
failure(job, StorageError.objectNotFound, true)
|
failure(job, StorageError.objectNotFound, true, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ public enum MessageSendJob: JobExecutor {
|
||||||
//
|
//
|
||||||
// Note: Normal attachments should be sent in a non-durable way but any
|
// Note: Normal attachments should be sent in a non-durable way but any
|
||||||
// attachments for LinkPreviews and Quotes will be processed through this mechanism
|
// attachments for LinkPreviews and Quotes will be processed through this mechanism
|
||||||
let attachmentState: (shouldFail: Bool, shouldDefer: Bool, fileIds: [String])? = Storage.shared.write { db in
|
let attachmentState: (shouldFail: Bool, shouldDefer: Bool, fileIds: [String])? = dependencies.storage.write { db in
|
||||||
let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment
|
let allAttachmentStateInfo: [Attachment.StateInfo] = try Attachment
|
||||||
.stateInfo(interactionId: interactionId)
|
.stateInfo(interactionId: interactionId)
|
||||||
.fetchAll(db)
|
.fetchAll(db)
|
||||||
|
@ -110,7 +111,8 @@ public enum MessageSendJob: JobExecutor {
|
||||||
attachmentId: stateInfo.attachmentId
|
attachmentId: stateInfo.attachmentId
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
before: job
|
before: job,
|
||||||
|
dependencies: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.forEach { otherJobId, _ in
|
.forEach { otherJobId, _ in
|
||||||
|
@ -140,13 +142,13 @@ public enum MessageSendJob: JobExecutor {
|
||||||
// Note: If we have gotten to this point then any dependant attachment upload
|
// Note: If we have gotten to this point then any dependant attachment upload
|
||||||
// jobs will have permanently failed so this message send should also do so
|
// jobs will have permanently failed so this message send should also do so
|
||||||
guard attachmentState?.shouldFail == false else {
|
guard attachmentState?.shouldFail == false else {
|
||||||
failure(job, AttachmentError.notUploaded, true)
|
failure(job, AttachmentError.notUploaded, true, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer the job if we found incomplete uploads
|
// Defer the job if we found incomplete uploads
|
||||||
guard attachmentState?.shouldDefer == false else {
|
guard attachmentState?.shouldDefer == false else {
|
||||||
deferred(job)
|
deferred(job, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +163,7 @@ public enum MessageSendJob: JobExecutor {
|
||||||
details.message.threadId = (details.message.threadId ?? job.threadId)
|
details.message.threadId = (details.message.threadId ?? job.threadId)
|
||||||
|
|
||||||
// Perform the actual message sending
|
// Perform the actual message sending
|
||||||
Storage.shared.writeAsync { db -> Promise<Void> in
|
dependencies.storage.writeAsync { db -> Promise<Void> in
|
||||||
try MessageSender.sendImmediate(
|
try MessageSender.sendImmediate(
|
||||||
db,
|
db,
|
||||||
message: details.message,
|
message: details.message,
|
||||||
|
@ -171,20 +173,20 @@ public enum MessageSendJob: JobExecutor {
|
||||||
isSyncMessage: (details.isSyncMessage == true)
|
isSyncMessage: (details.isSyncMessage == true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.done(on: queue) { _ in success(job, false) }
|
.done(on: queue) { _ in success(job, false, dependencies) }
|
||||||
.catch(on: queue) { error in
|
.catch(on: queue) { error in
|
||||||
SNLog("Couldn't send message due to error: \(error).")
|
SNLog("Couldn't send message due to error: \(error).")
|
||||||
|
|
||||||
switch error {
|
switch error {
|
||||||
case let senderError as MessageSenderError where !senderError.isRetryable:
|
case let senderError as MessageSenderError where !senderError.isRetryable:
|
||||||
failure(job, error, true)
|
failure(job, error, true, dependencies)
|
||||||
|
|
||||||
case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 429: // Rate limited
|
case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 429: // Rate limited
|
||||||
failure(job, error, true)
|
failure(job, error, true, dependencies)
|
||||||
|
|
||||||
case SnodeAPIError.clockOutOfSync:
|
case SnodeAPIError.clockOutOfSync:
|
||||||
SNLog("\(originalSentTimestamp != nil ? "Permanently Failing" : "Failing") to send \(type(of: details.message)) due to clock out of sync issue.")
|
SNLog("\(originalSentTimestamp != nil ? "Permanently Failing" : "Failing") to send \(type(of: details.message)) due to clock out of sync issue.")
|
||||||
failure(job, error, (originalSentTimestamp != nil))
|
failure(job, error, (originalSentTimestamp != nil), dependencies)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
SNLog("Failed to send \(type(of: details.message)).")
|
SNLog("Failed to send \(type(of: details.message)).")
|
||||||
|
@ -192,15 +194,15 @@ public enum MessageSendJob: JobExecutor {
|
||||||
if details.message is VisibleMessage {
|
if details.message is VisibleMessage {
|
||||||
guard
|
guard
|
||||||
let interactionId: Int64 = job.interactionId,
|
let interactionId: Int64 = job.interactionId,
|
||||||
Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true
|
dependencies.storage.read({ db in try Interaction.exists(db, id: interactionId) }) == true
|
||||||
else {
|
else {
|
||||||
// The message has been deleted so permanently fail the job
|
// The message has been deleted so permanently fail the job
|
||||||
failure(job, error, true)
|
failure(job, error, true, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
failure(job, error, false)
|
failure(job, error, false, dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.retainUntilComplete()
|
.retainUntilComplete()
|
||||||
|
|
|
@ -13,15 +13,16 @@ public enum NotifyPushServerJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||||
else {
|
else {
|
||||||
failure(job, JobRunnerError.missingRequiredDetails, false)
|
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +33,8 @@ public enum NotifyPushServerJob: JobExecutor {
|
||||||
maxRetryCount: 4,
|
maxRetryCount: 4,
|
||||||
queue: queue
|
queue: queue
|
||||||
)
|
)
|
||||||
.done(on: queue) { _ in success(job, false) }
|
.done(on: queue) { _ in success(job, false, dependencies) }
|
||||||
.catch(on: queue) { error in failure(job, error, false) }
|
.catch(on: queue) { error in failure(job, error, false, dependencies) }
|
||||||
.retainUntilComplete()
|
.retainUntilComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,14 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
// Don't run when inactive or not in main app
|
// Don't run when inactive or not in main app
|
||||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||||
deferred(job) // Don't need to do anything if it's not the main app
|
deferred(job, dependencies) // Don't need to do anything if it's not the main app
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
||||||
// in the database so we need to create a dummy one to retrieve the default room data
|
// in the database so we need to create a dummy one to retrieve the default room data
|
||||||
let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: OpenGroupAPI.defaultServer)
|
let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: OpenGroupAPI.defaultServer)
|
||||||
|
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
guard try OpenGroup.exists(db, id: defaultGroupId) == false else { return }
|
guard try OpenGroup.exists(db, id: defaultGroupId) == false else { return }
|
||||||
|
|
||||||
_ = try OpenGroup(
|
_ = try OpenGroup(
|
||||||
|
@ -43,8 +44,8 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGroupManager.getDefaultRoomsIfNeeded()
|
OpenGroupManager.getDefaultRoomsIfNeeded()
|
||||||
.done(on: queue) { _ in success(job, false) }
|
.done(on: queue) { _ in success(job, false, dependencies) }
|
||||||
.catch(on: queue) { error in failure(job, error, false) }
|
.catch(on: queue) { error in failure(job, error, false, dependencies) }
|
||||||
.retainUntilComplete()
|
.retainUntilComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,17 @@ public enum SendReadReceiptsJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let threadId: String = job.threadId,
|
let threadId: String = job.threadId,
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||||
else {
|
else {
|
||||||
failure(job, JobRunnerError.missingRequiredDetails, false)
|
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,11 +32,11 @@ public enum SendReadReceiptsJob: JobExecutor {
|
||||||
// something is marked as read we want to try and run immediately so don't scuedule
|
// something is marked as read we want to try and run immediately so don't scuedule
|
||||||
// another run in this case)
|
// another run in this case)
|
||||||
guard !details.timestampMsValues.isEmpty else {
|
guard !details.timestampMsValues.isEmpty else {
|
||||||
success(job, true)
|
success(job, true, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.writeAsync { db in
|
.writeAsync { db in
|
||||||
try MessageSender.sendImmediate(
|
try MessageSender.sendImmediate(
|
||||||
db,
|
db,
|
||||||
|
@ -54,7 +55,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
||||||
var shouldFinishCurrentJob: Bool = false
|
var shouldFinishCurrentJob: Bool = false
|
||||||
let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + minRunFrequency)
|
let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + minRunFrequency)
|
||||||
|
|
||||||
let updatedJob: Job? = Storage.shared.write { db in
|
let updatedJob: Job? = dependencies.storage.write { db in
|
||||||
// If another 'sendReadReceipts' job was scheduled then update that one
|
// If another 'sendReadReceipts' job was scheduled then update that one
|
||||||
// to run at 'nextRunTimestamp' and make the current job stop
|
// to run at 'nextRunTimestamp' and make the current job stop
|
||||||
if
|
if
|
||||||
|
@ -79,9 +80,9 @@ public enum SendReadReceiptsJob: JobExecutor {
|
||||||
.saved(db)
|
.saved(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
success(updatedJob ?? job, shouldFinishCurrentJob)
|
success(updatedJob ?? job, shouldFinishCurrentJob, dependencies)
|
||||||
}
|
}
|
||||||
.catch(on: queue) { error in failure(job, error, false) }
|
.catch(on: queue) { error in failure(job, error, false, dependencies) }
|
||||||
.retainUntilComplete()
|
.retainUntilComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,14 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
// Don't run when inactive or not in main app
|
// Don't run when inactive or not in main app
|
||||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||||
deferred(job) // Don't need to do anything if it's not the main app
|
deferred(job, dependencies) // Don't need to do anything if it's not the main app
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,18 +32,18 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
||||||
// Reset the `nextRunTimestamp` value just in case the last run failed so we don't get stuck
|
// Reset the `nextRunTimestamp` value just in case the last run failed so we don't get stuck
|
||||||
// in a loop endlessly deferring the job
|
// in a loop endlessly deferring the job
|
||||||
if let jobId: Int64 = job.id {
|
if let jobId: Int64 = job.id {
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
try Job
|
try Job
|
||||||
.filter(id: jobId)
|
.filter(id: jobId)
|
||||||
.updateAll(db, Job.Columns.nextRunTimestamp.set(to: 0))
|
.updateAll(db, Job.Columns.nextRunTimestamp.set(to: 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deferred(job)
|
deferred(job, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The user defaults flag is updated in ProfileManager
|
// Note: The user defaults flag is updated in ProfileManager
|
||||||
let profile: Profile = Profile.fetchOrCreateCurrentUser()
|
let profile: Profile = Profile.fetchOrCreateCurrentUser(dependencies: dependencies)
|
||||||
let profileFilePath: String? = profile.profilePictureFileName
|
let profileFilePath: String? = profile.profilePictureFileName
|
||||||
.map { ProfileManager.profileAvatarFilepath(filename: $0) }
|
.map { ProfileManager.profileAvatarFilepath(filename: $0) }
|
||||||
|
|
||||||
|
@ -58,10 +59,10 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
||||||
// issue as it will write to the database and this closure is already called within
|
// issue as it will write to the database and this closure is already called within
|
||||||
// another database write
|
// another database write
|
||||||
queue.async {
|
queue.async {
|
||||||
success(job, false)
|
success(job, false, dependencies)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
failure: { error in failure(job, error, false) }
|
failure: { error in failure(job, error, false, dependencies) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,12 +270,12 @@ public final class MessageSender {
|
||||||
NotifyPushServerJob.run(
|
NotifyPushServerJob.run(
|
||||||
job,
|
job,
|
||||||
queue: DispatchQueue.global(qos: .default),
|
queue: DispatchQueue.global(qos: .default),
|
||||||
success: { _, _ in seal.fulfill(()) },
|
success: { _, _, _ in seal.fulfill(()) },
|
||||||
failure: { _, _, _ in
|
failure: { _, _, _, _ in
|
||||||
// Always fulfill because the notify PN server job isn't critical.
|
// Always fulfill because the notify PN server job isn't critical.
|
||||||
seal.fulfill(())
|
seal.fulfill(())
|
||||||
},
|
},
|
||||||
deferred: { _ in
|
deferred: { _, _ in
|
||||||
// Always fulfill because the notify PN server job isn't critical.
|
// Always fulfill because the notify PN server job isn't critical.
|
||||||
seal.fulfill(())
|
seal.fulfill(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,9 +284,9 @@ public final class ClosedGroupPoller {
|
||||||
MessageReceiveJob.run(
|
MessageReceiveJob.run(
|
||||||
job,
|
job,
|
||||||
queue: queue,
|
queue: queue,
|
||||||
success: { _, _ in seal.fulfill(()) },
|
success: { _, _, _ in seal.fulfill(()) },
|
||||||
failure: { _, _, _ in seal.fulfill(()) },
|
failure: { _, _, _, _ in seal.fulfill(()) },
|
||||||
deferred: { _ in seal.fulfill(()) }
|
deferred: { _, _ in seal.fulfill(()) }
|
||||||
)
|
)
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
|
|
|
@ -13,14 +13,15 @@ public enum GetSnodePoolJob: JobExecutor {
|
||||||
public static func run(
|
public static func run(
|
||||||
_ job: Job,
|
_ job: Job,
|
||||||
queue: DispatchQueue,
|
queue: DispatchQueue,
|
||||||
success: @escaping (Job, Bool) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job) -> ()
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
// If the user doesn't exist then don't do anything (when the user registers we run this
|
// If the user doesn't exist then don't do anything (when the user registers we run this
|
||||||
// job directly)
|
// job directly)
|
||||||
guard Identity.userExists() else {
|
guard Identity.userExists() else {
|
||||||
deferred(job)
|
deferred(job, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +31,13 @@ public enum GetSnodePoolJob: JobExecutor {
|
||||||
// wait if we already have a potentially valid snode pool
|
// wait if we already have a potentially valid snode pool
|
||||||
guard !SnodeAPI.hasCachedSnodesInclusingExpired() else {
|
guard !SnodeAPI.hasCachedSnodesInclusingExpired() else {
|
||||||
SnodeAPI.getSnodePool().retainUntilComplete()
|
SnodeAPI.getSnodePool().retainUntilComplete()
|
||||||
success(job, false)
|
success(job, false, dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SnodeAPI.getSnodePool()
|
SnodeAPI.getSnodePool()
|
||||||
.done(on: queue) { _ in success(job, false) }
|
.done(on: queue) { _ in success(job, false, dependencies) }
|
||||||
.catch(on: queue) { error in failure(job, error, false) }
|
.catch(on: queue) { error in failure(job, error, false, dependencies) }
|
||||||
.retainUntilComplete()
|
.retainUntilComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,9 +45,9 @@ public enum GetSnodePoolJob: JobExecutor {
|
||||||
GetSnodePoolJob.run(
|
GetSnodePoolJob.run(
|
||||||
Job(variant: .getSnodePool),
|
Job(variant: .getSnodePool),
|
||||||
queue: DispatchQueue.global(qos: .background),
|
queue: DispatchQueue.global(qos: .background),
|
||||||
success: { _, _ in },
|
success: { _, _, _ in },
|
||||||
failure: { _, _, _ in },
|
failure: { _, _, _, _ in },
|
||||||
deferred: { _ in }
|
deferred: { _, _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,94 @@ public extension Database {
|
||||||
|
|
||||||
sqlite3_interrupt(sqliteConnection)
|
sqlite3_interrupt(sqliteConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is a custom implementation of the `afterNextTransaction` method which executes the closures within their own
|
||||||
|
/// transactions to allow for nesting of 'afterNextTransaction' actions
|
||||||
|
///
|
||||||
|
/// **Note:** GRDB doesn't notify read-only transactions to transaction observers
|
||||||
|
func afterNextTransactionNested(
|
||||||
|
onCommit: @escaping (Database) -> Void,
|
||||||
|
onRollback: @escaping (Database) -> Void = { _ in }
|
||||||
|
) {
|
||||||
|
afterNextTransactionNestedOnce(
|
||||||
|
dedupeId: UUID().uuidString,
|
||||||
|
onCommit: onCommit,
|
||||||
|
onRollback: onRollback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func afterNextTransactionNestedOnce(
|
||||||
|
dedupeId: String,
|
||||||
|
onCommit: @escaping (Database) -> Void,
|
||||||
|
onRollback: @escaping (Database) -> Void = { _ in }
|
||||||
|
) {
|
||||||
|
// Only allow a single observer per `dedupeId` per transaction, this allows us to
|
||||||
|
// schedule an action to run at most once per transaction (eg. auto-scheduling a ConfigSyncJob
|
||||||
|
// when receiving messages)
|
||||||
|
guard !TransactionHandler.registeredHandlers.wrappedValue.contains(dedupeId) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
add(
|
||||||
|
transactionObserver: TransactionHandler(
|
||||||
|
identifier: dedupeId,
|
||||||
|
onCommit: onCommit,
|
||||||
|
onRollback: onRollback
|
||||||
|
),
|
||||||
|
extent: .nextTransaction
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate class TransactionHandler: TransactionObserver {
|
||||||
|
static var registeredHandlers: Atomic<Set<String>> = Atomic([])
|
||||||
|
|
||||||
|
let identifier: String
|
||||||
|
let onCommit: (Database) -> Void
|
||||||
|
let onRollback: (Database) -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
identifier: String,
|
||||||
|
onCommit: @escaping (Database) -> Void,
|
||||||
|
onRollback: @escaping (Database) -> Void
|
||||||
|
) {
|
||||||
|
self.identifier = identifier
|
||||||
|
self.onCommit = onCommit
|
||||||
|
self.onRollback = onRollback
|
||||||
|
|
||||||
|
TransactionHandler.registeredHandlers.mutate { $0.insert(identifier) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore changes
|
||||||
|
func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { false }
|
||||||
|
func databaseDidChange(with event: DatabaseEvent) { }
|
||||||
|
|
||||||
|
func databaseDidCommit(_ db: Database) {
|
||||||
|
TransactionHandler.registeredHandlers.mutate { $0.remove(identifier) }
|
||||||
|
|
||||||
|
do {
|
||||||
|
try db.inTransaction {
|
||||||
|
onCommit(db)
|
||||||
|
return .commit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
SNLog("[Database] afterNextTransactionNested onCommit failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func databaseDidRollback(_ db: Database) {
|
||||||
|
TransactionHandler.registeredHandlers.mutate { $0.remove(identifier) }
|
||||||
|
|
||||||
|
do {
|
||||||
|
try db.inTransaction {
|
||||||
|
onRollback(db)
|
||||||
|
return .commit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
SNLog("[Database] afterNextTransactionNested onRollback failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
import Quick
|
||||||
|
import Nimble
|
||||||
|
|
||||||
|
@testable import SessionUtilitiesKit
|
||||||
|
|
||||||
|
class JobRunnerSpec: QuickSpec {
|
||||||
|
public enum TestSuccessfulJob: JobExecutor {
|
||||||
|
static let maxFailureCount: Int = 0
|
||||||
|
static let requiresThreadId: Bool = false
|
||||||
|
static let requiresInteractionId: Bool = false
|
||||||
|
|
||||||
|
static func run(
|
||||||
|
_ job: Job,
|
||||||
|
queue: DispatchQueue,
|
||||||
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
|
dependencies: Dependencies
|
||||||
|
) {
|
||||||
|
success(job, true, dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Spec
|
||||||
|
|
||||||
|
override func spec() {
|
||||||
|
var jobRunner: JobRunner!
|
||||||
|
var mockStorage: Storage!
|
||||||
|
var dependencies: Dependencies!
|
||||||
|
|
||||||
|
// MARK: - JobRunner
|
||||||
|
|
||||||
|
describe("a JobRunner") {
|
||||||
|
beforeEach {
|
||||||
|
mockStorage = Storage(
|
||||||
|
customWriter: try! DatabaseQueue(),
|
||||||
|
customMigrations: [
|
||||||
|
SNUtilitiesKit.migrations()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
dependencies = Dependencies(
|
||||||
|
storage: mockStorage,
|
||||||
|
date: Date(timeIntervalSince1970: 1234567890)
|
||||||
|
)
|
||||||
|
|
||||||
|
jobRunner = JobRunner()
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach {
|
||||||
|
jobRunner = nil
|
||||||
|
mockStorage = nil
|
||||||
|
dependencies = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
context("when configuring") {
|
||||||
|
it("adds an executor correctly") {
|
||||||
|
// TODO: Test this
|
||||||
|
jobRunner.add(executor: TestSuccessfulJob.self, for: .messageSend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue