264 lines
9.0 KiB
Swift
264 lines
9.0 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||
//
|
||
// stringlint:disable
|
||
|
||
import Foundation
|
||
import GRDB
|
||
import SessionUtilitiesKit
|
||
|
||
public struct OpenGroup: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||
public static var databaseTableName: String { "openGroup" }
|
||
internal static let threadForeignKey = ForeignKey([Columns.threadId], to: [SessionThread.Columns.id])
|
||
private static let thread = belongsTo(SessionThread.self, using: threadForeignKey)
|
||
public static let members = hasMany(GroupMember.self, using: GroupMember.openGroupForeignKey)
|
||
|
||
public typealias Columns = CodingKeys
|
||
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||
case threadId
|
||
case server
|
||
case roomToken
|
||
case publicKey
|
||
case name
|
||
case isActive
|
||
case roomDescription = "description"
|
||
case imageId
|
||
case imageData
|
||
case userCount
|
||
case infoUpdates
|
||
case sequenceNumber
|
||
case inboxLatestMessageId
|
||
case outboxLatestMessageId
|
||
case pollFailureCount
|
||
case permissions
|
||
}
|
||
|
||
public struct Permissions: OptionSet, Codable, DatabaseValueConvertible, Hashable {
|
||
public let rawValue: UInt16
|
||
|
||
public init(rawValue: UInt16) {
|
||
self.rawValue = rawValue
|
||
}
|
||
|
||
public init(roomInfo: OpenGroupAPI.RoomPollInfo) {
|
||
var permissions: Permissions = []
|
||
|
||
if roomInfo.read { permissions.insert(.read) }
|
||
if roomInfo.write { permissions.insert(.write) }
|
||
if roomInfo.upload { permissions.insert(.upload) }
|
||
|
||
self.init(rawValue: permissions.rawValue)
|
||
}
|
||
|
||
public func toString() -> String {
|
||
return ""
|
||
.appending(self.contains(.read) ? "r" : "-")
|
||
.appending(self.contains(.write) ? "w" : "-")
|
||
.appending(self.contains(.upload) ? "u" : "-")
|
||
}
|
||
|
||
static let read: Permissions = Permissions(rawValue: 1 << 0)
|
||
static let write: Permissions = Permissions(rawValue: 1 << 1)
|
||
static let upload: Permissions = Permissions(rawValue: 1 << 2)
|
||
|
||
static let all: Permissions = [ .read, .write, .upload ]
|
||
}
|
||
|
||
/// The Community public key takes up 32 bytes
|
||
static let pubkeyByteLength: Int = 32
|
||
|
||
public var id: String { threadId } // Identifiable
|
||
|
||
/// The id for the thread this open group belongs to
|
||
///
|
||
/// **Note:** This value will always be `\(server).\(room)` (This needs it’s own column to
|
||
/// allow for db joining to the Thread table)
|
||
public let threadId: String
|
||
|
||
/// The server for the group
|
||
public let server: String
|
||
|
||
/// The specific room on the server for the group
|
||
///
|
||
/// **Note:** In order to support the default open group query we need an OpenGroup entry in
|
||
/// the database, for this entry the `roomToken` value will be an empty string so we can ignore
|
||
/// it when polling
|
||
public let roomToken: String
|
||
|
||
/// The public key for the group
|
||
public let publicKey: String
|
||
|
||
/// Flag indicating whether this is an OpenGroup the user has actively joined (we store inactive
|
||
/// open groups so we can display them in the UI but they won't be polled for)
|
||
public let isActive: Bool
|
||
|
||
/// The name for the group
|
||
public let name: String
|
||
|
||
/// The description for the room
|
||
public let roomDescription: String?
|
||
|
||
/// The ID with which the image can be retrieved from the server
|
||
public let imageId: String?
|
||
|
||
/// The image for the group
|
||
public let imageData: Data?
|
||
|
||
/// The number of users in the group
|
||
public let userCount: Int64
|
||
|
||
/// Monotonic room information counter that increases each time the room's metadata changes
|
||
public let infoUpdates: Int64
|
||
|
||
/// Sequence number for the most recently received message from the open group
|
||
public let sequenceNumber: Int64
|
||
|
||
/// The id of the most recently received inbox message
|
||
///
|
||
/// **Note:** This value is unique per server rather than per room (ie. all rooms in the same server will be
|
||
/// updated whenever this value changes)
|
||
public let inboxLatestMessageId: Int64
|
||
|
||
/// The id of the most recently received outbox message
|
||
///
|
||
/// **Note:** This value is unique per server rather than per room (ie. all rooms in the same server will be
|
||
/// updated whenever this value changes)
|
||
public let outboxLatestMessageId: Int64
|
||
|
||
/// The number of times this room has failed to poll since the last successful poll
|
||
public let pollFailureCount: Int64
|
||
|
||
/// The permissions this room has for current user
|
||
public let permissions: Permissions?
|
||
|
||
// MARK: - Relationships
|
||
|
||
public var thread: QueryInterfaceRequest<SessionThread> {
|
||
request(for: OpenGroup.thread)
|
||
}
|
||
|
||
public var moderatorIds: QueryInterfaceRequest<GroupMember> {
|
||
request(for: OpenGroup.members)
|
||
.filter(GroupMember.Columns.role == GroupMember.Role.moderator)
|
||
}
|
||
|
||
public var adminIds: QueryInterfaceRequest<GroupMember> {
|
||
request(for: OpenGroup.members)
|
||
.filter(GroupMember.Columns.role == GroupMember.Role.admin)
|
||
}
|
||
|
||
// MARK: - Initialization
|
||
|
||
public init(
|
||
server: String,
|
||
roomToken: String,
|
||
publicKey: String,
|
||
isActive: Bool,
|
||
name: String,
|
||
roomDescription: String? = nil,
|
||
imageId: String? = nil,
|
||
imageData: Data? = nil,
|
||
userCount: Int64,
|
||
infoUpdates: Int64,
|
||
sequenceNumber: Int64 = 0,
|
||
inboxLatestMessageId: Int64 = 0,
|
||
outboxLatestMessageId: Int64 = 0,
|
||
pollFailureCount: Int64 = 0,
|
||
permissions: Permissions? = nil
|
||
) {
|
||
self.threadId = OpenGroup.idFor(roomToken: roomToken, server: server)
|
||
self.server = server.lowercased()
|
||
self.roomToken = roomToken
|
||
self.publicKey = publicKey
|
||
self.isActive = isActive
|
||
self.name = name
|
||
self.roomDescription = roomDescription
|
||
self.imageId = imageId
|
||
self.imageData = imageData
|
||
self.userCount = userCount
|
||
self.infoUpdates = infoUpdates
|
||
self.sequenceNumber = sequenceNumber
|
||
self.inboxLatestMessageId = inboxLatestMessageId
|
||
self.outboxLatestMessageId = outboxLatestMessageId
|
||
self.pollFailureCount = pollFailureCount
|
||
self.permissions = permissions
|
||
}
|
||
}
|
||
|
||
// MARK: - GRDB Interactions
|
||
|
||
public extension OpenGroup {
|
||
static func fetchOrCreate(
|
||
_ db: Database,
|
||
server: String,
|
||
roomToken: String,
|
||
publicKey: String
|
||
) -> OpenGroup {
|
||
guard let existingGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else {
|
||
return OpenGroup(
|
||
server: server,
|
||
roomToken: roomToken,
|
||
publicKey: publicKey,
|
||
isActive: false,
|
||
name: roomToken, // Default the name to the `roomToken` until we get retrieve the actual name
|
||
roomDescription: nil,
|
||
imageId: nil,
|
||
imageData: nil,
|
||
userCount: 0,
|
||
infoUpdates: 0,
|
||
sequenceNumber: 0,
|
||
inboxLatestMessageId: 0,
|
||
outboxLatestMessageId: 0,
|
||
pollFailureCount: 0,
|
||
permissions: nil
|
||
)
|
||
}
|
||
|
||
return existingGroup
|
||
}
|
||
}
|
||
|
||
// MARK: - Search Queries
|
||
|
||
public extension OpenGroup {
|
||
struct FullTextSearch: Decodable, ColumnExpressible {
|
||
public typealias Columns = CodingKeys
|
||
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
|
||
case name
|
||
}
|
||
|
||
let name: String
|
||
}
|
||
}
|
||
|
||
// MARK: - Convenience
|
||
|
||
public extension OpenGroup {
|
||
static func idFor(roomToken: String, server: String) -> String {
|
||
// Always force the server to lowercase
|
||
return "\(server.lowercased()).\(roomToken)"
|
||
}
|
||
}
|
||
|
||
extension OpenGroup: CustomStringConvertible, CustomDebugStringConvertible {
|
||
public var description: String { "\(name) (Server: \(server), Room: \(roomToken))" }
|
||
public var debugDescription: String {
|
||
[
|
||
"OpenGroup(server: \"\(server)\"",
|
||
"roomToken: \"\(roomToken)\"",
|
||
"id: \"\(id)\"",
|
||
"publicKey: \"\(publicKey)\"",
|
||
"isActive: \(isActive)",
|
||
"name: \"\(name)\"",
|
||
"roomDescription: \(roomDescription.map { "\"\($0)\"" } ?? "null")",
|
||
"imageId: \(imageId ?? "null")",
|
||
"userCount: \(userCount)",
|
||
"infoUpdates: \(infoUpdates)",
|
||
"sequenceNumber: \(sequenceNumber)",
|
||
"inboxLatestMessageId: \(inboxLatestMessageId)",
|
||
"outboxLatestMessageId: \(outboxLatestMessageId)",
|
||
"pollFailureCount: \(pollFailureCount)",
|
||
"permissions: \(permissions?.toString() ?? "---"))"
|
||
].joined(separator: ", ")
|
||
}
|
||
}
|