Add TSGroupModelBuilder.

This commit is contained in:
Matthew Chen 2020-02-19 16:04:14 -03:00
parent fe692fc281
commit c572db4886
8 changed files with 242 additions and 217 deletions

View File

@ -35,12 +35,11 @@ public extension UpdateGroupViewController {
newAvatarData: Data?,
v1Members: Set<SignalServiceAddress>) -> 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)")

View File

@ -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.

View File

@ -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,

View File

@ -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).")

View File

@ -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<SignalServiceAddress>,
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<SignalServiceAddress>,
transaction: SDSAnyReadTransaction) -> Bool {
public static func canUseV2(for members: Set<SignalServiceAddress>,
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<TSGroupModel> 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,

View File

@ -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.

View File

@ -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 {

View File

@ -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<SignalServiceAddress>,
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
}
}