mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
aabf656d89
Updated the config message generation for GRDB Migrated more preferences into GRDB Added paging to the MediaTileViewController and sorted out the various animations/transitions Fixed an issue where the 'recipientState' for the 'baseQuery' on the ConversationCell.ViewModel wasn't grouping correctly Fixed an issue where the MediaZoomAnimationController could fail if the contextual info wasn't available Fixed an issue where the MediaZoomAnimationController bounce looked odd when returning to the detail screen from the tile screen Fixed an issue where the MediaZoomAnimationController didn't work for videos Fixed a bug where the YDB to GRDB migration wasn't properly handling video files Fixed a number of minor UI bugs with the GalleryRailView Deleted a bunch of legacy code
329 lines
12 KiB
Swift
329 lines
12 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import Foundation
|
|
import GRDB
|
|
|
|
public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
|
|
public static var databaseTableName: String { "job" }
|
|
internal static let dependencyForeignKey = ForeignKey([Columns.id], to: [JobDependencies.Columns.dependantId])
|
|
internal static let dependantJobForeignKey = ForeignKey([Columns.id], to: [JobDependencies.Columns.jobId])
|
|
internal static let dependencies = hasMany(Job.self, using: dependencyForeignKey)
|
|
internal static let dependantJobs = hasMany(Job.self, using: dependencyForeignKey)
|
|
|
|
public typealias Columns = CodingKeys
|
|
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
|
case id
|
|
case failureCount
|
|
case variant
|
|
case behaviour
|
|
case nextRunTimestamp
|
|
case threadId
|
|
case interactionId
|
|
case details
|
|
}
|
|
|
|
public enum Variant: Int, Codable, DatabaseValueConvertible {
|
|
/// This is a recurring job that handles the removal of disappearing messages and is triggered
|
|
/// 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 removes expired and orphaned data, it runs on launch and can also be triggered
|
|
/// as 'runOnce' to avoid waiting until the next launch to clear data
|
|
case garbageCollection
|
|
|
|
/// 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
|
|
/// latest device push tokens
|
|
case syncPushTokens = 2000
|
|
|
|
/// This is a job that runs once whenever a message is sent to notify the push notification server
|
|
/// about the message
|
|
case notifyPushServer
|
|
|
|
/// This is a job that runs once at most every 3 seconds per thread whenever a message is marked as read
|
|
/// (if read receipts are enabled) to notify other members in a conversation that their message was read
|
|
case sendReadReceipts
|
|
|
|
/// This is a job that runs once whenever a message is received to attempt to decode and properly
|
|
/// process the message
|
|
case messageReceive = 3000
|
|
|
|
/// This is a job that runs once whenever a message is sent to attempt to encode and properly
|
|
/// send the message
|
|
case messageSend
|
|
|
|
/// This is a job that runs once whenever an attachment is uploaded to attempt to encode and properly
|
|
/// upload the attachment
|
|
case attachmentUpload
|
|
|
|
/// This is a job that runs once whenever an attachment is downloaded to attempt to decode and properly
|
|
/// download the attachment
|
|
case attachmentDownload
|
|
}
|
|
|
|
public enum Behaviour: Int, Codable, DatabaseValueConvertible {
|
|
/// This job will run once and then be removed from the jobs table
|
|
case runOnce
|
|
|
|
/// This job will run once the next time the app launches and then be removed from the jobs table
|
|
case runOnceNextLaunch
|
|
|
|
/// This job will run and then will be updated with a new `nextRunTimestamp` (at least 1 second in
|
|
/// the future) in order to be run again
|
|
case recurring
|
|
|
|
/// 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 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
|
|
/// the database yet this value will be `nil`
|
|
public var id: Int64? = nil
|
|
|
|
/// A counter for the number of times this job has failed
|
|
public let failureCount: UInt
|
|
|
|
/// The type of job
|
|
public let variant: Variant
|
|
|
|
/// The type of job
|
|
public let behaviour: Behaviour
|
|
|
|
/// Seconds since epoch to indicate the next datetime that this job should run
|
|
public let nextRunTimestamp: TimeInterval
|
|
|
|
/// The id of the thread this job is associated with, if the associated thread is deleted this job will
|
|
/// also be deleted
|
|
///
|
|
/// **Note:** This will only be populated for Jobs associated to threads
|
|
public let threadId: String?
|
|
|
|
/// The id of the interaction this job is associated with, if the associated interaction is deleted this
|
|
/// job will also be deleted
|
|
///
|
|
/// **Note:** This will only be populated for Jobs associated to interactions
|
|
public let interactionId: Int64?
|
|
|
|
/// JSON encoded data required for the job
|
|
public let details: Data?
|
|
|
|
/// The other jobs which this job is dependant on
|
|
///
|
|
/// **Note:** When completing a job the dependencies **MUST** be cleared before the job is
|
|
/// deleted or it will automatically delete any dependant jobs
|
|
public var dependencies: QueryInterfaceRequest<Job> {
|
|
request(for: Job.dependencies)
|
|
}
|
|
|
|
/// The other jobs which depend on this job
|
|
///
|
|
/// **Note:** When completing a job the dependencies **MUST** be cleared before the job is
|
|
/// deleted or it will automatically delete any dependant jobs
|
|
public var dependantJobs: QueryInterfaceRequest<Job> {
|
|
request(for: Job.dependantJobs)
|
|
}
|
|
|
|
// MARK: - Initialization
|
|
|
|
fileprivate init(
|
|
id: Int64?,
|
|
failureCount: UInt,
|
|
variant: Variant,
|
|
behaviour: Behaviour,
|
|
nextRunTimestamp: TimeInterval,
|
|
threadId: String?,
|
|
interactionId: Int64?,
|
|
details: Data?
|
|
) {
|
|
self.id = id
|
|
self.failureCount = failureCount
|
|
self.variant = variant
|
|
self.behaviour = behaviour
|
|
self.nextRunTimestamp = nextRunTimestamp
|
|
self.threadId = threadId
|
|
self.interactionId = interactionId
|
|
self.details = details
|
|
}
|
|
|
|
public init(
|
|
failureCount: UInt = 0,
|
|
variant: Variant,
|
|
behaviour: Behaviour = .runOnce,
|
|
nextRunTimestamp: TimeInterval = 0,
|
|
threadId: String? = nil,
|
|
interactionId: Int64? = nil
|
|
) {
|
|
self.failureCount = failureCount
|
|
self.variant = variant
|
|
self.behaviour = behaviour
|
|
self.nextRunTimestamp = nextRunTimestamp
|
|
self.threadId = threadId
|
|
self.interactionId = interactionId
|
|
self.details = nil
|
|
}
|
|
|
|
public init?<T: Encodable>(
|
|
failureCount: UInt = 0,
|
|
variant: Variant,
|
|
behaviour: Behaviour = .runOnce,
|
|
nextRunTimestamp: TimeInterval = 0,
|
|
threadId: String? = nil,
|
|
interactionId: Int64? = nil,
|
|
details: T?
|
|
) {
|
|
precondition(T.self != Job.self, "[Job] Fatal error trying to create a Job with a Job as it's details")
|
|
|
|
guard
|
|
let details: T = details,
|
|
let detailsData: Data = try? JSONEncoder().encode(details)
|
|
else { return nil }
|
|
|
|
self.failureCount = failureCount
|
|
self.variant = variant
|
|
self.behaviour = behaviour
|
|
self.nextRunTimestamp = nextRunTimestamp
|
|
self.threadId = threadId
|
|
self.interactionId = interactionId
|
|
self.details = detailsData
|
|
}
|
|
|
|
// MARK: - Custom Database Interaction
|
|
|
|
public mutating func didInsert(with rowID: Int64, for column: String?) {
|
|
self.id = rowID
|
|
}
|
|
|
|
public func delete(_ db: Database) throws -> Bool {
|
|
// Delete any dependencies
|
|
try dependantJobs
|
|
.deleteAll(db)
|
|
|
|
return try performDelete(db)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
var isBlocking: Bool {
|
|
switch self.behaviour {
|
|
case .recurringOnLaunchBlocking,
|
|
.recurringOnLaunchBlockingOncePerSession,
|
|
.recurringOnActiveBlocking:
|
|
return true
|
|
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
func with(
|
|
failureCount: UInt = 0,
|
|
nextRunTimestamp: TimeInterval
|
|
) -> Job {
|
|
return Job(
|
|
id: id,
|
|
failureCount: failureCount,
|
|
variant: variant,
|
|
behaviour: behaviour,
|
|
nextRunTimestamp: nextRunTimestamp,
|
|
threadId: threadId,
|
|
interactionId: interactionId,
|
|
details: details
|
|
)
|
|
}
|
|
|
|
func with<T: Encodable>(details: T) -> Job? {
|
|
guard let detailsData: Data = try? JSONEncoder().encode(details) else { return nil }
|
|
|
|
return Job(
|
|
id: id,
|
|
failureCount: failureCount,
|
|
variant: variant,
|
|
behaviour: behaviour,
|
|
nextRunTimestamp: nextRunTimestamp,
|
|
threadId: threadId,
|
|
interactionId: interactionId,
|
|
details: detailsData
|
|
)
|
|
}
|
|
}
|