Path: blob/main/SignalServiceKit/Groups/TSGroupModelBuilder.swift
1 views
//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public import LibSignalClient
public struct TSGroupModelBuilder {
private enum GroupVersion {
case V1(groupId: Data)
case V2(secretParamsData: Data)
}
private let groupVersion: GroupVersion
public var name: String?
public var descriptionText: String?
public var avatarDataState: TSGroupModel.AvatarDataState = .missing
public var groupMembership = GroupMembership()
public var groupAccess: GroupAccess?
public var groupV2Revision: UInt32 = 0
public var avatarUrlPath: String?
public var inviteLinkPassword: Data?
public var isAnnouncementsOnly: Bool = false
public var isJoinRequestPlaceholder: Bool = false
public var addedByAddress: SignalServiceAddress?
public var wasJustMigrated: Bool = false
public var didJustAddSelfViaGroupLink: Bool = false
public var isTerminated: Bool = false
private init(groupVersion: GroupVersion) {
self.groupVersion = groupVersion
}
public init(secretParams: GroupSecretParams) {
self.init(groupVersion: .V2(secretParamsData: secretParams.serialize()))
}
fileprivate init(groupModel: TSGroupModel) {
if let v2 = groupModel as? TSGroupModelV2 {
self.init(groupVersion: .V2(secretParamsData: v2.secretParamsData))
self.groupAccess = v2.access
self.groupV2Revision = v2.revision
self.avatarUrlPath = v2.avatarUrlPath
self.inviteLinkPassword = v2.inviteLinkPassword
self.isAnnouncementsOnly = v2.isAnnouncementsOnly
self.descriptionText = v2.descriptionText
self.isTerminated = v2.isTerminated
// Do not copy transient properties:
//
// * isJoinRequestPlaceholder
// * wasJustMigrated
// * didJustAddSelfViaGroupLink
//
// We want to discard these values when updating group models.
} else {
self.init(groupVersion: .V1(groupId: groupModel.groupId))
}
self.name = groupModel.groupName
self.avatarDataState = groupModel.avatarDataState
self.groupMembership = groupModel.groupMembership
self.addedByAddress = groupModel.addedByAddress
}
// Convert a group state proto received from the service
// into a group model.
private init(groupV2Snapshot: GroupV2Snapshot) throws {
self.init(secretParams: groupV2Snapshot.groupSecretParams)
self.name = groupV2Snapshot.title
self.descriptionText = groupV2Snapshot.descriptionText
self.avatarDataState = groupV2Snapshot.avatarDataState
self.groupMembership = groupV2Snapshot.groupMembership
self.groupAccess = groupV2Snapshot.groupAccess
self.groupV2Revision = groupV2Snapshot.revision
self.avatarUrlPath = groupV2Snapshot.avatarUrlPath
self.inviteLinkPassword = groupV2Snapshot.inviteLinkPassword
self.isAnnouncementsOnly = groupV2Snapshot.isAnnouncementsOnly
self.isJoinRequestPlaceholder = false
self.wasJustMigrated = false
self.didJustAddSelfViaGroupLink = false
self.isTerminated = groupV2Snapshot.isTerminated
}
static func builderForSnapshot(groupV2Snapshot: GroupV2Snapshot, transaction: DBWriteTransaction) throws -> TSGroupModelBuilder {
return try TSGroupModelBuilder(groupV2Snapshot: groupV2Snapshot)
}
public mutating func apply(options: TSGroupModelOptions) {
if options.contains(.didJustAddSelfViaGroupLink) {
didJustAddSelfViaGroupLink = true
}
}
public func build() throws -> TSGroupModel {
let allUsers = groupMembership.allMembersOfAnyKind
for recipientAddress in allUsers {
guard recipientAddress.isValid else {
throw OWSAssertionError("Invalid address.")
}
}
var name: String?
if let strippedName = self.name?.stripped.nilIfEmpty {
name = strippedName
}
switch groupVersion {
case .V1(let groupId):
guard GroupManager.isValidGroupId(groupId, groupsVersion: .V1) else {
throw OWSAssertionError("Invalid groupId.")
}
return TSGroupModel(
groupId: groupId,
name: name,
avatarData: avatarDataState.dataIfPresent,
members: Array(groupMembership.fullMembers),
addedBy: addedByAddress,
)
case .V2(let secretParamsData):
let groupSecretParams = try GroupSecretParams(contents: secretParamsData)
return TSGroupModelV2(
groupId: try groupSecretParams.getPublicParams().getGroupIdentifier().serialize(),
name: name,
descriptionText: descriptionText?.stripped.nilIfEmpty,
avatarDataState: avatarDataState,
groupMembership: groupMembership,
groupAccess: groupAccess ?? .defaultForV2,
revision: groupV2Revision,
secretParamsData: groupSecretParams.serialize(),
avatarUrlPath: avatarUrlPath,
inviteLinkPassword: inviteLinkPassword,
isAnnouncementsOnly: isAnnouncementsOnly,
isJoinRequestPlaceholder: isJoinRequestPlaceholder,
wasJustMigrated: wasJustMigrated,
didJustAddSelfViaGroupLink: didJustAddSelfViaGroupLink,
addedByAddress: addedByAddress,
isTerminated: isTerminated,
)
}
}
public func buildAsV2() throws -> TSGroupModelV2 {
guard let model = try build() as? TSGroupModelV2 else {
throw OWSAssertionError("[GV1] Should be impossible to create a V1 group!")
}
return model
}
}
// MARK: -
public extension TSGroupModel {
var asBuilder: TSGroupModelBuilder {
return TSGroupModelBuilder(groupModel: self)
}
}
// MARK: -
public struct TSGroupModelOptions: OptionSet {
public let rawValue: Int
public static let didJustAddSelfViaGroupLink = TSGroupModelOptions(rawValue: 1 << 0)
public static let throttle = TSGroupModelOptions(rawValue: 1 << 1)
public init(rawValue: Int) {
self.rawValue = rawValue
}
}