session-ios/SessionMessagingKit/Database/Models/SessionThread.swift
Morgan Pretty ed9f4ea6c6 Fixed a few closed group and job issues
Fixed a few job migration issues
Fixed an issue with the closed group key pair management (wasn't storing keys correctly)
Refactored the OWSSound (now Preferences.Sound)
Added the logic for the AttachmentDownloadJob and enabled jobs to be cascade deleted via interactions
Optimised the HomeViewModel database observation query (fetch specific columns so changes outside those don't trigger updates)
Updated to the latest GRDB (ran into a deadlock which should be fixed in a newer version)
2022-04-22 18:47:11 +10:00

184 lines
6.3 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
public struct SessionThread: Codable, Identifiable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "thread" }
private static let contact = hasOne(Contact.self, using: Contact.threadForeignKey)
private static let closedGroup = hasOne(ClosedGroup.self, using: ClosedGroup.threadForeignKey)
private static let openGroup = hasOne(OpenGroup.self, using: OpenGroup.threadForeignKey)
private static let disappearingMessagesConfiguration = hasOne(
DisappearingMessagesConfiguration.self,
using: DisappearingMessagesConfiguration.threadForeignKey
)
public static let interactions = hasMany(Interaction.self, using: Interaction.threadForeignKey)
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case id
case variant
case creationDateTimestamp
case shouldBeVisible
case isPinned
case messageDraft
case notificationMode
case notificationSound
case mutedUntilTimestamp
}
public enum Variant: Int, Codable, DatabaseValueConvertible {
case contact
case closedGroup
case openGroup
}
public enum NotificationMode: Int, Codable, DatabaseValueConvertible {
case none
case all
case mentionsOnly // Only applicable to group threads
}
/// Unique identifier for a thread (formerly known as uniqueId)
///
/// This value will depend on the variant:
/// **contact:** The contact id
/// **closedGroup:** The closed group public key
/// **openGroup:** The `\(server.lowercased()).\(room)` value
public let id: String
/// Enum indicating what type of thread this is
public let variant: Variant
/// A timestamp indicating when this thread was created
public let creationDateTimestamp: TimeInterval
/// A flag indicating whether the thread should be visible
public let shouldBeVisible: Bool
/// A flag indicating whether the thread is pinned
public let isPinned: Bool
/// The value the user started entering into the input field before they left the conversation screen
public let messageDraft: String?
/// The notification mode this thread is set to
public let notificationMode: NotificationMode
/// The sound which should be used when receiving a notification for this thread
///
/// **Note:** If unset this will use the `Preferences.Sound.defaultNotificationSound`
public let notificationSound: Preferences.Sound?
/// Timestamp (seconds since epoch) for when this thread should stop being muted
public let mutedUntilTimestamp: TimeInterval?
// MARK: - Relationships
public var contact: QueryInterfaceRequest<Contact> {
request(for: SessionThread.contact)
}
public var closedGroup: QueryInterfaceRequest<ClosedGroup> {
request(for: SessionThread.closedGroup)
}
public var openGroup: QueryInterfaceRequest<OpenGroup> {
request(for: SessionThread.openGroup)
}
public var disappearingMessagesConfiguration: QueryInterfaceRequest<DisappearingMessagesConfiguration> {
request(for: SessionThread.disappearingMessagesConfiguration)
}
public var interactions: QueryInterfaceRequest<Interaction> {
request(for: SessionThread.interactions)
}
// MARK: - Initialization
public init(
id: String,
variant: Variant,
creationDateTimestamp: TimeInterval = Date().timeIntervalSince1970,
shouldBeVisible: Bool = false,
isPinned: Bool = false,
messageDraft: String? = nil,
notificationMode: NotificationMode = .all,
notificationSound: Preferences.Sound? = nil,
mutedUntilTimestamp: TimeInterval? = nil
) {
self.id = id
self.variant = variant
self.creationDateTimestamp = creationDateTimestamp
self.shouldBeVisible = shouldBeVisible
self.isPinned = isPinned
self.messageDraft = messageDraft
self.notificationMode = notificationMode
self.notificationSound = notificationSound
self.mutedUntilTimestamp = mutedUntilTimestamp
}
}
// MARK: - GRDB Interactions
public extension SessionThread {
/// The `variant` will be ignored if an existing thread is found
static func fetchOrCreate(_ db: Database, id: ID, variant: Variant) -> SessionThread {
return ((try? fetchOne(db, id: id)) ?? SessionThread(id: id, variant: variant))
}
static func messageRequestThreads(_ db: Database) -> QueryInterfaceRequest<SessionThread> {
return SessionThread
.filter(Columns.shouldBeVisible == true)
.filter(Columns.variant == Variant.contact)
.filter(Columns.id != getUserHexEncodedPublicKey(db))
.joining(
optional: contact
.filter(Contact.Columns.isApproved == false)
)
}
func isMessageRequest(_ db: Database) -> Bool {
return (
shouldBeVisible &&
variant == .contact &&
id != getUserHexEncodedPublicKey(db) && // Note to self
(try? Contact.fetchOne(db, id: id))?.isApproved != true
)
}
}
// MARK: - Convenience
public extension SessionThread {
func isNoteToSelf(_ db: Database? = nil) -> Bool {
return (
variant == .contact &&
id == getUserHexEncodedPublicKey(db)
)
}
func name(_ db: Database) -> String {
switch variant {
case .contact: return Profile.displayName(db, id: id)
case .closedGroup:
guard let name: String = try? String.fetchOne(db, closedGroup.select(ClosedGroup.Columns.name)), !name.isEmpty else {
return "Group"
}
return name
case .openGroup:
guard let name: String = try? String.fetchOne(db, openGroup.select(OpenGroup.Columns.name)), !name.isEmpty else {
return "Group"
}
return name
}
}
}