session-ios/SessionMessagingKit/Jobs/Types/DisappearingMessagesJob.swift
Morgan Pretty 3baeb981d9 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
2022-04-27 10:48:54 +10:00

105 lines
4.6 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public enum DisappearingMessagesJob: 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) -> ()
) {
// The 'backgroundTask' gets captured and cleared within the 'completion' block
let timestampNowMs: TimeInterval = (Date().timeIntervalSince1970 * 1000)
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: #function)
let updatedJob: Job? = GRDBStorage.shared.write { db in
_ = try Interaction
.filter(Interaction.Columns.expiresStartedAtMs != nil)
.filter(sql: "(\(Interaction.Columns.expiresStartedAtMs) + (\(Interaction.Columns.expiresInSeconds) * 1000) <= \(timestampNowMs)")
.deleteAll(db)
// 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)
// The 'if' is only there to prevent the "variable never read" warning from showing
if backgroundTask != nil { backgroundTask = nil }
}
}
// MARK: - Convenience
public extension DisappearingMessagesJob {
@discardableResult static func updateNextRunIfNeeded(_ db: Database) -> Job? {
// 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
.fetchOne(
db,
Interaction
.select(sql: "(\(Interaction.Columns.expiresStartedAtMs) + (\(Interaction.Columns.expiresInSeconds) * 1000)")
.order(sql: "(\(Interaction.Columns.expiresStartedAtMs) + (\(Interaction.Columns.expiresInSeconds) * 1000) asc")
)
guard let nextExpirationTimestampMs: Double = nextExpirationTimestampMs else { return nil }
return try? Job
.filter(Job.Columns.variant == Job.Variant.disappearingMessages)
.fetchOne(db)?
.with(nextRunTimestamp: ((nextExpirationTimestampMs / 1000) + 1))
.saved(db)
}
@discardableResult static func updateNextRunIfNeeded(_ db: Database, interactionIds: [Int64], startedAtMs: Double) -> Job? {
// Update the expiring messages expiresStartedAtMs value
let changeCount: Int? = try? Interaction
.filter(interactionIds.contains(Interaction.Columns.id))
.filter(Interaction.Columns.expiresInSeconds != nil && Interaction.Columns.expiresStartedAtMs == nil)
.updateAll(db, Interaction.Columns.expiresStartedAtMs.set(to: startedAtMs))
// If there were no changes then none of the provided `interactionIds` are expiring messages
guard (changeCount ?? 0) > 0 else { return nil }
return updateNextRunIfNeeded(db)
}
@discardableResult static func updateNextRunIfNeeded(_ db: Database, interaction: Interaction, startedAtMs: Double) -> Job? {
guard interaction.isExpiringMessage else { return nil }
// Don't clobber if multiple actions simultaneously triggered expiration
guard interaction.expiresStartedAtMs == nil || (interaction.expiresStartedAtMs ?? 0) > startedAtMs else {
return nil
}
do {
guard let interactionId: Int64 = try? (interaction.id ?? interaction.inserted(db).id) else {
throw GRDBStorageError.objectNotFound
}
return updateNextRunIfNeeded(db, interactionIds: [interactionId], startedAtMs: startedAtMs)
}
catch {
SNLog("Failed to update the expiring messages timer on an interaction")
return nil
}
}
}