// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation import GRDB import SessionUtil import SessionUtilitiesKit // MARK: - Group Info Handling internal extension SessionUtil { static let columnsRelatedToGroupInfo: [ColumnExpression] = [ ClosedGroup.Columns.name, ClosedGroup.Columns.displayPictureUrl, ClosedGroup.Columns.displayPictureEncryptionKey, DisappearingMessagesConfiguration.Columns.isEnabled, DisappearingMessagesConfiguration.Columns.type, DisappearingMessagesConfiguration.Columns.durationSeconds ] // MARK: - Incoming Changes static func handleGroupInfoUpdate( _ db: Database, in config: Config?, latestConfigSentTimestampMs: Int64, using dependencies: Dependencies ) throws { typealias GroupData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?) guard config.needsDump else { return } guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject } } } // MARK: - Outgoing Changes internal extension SessionUtil { static func updatingGroupInfo( _ db: Database, _ updated: [T], using dependencies: Dependencies ) throws -> [T] { guard let updatedGroups: [ClosedGroup] = updated as? [ClosedGroup] else { throw StorageError.generic } // Exclude legacy groups as they aren't managed via SessionUtil let targetGroups: [ClosedGroup] = updatedGroups .filter { SessionId(from: $0.id)?.prefix == .group } // If we only updated the current user contact then no need to continue guard !targetGroups.isEmpty else { return updated } // Loop through each of the groups and update their settings try targetGroups.forEach { group in try SessionUtil.performAndPushChange( db, for: .groupInfo, publicKey: group.threadId, using: dependencies ) { config in guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject } // Update the name var updatedName: [CChar] = group.name.cArray.nullTerminated() groups_info_set_name(conf, &updatedName) // Either assign the updated display pic, or sent a blank pic (to remove the current one) var displayPic: user_profile_pic = user_profile_pic() displayPic.url = group.displayPictureUrl.toLibSession() displayPic.key = group.displayPictureEncryptionKey.toLibSession() groups_info_set_pic(conf, displayPic) } } return updated } static func updatingDisappearingConfigsGroups( _ db: Database, _ updated: [T], using dependencies: Dependencies ) throws -> [T] { guard let updatedDisappearingConfigs: [DisappearingMessagesConfiguration] = updated as? [DisappearingMessagesConfiguration] else { throw StorageError.generic } // Filter out any disappearing config changes not related to updated groups let targetUpdatedConfigs: [DisappearingMessagesConfiguration] = updatedDisappearingConfigs .filter { SessionId.Prefix(from: $0.id) == .group } guard !targetUpdatedConfigs.isEmpty else { return updated } // We should only sync disappearing messages configs which are associated to existing groups let existingGroupIds: [String] = (try? ClosedGroup .filter(ids: targetUpdatedConfigs.map { $0.id }) .select(.threadId) .asRequest(of: String.self) .fetchAll(db)) .defaulting(to: []) // If none of the disappearing messages configs are associated with existing groups then ignore // the changes (no need to do a config sync) guard !existingGroupIds.isEmpty else { return updated } // Loop through each of the groups and update their settings try existingGroupIds .compactMap { groupId in targetUpdatedConfigs.first(where: { $0.id == groupId }).map { (groupId, $0) } } .forEach { groupIdentityPublicKey, updatedConfig in try SessionUtil.performAndPushChange( db, for: .groupInfo, publicKey: groupIdentityPublicKey, using: dependencies ) { config in guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject } groups_info_set_expiry_timer(conf, Int32(updatedConfig.durationSeconds)) } } return updated } } // MARK: - External Outgoing Changes public extension SessionUtil { static func update( _ db: Database, groupIdentityPublicKey: String, name: String? = nil, disappearingConfig: DisappearingMessagesConfiguration? = nil, using dependencies: Dependencies ) throws { try SessionUtil.performAndPushChange( db, for: .groupInfo, publicKey: groupIdentityPublicKey, using: dependencies ) { config in guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject } if let name: String = name { groups_info_set_name(conf, name.toLibSession()) } if let config: DisappearingMessagesConfiguration = disappearingConfig { groups_info_set_expiry_timer(conf, Int32(config.durationSeconds)) } } } }