From c572db4886677f201f1261fac1be5fe3b3e7df4e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 19 Feb 2020 16:04:14 -0300 Subject: [PATCH] Add TSGroupModelBuilder. --- .../UpdateGroupViewController.swift | 24 +- .../groups/GroupV2UpdatesImpl.swift | 8 +- SignalMessaging/groups/GroupsV2Changes.swift | 23 +- SignalMessaging/groups/GroupsV2Test.swift | 6 +- .../src/groups/GroupManager.swift | 230 ++++-------------- SignalServiceKit/src/groups/TSGroupModel.h | 4 +- .../src/groups/TSGroupModel.swift | 4 +- .../src/groups/TSGroupModelBuilder.swift | 160 ++++++++++++ 8 files changed, 242 insertions(+), 217 deletions(-) create mode 100644 SignalServiceKit/src/groups/TSGroupModelBuilder.swift diff --git a/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.swift b/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.swift index 66a7fb3689..ef4ea68125 100644 --- a/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.swift +++ b/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.swift @@ -35,12 +35,11 @@ public extension UpdateGroupViewController { newAvatarData: Data?, v1Members: Set) -> TSGroupModel { do { - let groupId = oldGroupModel.groupId return try databaseStorage.read { transaction in - let groupMembership: GroupMembership + let newGroupMembership: GroupMembership switch oldGroupModel.groupsVersion { case .V1: - groupMembership = GroupMembership(v1Members: v1Members) + newGroupMembership = GroupMembership(v1Members: v1Members) case .V2: // GroupsV2 TODO: This is a temporary implementation until we // rewrite groups v2 to be aware of roles, access, kicking @@ -59,19 +58,14 @@ public extension UpdateGroupViewController { } // GroupsV2 TODO: Remove members, change roles, etc. when // UI supports pending members, kicking members, etc.. - groupMembership = groupMembershipBuilder.build() + newGroupMembership = groupMembershipBuilder.build() } - let groupsVersion = oldGroupModel.groupsVersion - return try GroupManager.buildGroupModel(groupId: groupId, - name: newTitle, - avatarData: newAvatarData, - groupMembership: groupMembership, - groupAccess: oldGroupModel.groupAccess, - groupsVersion: groupsVersion, - groupV2Revision: oldGroupModel.groupV2Revision, - groupSecretParamsData: oldGroupModel.groupSecretParamsData, - newGroupSeed: nil, - transaction: transaction) + + var builder = oldGroupModel.asBuilder + builder.name = newTitle + builder.avatarData = newAvatarData + builder.groupMembership = newGroupMembership + return try builder.build(transaction: transaction) } } catch { owsFailDebug("Error: \(error)") diff --git a/SignalMessaging/groups/GroupV2UpdatesImpl.swift b/SignalMessaging/groups/GroupV2UpdatesImpl.swift index a81d48e31e..5aced01a89 100644 --- a/SignalMessaging/groups/GroupV2UpdatesImpl.swift +++ b/SignalMessaging/groups/GroupV2UpdatesImpl.swift @@ -458,8 +458,8 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { } } let oldGroupModel = groupThread.groupModel - let newGroupModel = try GroupManager.buildGroupModel(groupV2Snapshot: groupChange.snapshot, - transaction: transaction) + let builder = try TSGroupModelBuilder(groupV2Snapshot: groupChange.snapshot) + let newGroupModel = try builder.build(transaction: transaction) if changeRevision == oldGroupModel.groupV2Revision { if !oldGroupModel.isEqual(to: newGroupModel) { @@ -536,8 +536,8 @@ public class GroupV2UpdatesImpl: NSObject, GroupV2UpdatesSwift { } return databaseStorage.write(.promise) { (transaction: SDSAnyWriteTransaction) throws -> TSGroupThread in - let newGroupModel = try GroupManager.buildGroupModel(groupV2Snapshot: groupV2Snapshot, - transaction: transaction) + let builder = try TSGroupModelBuilder(groupV2Snapshot: groupV2Snapshot) + let newGroupModel = try builder.build(transaction: transaction) let newDisappearingMessageToken = groupV2Snapshot.disappearingMessageToken // groupUpdateSourceAddress is nil because we don't know the // author(s) of changes reflected in the snapshot. diff --git a/SignalMessaging/groups/GroupsV2Changes.swift b/SignalMessaging/groups/GroupsV2Changes.swift index bfe3057ba1..224c97d284 100644 --- a/SignalMessaging/groups/GroupsV2Changes.swift +++ b/SignalMessaging/groups/GroupsV2Changes.swift @@ -51,7 +51,6 @@ public class GroupsV2Changes { changeActionsProto: GroupsProtoGroupChangeActions, transaction: SDSAnyReadTransaction) throws -> ChangedGroupModel { let oldGroupModel = groupThread.groupModel - let groupId = oldGroupModel.groupId let groupsVersion = oldGroupModel.groupsVersion guard groupsVersion == .V2 else { throw OWSAssertionError("Invalid groupsVersion: \(groupsVersion).") @@ -261,7 +260,6 @@ public class GroupsV2Changes { if let disappearingMessagesTimerEncrypted = action.timer { let disappearingMessagesTimerDecrypted = try groupV2Params.decryptBlob(disappearingMessagesTimerEncrypted) let disappearingMessagesProto = try GroupsProtoDisappearingMessagesTimer.parseData(disappearingMessagesTimerDecrypted) - let durationSeconds = disappearingMessagesProto.duration newDisappearingMessageToken = DisappearingMessageToken.token(forProtoExpireTimer: disappearingMessagesProto.duration) } } @@ -283,17 +281,18 @@ public class GroupsV2Changes { let newGroupMembership = groupMembershipBuilder.build() let newGroupAccess = GroupAccess(members: newMembersAccess, attributes: newAttributesAccess) // GroupsV2 TODO: Avatar. - let avatarData: Data? = oldGroupModel.groupAvatarData + let newAvatarData: Data? = oldGroupModel.groupAvatarData + + var builder = oldGroupModel.asBuilder + builder.name = newGroupName + builder.avatarData = newAvatarData + builder.groupMembership = newGroupMembership + builder.groupAccess = newGroupAccess + builder.groupsVersion = groupsVersion + builder.groupV2Revision = newRevision + builder.groupSecretParamsData = groupSecretParamsData + let newGroupModel = try builder.build(transaction: transaction) - let newGroupModel = try GroupManager.buildGroupModel(groupId: groupId, - name: newGroupName, - avatarData: avatarData, - groupMembership: newGroupMembership, - groupAccess: newGroupAccess, - groupsVersion: groupsVersion, - groupV2Revision: newRevision, - groupSecretParamsData: groupSecretParamsData, - transaction: transaction) return ChangedGroupModel(oldGroupModel: oldGroupModel, newGroupModel: newGroupModel, newDisappearingMessageToken: newDisappearingMessageToken, diff --git a/SignalMessaging/groups/GroupsV2Test.swift b/SignalMessaging/groups/GroupsV2Test.swift index e9367bac91..09cb58a911 100644 --- a/SignalMessaging/groups/GroupsV2Test.swift +++ b/SignalMessaging/groups/GroupsV2Test.swift @@ -75,7 +75,7 @@ public class GroupsV2Test: NSObject { } }.map(on: .global()) { (groupId: Data, groupV2Snapshot: GroupV2Snapshot) throws -> Data in let groupModel = try self.databaseStorage.read { transaction in - return try GroupManager.buildGroupModel(groupV2Snapshot: groupV2Snapshot, transaction: transaction) + return try TSGroupModelBuilder(groupV2Snapshot: groupV2Snapshot).build(transaction: transaction) } guard groupModel.groupV2Revision == 0 else { throw OWSAssertionError("Unexpected groupV2Revision: \(groupModel.groupV2Revision).") @@ -145,7 +145,7 @@ public class GroupsV2Test: NSObject { } }.map(on: .global()) { (groupId: Data, groupV2Snapshot: GroupV2Snapshot) throws -> Data in let groupModel = try self.databaseStorage.read { transaction in - return try GroupManager.buildGroupModel(groupV2Snapshot: groupV2Snapshot, transaction: transaction) + return try TSGroupModelBuilder(groupV2Snapshot: groupV2Snapshot).build(transaction: transaction) } guard groupModel.groupV2Revision == 1 else { throw OWSAssertionError("Unexpected groupV2Revision: \(groupModel.groupV2Revision).") @@ -215,7 +215,7 @@ public class GroupsV2Test: NSObject { } }.map(on: .global()) { (groupId: Data, groupV2Snapshot: GroupV2Snapshot) throws -> Data in let groupModel = try self.databaseStorage.read { transaction in - return try GroupManager.buildGroupModel(groupV2Snapshot: groupV2Snapshot, transaction: transaction) + return try TSGroupModelBuilder(groupV2Snapshot: groupV2Snapshot).build(transaction: transaction) } guard groupModel.groupV2Revision == 2 else { throw OWSAssertionError("Unexpected groupV2Revision: \(groupModel.groupV2Revision).") diff --git a/SignalServiceKit/src/groups/GroupManager.swift b/SignalServiceKit/src/groups/GroupManager.swift index 4be7939e82..3b841b5335 100644 --- a/SignalServiceKit/src/groups/GroupManager.swift +++ b/SignalServiceKit/src/groups/GroupManager.swift @@ -102,148 +102,23 @@ public class GroupManager: NSObject { // MARK: - Group Models - public static func buildGroupModel(groupId groupIdParam: Data?, - name nameParam: String?, - avatarData: Data?, - groupMembership: GroupMembership, - groupAccess: GroupAccess, - groupsVersion groupsVersionParam: GroupsVersion? = nil, - groupV2Revision: UInt32, - groupSecretParamsData groupSecretParamsDataParam: Data? = nil, - newGroupSeed newGroupSeedParam: NewGroupSeed? = nil, - transaction: SDSAnyReadTransaction) throws -> TSGroupModel { - - let newGroupSeed: NewGroupSeed - if let newGroupSeedParam = newGroupSeedParam { - newGroupSeed = newGroupSeedParam - } else { - newGroupSeed = NewGroupSeed() - } - - let allUsers = groupMembership.allUsers - for recipientAddress in allUsers { - guard recipientAddress.isValid else { - throw OWSAssertionError("Invalid address.") - } - } - var name: String? - if let strippedName = nameParam?.stripped, - strippedName.count > 0 { - name = strippedName - } - - let groupsVersion: GroupsVersion - if let groupsVersionParam = groupsVersionParam { - groupsVersion = groupsVersionParam - } else { - groupsVersion = self.groupsVersion(for: allUsers, - transaction: transaction) - } - - let groupId: Data - if let groupIdParam = groupIdParam { - groupId = groupIdParam - } else { - switch groupsVersion { - case .V1: - groupId = newGroupSeed.groupIdV1 - case .V2: - guard let groupIdV2 = newGroupSeed.groupIdV2 else { - throw OWSAssertionError("Missing groupIdV2.") - } - groupId = groupIdV2 - } - } - guard isValidGroupId(groupId, groupsVersion: groupsVersion) else { - throw OWSAssertionError("Invalid groupId.") - } - - switch groupsVersion { - case .V1: - return TSGroupModel(groupId: groupId, - name: name, - avatarData: avatarData, - members: Array(groupMembership.nonPendingMembers)) - case .V2: - let groupSecretParamsData: Data - if let groupSecretParamsDataParam = groupSecretParamsDataParam { - groupSecretParamsData = groupSecretParamsDataParam - } else { - guard let groupSecretParamsDataParam = newGroupSeed.groupSecretParamsData else { - throw OWSAssertionError("Missing groupSecretParamsData.") - } - groupSecretParamsData = groupSecretParamsDataParam - } - - return TSGroupModelV2(groupId: groupId, - name: name, - avatarData: avatarData, - groupMembership: groupMembership, - groupAccess: groupAccess, - revision: groupV2Revision, - secretParamsData: groupSecretParamsData) - } - } - - // Convert a group state proto received from the service - // into a group model. - public static func buildGroupModel(groupV2Snapshot: GroupV2Snapshot, - transaction: SDSAnyReadTransaction) throws -> TSGroupModel { - let groupSecretParamsData = groupV2Snapshot.groupSecretParamsData - let groupId = try groupsV2.groupId(forGroupSecretParamsData: groupSecretParamsData) - let name: String = groupV2Snapshot.title - let groupMembership = groupV2Snapshot.groupMembership - let groupAccess = groupV2Snapshot.groupAccess - // GroupsV2 TODO: Avatar. - let avatarData: Data? = nil - let groupsVersion = GroupsVersion.V2 - let revision = groupV2Snapshot.revision - - return try buildGroupModel(groupId: groupId, - name: name, - avatarData: avatarData, - groupMembership: groupMembership, - groupAccess: groupAccess, - groupsVersion: groupsVersion, - groupV2Revision: revision, - groupSecretParamsData: groupSecretParamsData, - transaction: transaction) - } - // This should only be used for certain legacy edge cases. @objc public static func fakeGroupModel(groupId: Data?, transaction: SDSAnyReadTransaction) -> TSGroupModel? { do { - let groupMembership = GroupMembership.empty - let groupAccess = GroupAccess.allAccess - return try buildGroupModel(groupId: groupId, - name: nil, - avatarData: nil, - groupMembership: groupMembership, - groupAccess: groupAccess, - groupsVersion: .V1, - groupV2Revision: 0, - groupSecretParamsData: nil, - transaction: transaction) + var builder = TSGroupModelBuilder() + builder.groupId = groupId + builder.groupsVersion = .V1 + return try builder.build(transaction: transaction) } catch { owsFailDebug("Error: \(error)") return nil } } - private static func groupsVersion(for members: Set, - transaction: SDSAnyReadTransaction) -> GroupsVersion { - - guard RemoteConfig.groupsV2CreateGroups else { - return .V1 - } - let canUseV2 = self.canUseV2(for: members, transaction: transaction) - return canUseV2 ? defaultGroupsVersion : .V1 - } - - private static func canUseV2(for members: Set, - transaction: SDSAnyReadTransaction) -> Bool { + public static func canUseV2(for members: Set, + transaction: SDSAnyReadTransaction) -> Bool { for recipientAddress in members { guard doesUserSupportGroupsV2(address: recipientAddress, transaction: transaction) else { @@ -363,14 +238,14 @@ public class GroupManager: NSObject { throw OWSAssertionError("Missing localAddress.") } - return try self.buildGroupModel(groupId: groupId, - name: name, - avatarData: avatarData, - groupMembership: groupMembership, - groupAccess: groupAccess, - groupV2Revision: 0, - newGroupSeed: newGroupSeed, - transaction: transaction) + var builder = TSGroupModelBuilder() + builder.groupId = groupId + builder.name = name + builder.avatarData = avatarData + builder.groupMembership = groupMembership + builder.groupAccess = groupAccess + builder.newGroupSeed = newGroupSeed + return try builder.build(transaction: transaction) } return groupModel }.then(on: .global()) { (proposedGroupModel: TSGroupModel) -> Promise in @@ -384,7 +259,7 @@ public class GroupManager: NSObject { self.groupsV2.fetchCurrentGroupV2Snapshot(groupModel: proposedGroupModel) }.map(on: .global()) { (groupV2Snapshot: GroupV2Snapshot) -> TSGroupModel in let createdGroupModel = try self.databaseStorage.read { transaction in - return try self.buildGroupModel(groupV2Snapshot: groupV2Snapshot, transaction: transaction) + return try TSGroupModelBuilder(groupV2Snapshot: groupV2Snapshot).build(transaction: transaction) } if proposedGroupModel != createdGroupModel { Logger.verbose("proposedGroupModel: \(proposedGroupModel.debugDescription)") @@ -592,14 +467,15 @@ public class GroupManager: NSObject { // GroupsV2 TODO: Let tests specify access levels. let groupAccess = GroupAccess.allAccess // Use buildGroupModel() to fill in defaults, like it was a new group. - let groupModel = try buildGroupModel(groupId: groupId, - name: name, - avatarData: avatarData, - groupMembership: groupMembership, - groupAccess: groupAccess, - groupsVersion: groupsVersion, - groupV2Revision: 0, - transaction: transaction) + + var builder = TSGroupModelBuilder() + builder.groupId = groupId + builder.name = name + builder.avatarData = avatarData + builder.groupMembership = groupMembership + builder.groupAccess = groupAccess + builder.groupsVersion = groupsVersion + let groupModel = try builder.build(transaction: transaction) // Just create it in the database, don't create it on the service. // @@ -654,15 +530,16 @@ public class GroupManager: NSObject { groupUpdateSourceAddress: SignalServiceAddress?, transaction: SDSAnyWriteTransaction) throws -> UpsertGroupResult { - let groupModel = try buildGroupModel(groupId: groupId, - name: name, - avatarData: avatarData, - groupMembership: groupMembership, - groupAccess: groupAccess, - groupsVersion: groupsVersion, - groupV2Revision: groupV2Revision, - groupSecretParamsData: groupSecretParamsData, - transaction: transaction) + var builder = TSGroupModelBuilder() + builder.groupId = groupId + builder.name = name + builder.avatarData = avatarData + builder.groupMembership = groupMembership + builder.groupAccess = groupAccess + builder.groupsVersion = groupsVersion + builder.groupV2Revision = groupV2Revision + builder.groupSecretParamsData = groupSecretParamsData + let groupModel = try builder.build(transaction: transaction) return try remoteUpsertExistingGroup(groupModel: groupModel, disappearingMessageToken: disappearingMessageToken, @@ -898,15 +775,16 @@ public class GroupManager: NSObject { // since we'll probably be updating the TSGroupThread's // group models with one derived from the service. let newRevision = oldGroupModel.groupV2Revision + 1 - let newGroupModel = try buildGroupModel(groupId: oldGroupModel.groupId, - name: name, - avatarData: avatarData, - groupMembership: groupMembership, - groupAccess: groupAccess, - groupsVersion: oldGroupModel.groupsVersion, - groupV2Revision: newRevision, - groupSecretParamsData: oldGroupModel.groupSecretParamsData, - transaction: transaction) + var builder = TSGroupModelBuilder() + builder.groupId = oldGroupModel.groupId + builder.name = name + builder.avatarData = avatarData + builder.groupMembership = groupMembership + builder.groupAccess = groupAccess + builder.groupsVersion = oldGroupModel.groupsVersion + builder.groupV2Revision = newRevision + builder.groupSecretParamsData = oldGroupModel.groupSecretParamsData + let newGroupModel = try builder.build(transaction: transaction) if oldGroupModel.isEqual(to: newGroupModel) { // Skip redundant update. throw GroupsV2Error.redundantChange @@ -1028,8 +906,8 @@ public class GroupManager: NSObject { } private static func sendDisappearingMessagesConfigurationMessage(updateResult: UpdateDMConfigurationResult, - thread: TSThread, - transaction: SDSAnyWriteTransaction) { + thread: TSThread, + transaction: SDSAnyWriteTransaction) { guard updateResult.action == .updated else { // The update was redundant, don't send an update message. return @@ -1090,17 +968,11 @@ public class GroupManager: NSObject { var groupMembershipBuilder = oldGroupModel.groupMembership.asBuilder groupMembershipBuilder.remove(localAddress) let newGroupMembership = groupMembershipBuilder.build() - let groupAccess = oldGroupModel.groupAccess - let newGroupModel = try self.buildGroupModel(groupId: groupId, - name: oldGroupModel.groupName, - avatarData: oldGroupModel.groupAvatarData, - groupMembership: newGroupMembership, - groupAccess: groupAccess, - groupsVersion: oldGroupModel.groupsVersion, - groupV2Revision: oldGroupModel.groupV2Revision, - groupSecretParamsData: nil, - newGroupSeed: nil, - transaction: transaction) + + var builder = oldGroupModel.asBuilder + builder.groupMembership = newGroupMembership + let newGroupModel = try builder.build(transaction: transaction) + let groupUpdateSourceAddress = localAddress let result = try self.updateExistingGroupThreadInDatabaseAndCreateInfoMessage(newGroupModel: newGroupModel, newDisappearingMessageToken: nil, diff --git a/SignalServiceKit/src/groups/TSGroupModel.h b/SignalServiceKit/src/groups/TSGroupModel.h index 5e80db6be1..552b39d336 100644 --- a/SignalServiceKit/src/groups/TSGroupModel.h +++ b/SignalServiceKit/src/groups/TSGroupModel.h @@ -19,10 +19,10 @@ typedef NS_CLOSED_ENUM(uint32_t, GroupsVersion) { GroupsVersionV1 = 0, GroupsVer typedef NS_CLOSED_ENUM( NSUInteger, TSGroupMemberRole) { TSGroupMemberRole_Normal = 0, TSGroupMemberRole_Administrator = 1 }; -// NOTE: This class is tightly coupled to GroupManager. +// NOTE: This class is tightly coupled to TSGroupModelBuilder. // If you modify this class - especially if you // add any new properties - make sure to update -// GroupManager.buildGroupModel(). +// TSGroupModelBuilder. @interface TSGroupModel : MTLModel // groupMembers includes administrators and normal members. diff --git a/SignalServiceKit/src/groups/TSGroupModel.swift b/SignalServiceKit/src/groups/TSGroupModel.swift index a0ef411809..f87669f057 100644 --- a/SignalServiceKit/src/groups/TSGroupModel.swift +++ b/SignalServiceKit/src/groups/TSGroupModel.swift @@ -21,10 +21,10 @@ public extension TSGroupModel { // Like TSGroupModel, TSGroupModelV2 is intended to be immutable. // -// NOTE: This class is tightly coupled to GroupManager. +// NOTE: This class is tightly coupled to TSGroupModelBuilder. // If you modify this class - especially if you // add any new properties - make sure to update -// GroupManager.buildGroupModel(). +// TSGroupModelBuilder. @objc public class TSGroupModelV2: TSGroupModel { diff --git a/SignalServiceKit/src/groups/TSGroupModelBuilder.swift b/SignalServiceKit/src/groups/TSGroupModelBuilder.swift new file mode 100644 index 0000000000..411e66c4b6 --- /dev/null +++ b/SignalServiceKit/src/groups/TSGroupModelBuilder.swift @@ -0,0 +1,160 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +import Foundation + +public struct TSGroupModelBuilder { + + // MARK: - Dependencies + + private var groupsV2: GroupsV2 { + return SSKEnvironment.shared.groupsV2 + } + + // MARK: - + + public var groupId: Data? + public var name: String? + public var avatarData: Data? + public var groupMembership = GroupMembership() + public var groupAccess: GroupAccess? + public var groupsVersion: GroupsVersion? + public var groupV2Revision: UInt32 = 0 + public var groupSecretParamsData: Data? + public var newGroupSeed: NewGroupSeed? + + public init() {} + + // Convert a group state proto received from the service + // into a group model. + public init(groupV2Snapshot: GroupV2Snapshot) throws { + self.groupId = try groupsV2.groupId(forGroupSecretParamsData: groupV2Snapshot.groupSecretParamsData) + self.name = groupV2Snapshot.title + // GroupsV2 TODO: Avatar. + // self.avatarData = groupV2Snapshot.avatarData + self.groupMembership = groupV2Snapshot.groupMembership + self.groupAccess = groupV2Snapshot.groupAccess + self.groupsVersion = GroupsVersion.V2 + self.groupV2Revision = groupV2Snapshot.revision + self.groupSecretParamsData = groupV2Snapshot.groupSecretParamsData + } + + public func build(transaction: SDSAnyReadTransaction) throws -> TSGroupModel { + + let allUsers = groupMembership.allUsers + for recipientAddress in allUsers { + guard recipientAddress.isValid else { + throw OWSAssertionError("Invalid address.") + } + } + + var name: String? + if let strippedName = self.name?.stripped, + strippedName.count > 0 { + name = strippedName + } + + let groupsVersion = buildGroupsVersion(for: allUsers, + transaction: transaction) + + let newGroupSeed = self.newGroupSeed ?? NewGroupSeed() + + let groupId = try buildGroupId(groupsVersion: groupsVersion, + newGroupSeed: newGroupSeed) + + guard GroupManager.isValidGroupId(groupId, groupsVersion: groupsVersion) else { + throw OWSAssertionError("Invalid groupId.") + } + + switch groupsVersion { + case .V1: + return TSGroupModel(groupId: groupId, + name: name, + avatarData: avatarData, + members: Array(groupMembership.nonPendingMembers)) + case .V2: + let groupAccess = buildGroupAccess(groupsVersion: groupsVersion) + let groupSecretParamsData = try buildGroupSecretParamsData(newGroupSeed: newGroupSeed) + + return TSGroupModelV2(groupId: groupId, + name: name, + avatarData: avatarData, + groupMembership: groupMembership, + groupAccess: groupAccess, + revision: groupV2Revision, + secretParamsData: groupSecretParamsData) + } + } + + private func buildGroupId(groupsVersion: GroupsVersion, + newGroupSeed: NewGroupSeed) throws -> Data { + if let value = groupId { + return value + } + + switch groupsVersion { + case .V1: + return newGroupSeed.groupIdV1 + case .V2: + guard let groupIdV2 = newGroupSeed.groupIdV2 else { + throw OWSAssertionError("Missing groupIdV2.") + } + return groupIdV2 + } + } + + private func buildGroupSecretParamsData(newGroupSeed: NewGroupSeed) throws -> Data { + if let value = groupSecretParamsData { + return value + } + + guard let value = newGroupSeed.groupSecretParamsData else { + throw OWSAssertionError("Missing groupSecretParamsData.") + } + return value + } + + private func buildGroupAccess(groupsVersion: GroupsVersion) -> GroupAccess { + if let value = groupAccess { + return value + } + + switch groupsVersion { + case .V1: + return GroupAccess.forV1 + case .V2: + return GroupAccess.defaultV2Access + } + } + + private func buildGroupsVersion(for members: Set, + transaction: SDSAnyReadTransaction) -> GroupsVersion { + if let value = groupsVersion { + return value + } + + guard RemoteConfig.groupsV2CreateGroups else { + return .V1 + } + let canUseV2 = GroupManager.canUseV2(for: members, transaction: transaction) + return canUseV2 ? GroupManager.defaultGroupsVersion : .V1 + } +} + +// MARK: - + +public extension TSGroupModel { + var asBuilder: TSGroupModelBuilder { + var builder = TSGroupModelBuilder() + builder.groupId = self.groupId + builder.name = self.groupName + builder.avatarData = self.groupAvatarData + builder.groupMembership = self.groupMembership + builder.groupAccess = self.groupAccess + builder.groupsVersion = self.groupsVersion + builder.groupV2Revision = self.groupV2Revision + builder.groupSecretParamsData = self.groupSecretParamsData + return builder + } +}