Signal-iOS/Signal/src/ViewControllers/DebugUI/DebugUIStress.swift
2021-04-29 15:46:43 -07:00

359 lines
16 KiB
Swift

//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
#if DEBUG
@objc
public extension DebugUIStress {
private static func nameForClonedGroup(_ groupThread: TSGroupThread) -> String {
guard let groupName = groupThread.groupModel.groupName else {
return "Cloned Group"
}
return groupName + " Copy"
}
// Creates a new group (by cloning the current group) without informing the,
// other members. This can be used to test "group info requests", etc.
class func cloneAsV1orV2Group(_ oldGroupThread: TSGroupThread) {
firstly { () -> Promise<TSGroupThread> in
let groupName = Self.nameForClonedGroup(oldGroupThread)
return GroupManager.localCreateNewGroup(members: oldGroupThread.groupModel.groupMembers,
groupId: nil,
name: groupName,
avatarData: oldGroupThread.groupModel.groupAvatarData,
disappearingMessageToken: .disabledToken,
newGroupSeed: nil,
shouldSendMessage: false)
}.done { newGroupThread in
self.databaseStorage.write { transaction in
let oldDMConfig = oldGroupThread.disappearingMessagesConfiguration(with: transaction)
_ = OWSDisappearingMessagesConfiguration.applyToken(oldDMConfig.asToken,
toThread: newGroupThread,
transaction: transaction)
}
Logger.info("Complete.")
SignalApp.shared().presentConversation(for: newGroupThread, animated: true)
}.catch(on: .global()) { error in
owsFailDebug("Error: \(error)")
}
}
// Creates a new group (by cloning the current group) without informing the,
// other members. This can be used to test "group info requests", etc.
class func cloneAsV1Group(_ oldGroupThread: TSGroupThread) {
do {
let groupName = Self.nameForClonedGroup(oldGroupThread) + " (v1)"
let newGroupThread = try self.databaseStorage.write { (transaction: SDSAnyWriteTransaction) throws -> TSGroupThread in
let newGroupThread = try GroupManager.createGroupForTests(members: oldGroupThread.groupModel.groupMembers,
name: groupName,
avatarData: oldGroupThread.groupModel.groupAvatarData,
groupId: nil,
groupsVersion: .V1,
transaction: transaction)
let oldDMConfig = oldGroupThread.disappearingMessagesConfiguration(with: transaction)
_ = OWSDisappearingMessagesConfiguration.applyToken(oldDMConfig.asToken,
toThread: newGroupThread,
transaction: transaction)
return newGroupThread
}
assert(newGroupThread.groupModel.groupsVersion == .V1)
SignalApp.shared().presentConversation(for: newGroupThread, animated: true)
} catch {
owsFailDebug("Error: \(error)")
}
}
// Creates a new group (by cloning the current group) without informing the,
// other members. This can be used to test "group info requests", etc.
class func cloneAsV2Group(_ oldGroupThread: TSGroupThread) {
firstly { () -> Promise<Void> in
let members: [SignalServiceAddress] = oldGroupThread.groupModel.groupMembers
for member in members {
Logger.verbose("Candidate member: \(member)")
}
return GroupManager.tryToEnableGroupsV2(for: members, isBlocking: true, ignoreErrors: true)
}.then { () -> Promise<TSGroupThread> in
guard GroupManager.defaultGroupsVersion == .V2 else {
throw OWSAssertionError("Groups v2 not enabled.")
}
let members = try self.databaseStorage.read { (transaction: SDSAnyReadTransaction) throws -> [SignalServiceAddress] in
let members: [SignalServiceAddress] = oldGroupThread.groupModel.groupMembers.filter { address in
GroupManager.doesUserSupportGroupsV2(address: address, transaction: transaction)
}
guard GroupManager.canUseV2(for: Set(members), transaction: transaction) else {
throw OWSAssertionError("Error filtering users.")
}
return members
}
for member in members {
Logger.verbose("Member: \(member)")
}
let groupName = Self.nameForClonedGroup(oldGroupThread) + " (v2)"
return GroupManager.localCreateNewGroup(members: members,
groupId: nil,
name: groupName,
avatarData: oldGroupThread.groupModel.groupAvatarData,
disappearingMessageToken: .disabledToken,
newGroupSeed: nil,
shouldSendMessage: false)
}.done { (newGroupThread) in
assert(newGroupThread.groupModel.groupsVersion == .V2)
self.databaseStorage.write { transaction in
let oldDMConfig = oldGroupThread.disappearingMessagesConfiguration(with: transaction)
_ = OWSDisappearingMessagesConfiguration.applyToken(oldDMConfig.asToken,
toThread: newGroupThread,
transaction: transaction)
}
Logger.info("Complete.")
SignalApp.shared().presentConversation(for: newGroupThread, animated: true)
}.catch(on: .global()) { error in
owsFailDebug("Error: \(error)")
}
}
class func copyToAnotherGroup(_ srcGroupThread: TSGroupThread, fromViewController: UIViewController) {
let groupThreads = self.databaseStorage.read { (transaction: SDSAnyReadTransaction) -> [TSGroupThread] in
TSThread.anyFetchAll(transaction: transaction).compactMap { $0 as? TSGroupThread }
}
guard !groupThreads.isEmpty else {
owsFailDebug("No groups.")
return
}
let groupThreadPicker = GroupThreadPicker(groupThreads: groupThreads) { (dstGroupThread: TSGroupThread) in
Self.copyToAnotherGroup(srcGroupThread: srcGroupThread, dstGroupThread: dstGroupThread)
}
fromViewController.present(groupThreadPicker, animated: true)
}
class func copyToAnotherGroup(srcGroupThread: TSGroupThread, dstGroupThread: TSGroupThread) {
guard let localAddress = tsAccountManager.localAddress else {
owsFailDebug("Missing localAddress.")
return
}
let membersToAdd = srcGroupThread.groupMembership.allMembersOfAnyKind.subtracting(dstGroupThread.groupMembership.allMembersOfAnyKind)
firstly { () -> Promise<Void> in
for member in membersToAdd {
Logger.verbose("Candidate member: \(member)")
}
return GroupManager.tryToEnableGroupsV2(for: Array(membersToAdd), isBlocking: true, ignoreErrors: true)
}.then { () -> Promise<TSGroupThread> in
let oldGroupModel = dstGroupThread.groupModel
let newGroupModel = try self.databaseStorage.read { (transaction: SDSAnyReadTransaction) throws -> TSGroupModel in
let validMembersToAdd: [SignalServiceAddress]
if dstGroupThread.isGroupV1Thread {
validMembersToAdd = membersToAdd.filter { $0.phoneNumber != nil }
} else {
validMembersToAdd = membersToAdd.filter { address in
GroupManager.doesUserSupportGroupsV2(address: address, transaction: transaction)
}
}
for member in validMembersToAdd {
Logger.verbose("Adding: \(member)")
}
Logger.verbose("Adding: \(validMembersToAdd.count)")
guard !validMembersToAdd.isEmpty else {
throw OWSAssertionError("No valid members to add.")
}
var groupModelBuilder = oldGroupModel.asBuilder
var groupMembershipBuilder = oldGroupModel.groupMembership.asBuilder
groupMembershipBuilder.addFullMembers(Set(validMembersToAdd), role: .`normal`)
groupModelBuilder.groupMembership = groupMembershipBuilder.build()
return try groupModelBuilder.build(transaction: transaction)
}
guard oldGroupModel.groupsVersion == newGroupModel.groupsVersion else {
throw OWSAssertionError("Group Version failure.")
}
return GroupManager.localUpdateExistingGroup(oldGroupModel: oldGroupModel,
newGroupModel: newGroupModel,
dmConfiguration: nil,
groupUpdateSourceAddress: localAddress)
}.done { (groupThread) in
Logger.info("Complete.")
SignalApp.shared().presentConversation(for: groupThread, animated: true)
}.catch(on: .global()) { error in
owsFailDebug("Error: \(error)")
}
}
class func addDebugMembersToGroup(_ groupThread: TSGroupThread) {
let e164ToAdd: [String] = [
"+16785621057"
]
let membersToAdd = Set(e164ToAdd.map { SignalServiceAddress(phoneNumber: $0) })
let oldGroupModel = groupThread.groupModel
let newGroupModel: TSGroupModel
do {
newGroupModel = try databaseStorage.read { transaction in
var builder = oldGroupModel.asBuilder
let oldGroupMembership = oldGroupModel.groupMembership
var groupMembershipBuilder = oldGroupMembership.asBuilder
for address in membersToAdd {
assert(address.isValid)
guard !oldGroupMembership.isMemberOfAnyKind(address) else {
Logger.warn("Recipient is already in group.")
continue
}
// GroupManager will separate out members as pending if necessary.
groupMembershipBuilder.addFullMember(address, role: .normal)
}
builder.groupMembership = groupMembershipBuilder.build()
return try builder.build(transaction: transaction)
}
} catch {
owsFailDebug("Error: \(error)")
return
}
guard let localAddress = tsAccountManager.localAddress else {
owsFailDebug("Missing localAddress.")
return
}
firstly { () -> Promise<Void> in
return GroupManager.messageProcessingPromise(for: oldGroupModel,
description: self.logTag())
}.then(on: .global()) { _ in
// dmConfiguration: nil means don't change disappearing messages configuration.
GroupManager.localUpdateExistingGroup(oldGroupModel: oldGroupModel,
newGroupModel: newGroupModel,
dmConfiguration: nil,
groupUpdateSourceAddress: localAddress)
}.done(on: .global()) { (_) in
Logger.info("Complete.")
}.catch(on: .global()) { error in
owsFailDebug("Error: \(error)")
}
}
class func makeAllMembersAdmin(_ groupThread: TSGroupThread) {
guard let groupModelV2 = groupThread.groupModel as? TSGroupModelV2 else {
owsFailDebug("Invalid group model.")
return
}
let uuids = groupModelV2.groupMembership.fullMembers.compactMap { $0.uuid }
firstly { () -> Promise<TSGroupThread> in
GroupManager.changeMemberRolesV2(groupModel: groupModelV2, uuids: uuids, role: .administrator)
}.done(on: .global()) { (_) in
Logger.info("Complete.")
}.catch(on: .global()) { error in
owsFailDebug("Error: \(error)")
}
}
class func logMembership(_ groupThread: TSGroupThread) {
let groupMembership = groupThread.groupModel.groupMembership
let uuids = groupMembership.allMembersOfAnyKind.compactMap { $0.uuid }
let phoneNumbers = groupMembership.allMembersOfAnyKind.compactMap { $0.phoneNumber }
Logger.info("uuids: \(uuids.map { $0.uuidString }.joined(separator: "\n")).")
Logger.info("phoneNumbers: \(phoneNumbers.joined(separator: "\n")).")
}
class func deleteOtherProfiles() {
databaseStorage.write { transaction in
let profiles = OWSUserProfile.anyFetchAll(transaction: transaction)
for profile in profiles {
guard !OWSUserProfile.isLocalProfileAddress(profile.address) else {
continue
}
Logger.verbose("Deleting: \(profile.address)")
profile.anyRemove(transaction: transaction)
}
}
}
}
// MARK: -
class GroupThreadPicker: OWSTableViewController {
private let groupThreads: [TSGroupThread]
private let completion: (TSGroupThread) -> Void
init(groupThreads: [TSGroupThread], completion: @escaping (TSGroupThread) -> Void) {
self.groupThreads = groupThreads
self.completion = completion
super.init()
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Select Destination Group"
rebuildTableContents()
setupNavigationBar()
applyTheme()
}
// MARK: - Data providers
func setupNavigationBar() {
navigationItem.leftBarButtonItem = UIBarButtonItem(
title: CommonStrings.cancelButton,
style: .plain,
target: self,
action: #selector(didTapCancel)
)
}
func rebuildTableContents() {
let contactsManager = Self.contactsManager
let databaseStorage = Self.databaseStorage
let contents = OWSTableContents()
let section = OWSTableSection()
section.headerTitle = "Select a group to add the members to"
databaseStorage.read { transaction in
let sortedGroupThreads = self.groupThreads.sorted { (left, right) -> Bool in
left.lastInteractionRowId > right.lastInteractionRowId
}
for groupThread in sortedGroupThreads {
let groupName = contactsManager.displayName(for: groupThread, transaction: transaction)
section.add(OWSTableItem.actionItem(withText: groupName) { [weak self] in
self?.didSelectGroupThread(groupThread)
})
}
}
contents.addSection(section)
self.contents = contents
}
// MARK: - Actions
@objc func didTapCancel() {
presentingViewController?.dismiss(animated: true, completion: nil)
}
func didSelectGroupThread(_ groupThread: TSGroupThread) {
let completion = self.completion
presentingViewController?.dismiss(animated: true) {
completion(groupThread)
}
}
}
#endif