|
|
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import GRDB
|
|
|
|
import SessionUtil
|
|
|
|
import SessionSnodeKit
|
|
|
|
import SessionUtilitiesKit
|
|
|
|
|
|
|
|
// MARK: - Size Restrictions
|
|
|
|
|
|
|
|
public extension LibSession {
|
|
|
|
static var sizeMaxGroupMemberCount: Int { 100 }
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Group Members Handling
|
|
|
|
|
|
|
|
internal extension LibSession {
|
|
|
|
static let columnsRelatedToGroupMembers: [ColumnExpression] = [
|
|
|
|
GroupMember.Columns.role,
|
|
|
|
GroupMember.Columns.roleStatus
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Incoming Changes
|
|
|
|
|
|
|
|
internal extension LibSessionCacheType {
|
|
|
|
func handleGroupMembersUpdate(
|
|
|
|
_ db: Database,
|
|
|
|
in config: LibSession.Config?,
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
serverTimestampMs: Int64
|
|
|
|
) throws {
|
|
|
|
guard configNeedsDump(config) else { return }
|
|
|
|
guard case .object(let conf) = config else { throw LibSessionError.invalidConfigObject }
|
|
|
|
|
|
|
|
// Get the two member sets
|
|
|
|
let userSessionId: SessionId = dependencies[cache: .general].sessionId
|
|
|
|
let updatedMembers: Set<GroupMember> = try LibSession.extractMembers(from: conf, groupSessionId: groupSessionId)
|
|
|
|
let existingMembers: Set<GroupMember> = (try? GroupMember
|
|
|
|
.filter(GroupMember.Columns.groupId == groupSessionId.hexString)
|
|
|
|
.fetchSet(db))
|
|
|
|
.defaulting(to: [])
|
|
|
|
let updatedStandardMemberIds: Set<String> = updatedMembers
|
|
|
|
.filter { $0.role == .standard }
|
|
|
|
.map { $0.profileId }
|
|
|
|
.asSet()
|
|
|
|
let updatedAdminMemberIds: Set<String> = updatedMembers
|
|
|
|
.filter { $0.role == .admin }
|
|
|
|
.map { $0.profileId }
|
|
|
|
.asSet()
|
|
|
|
|
|
|
|
// Add in any new members and remove any removed members
|
|
|
|
try updatedMembers
|
|
|
|
.subtracting(existingMembers)
|
|
|
|
.forEach { try $0.upsert(db) }
|
|
|
|
|
|
|
|
try GroupMember
|
|
|
|
.filter(GroupMember.Columns.groupId == groupSessionId.hexString)
|
|
|
|
.filter(
|
|
|
|
(
|
|
|
|
GroupMember.Columns.role == GroupMember.Role.standard &&
|
|
|
|
!updatedStandardMemberIds.contains(GroupMember.Columns.profileId)
|
|
|
|
) || (
|
|
|
|
GroupMember.Columns.role == GroupMember.Role.admin &&
|
|
|
|
!updatedAdminMemberIds.contains(GroupMember.Columns.profileId)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.deleteAll(db)
|
|
|
|
|
|
|
|
// Schedule a job to process the removals
|
|
|
|
if (try? LibSession.extractPendingRemovals(from: conf, groupSessionId: groupSessionId))?.isEmpty == false {
|
|
|
|
dependencies[singleton: .jobRunner].add(
|
|
|
|
db,
|
|
|
|
job: Job(
|
|
|
|
variant: .processPendingGroupMemberRemovals,
|
|
|
|
threadId: groupSessionId.hexString,
|
|
|
|
details: ProcessPendingGroupMemberRemovalsJob.Details(
|
|
|
|
changeTimestampMs: serverTimestampMs
|
|
|
|
)
|
|
|
|
),
|
|
|
|
canStartJob: true
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the current user is an admin but doesn't have the 'accepted' status then update it now
|
|
|
|
let currentMemberIsNewAdmin: Bool = updatedMembers.contains { member in
|
|
|
|
member.profileId == userSessionId.hexString &&
|
|
|
|
member.role == .admin &&
|
|
|
|
member.roleStatus != .accepted
|
|
|
|
}
|
|
|
|
if currentMemberIsNewAdmin {
|
|
|
|
try GroupMember
|
|
|
|
.filter(GroupMember.Columns.profileId == userSessionId.hexString)
|
|
|
|
.filter(GroupMember.Columns.groupId == groupSessionId.hexString)
|
|
|
|
.updateAllAndConfig(
|
|
|
|
db,
|
|
|
|
GroupMember.Columns.roleStatus.set(to: GroupMember.RoleStatus.accepted),
|
|
|
|
calledFromConfig: .groupMembers,
|
|
|
|
using: dependencies
|
|
|
|
)
|
|
|
|
try LibSession.updateMemberStatus(
|
|
|
|
memberId: userSessionId.hexString,
|
|
|
|
role: .admin,
|
|
|
|
status: .accepted,
|
|
|
|
in: config
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there were members then also extract and update the profile information for the members
|
|
|
|
// if we don't have newer data locally
|
|
|
|
guard !updatedMembers.isEmpty else { return }
|
|
|
|
|
|
|
|
let groupProfiles: Set<Profile>? = try? LibSession.extractProfiles(
|
|
|
|
from: conf,
|
|
|
|
groupSessionId: groupSessionId,
|
|
|
|
serverTimestampMs: serverTimestampMs
|
|
|
|
)
|
|
|
|
|
|
|
|
groupProfiles?.forEach { profile in
|
|
|
|
try? Profile.updateIfNeeded(
|
|
|
|
db,
|
|
|
|
publicKey: profile.id,
|
|
|
|
displayNameUpdate: .contactUpdate(profile.name),
|
|
|
|
displayPictureUpdate: {
|
|
|
|
guard
|
|
|
|
let profilePictureUrl: String = profile.profilePictureUrl,
|
|
|
|
let profileKey: Data = profile.profileEncryptionKey
|
|
|
|
else { return .none }
|
|
|
|
|
|
|
|
return .contactUpdateTo(
|
|
|
|
url: profilePictureUrl,
|
|
|
|
key: profileKey,
|
|
|
|
fileName: nil
|
|
|
|
)
|
|
|
|
}(),
|
|
|
|
sentTimestamp: TimeInterval(Double(serverTimestampMs) * 1000),
|
|
|
|
calledFromConfig: .groupMembers,
|
|
|
|
using: dependencies
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Outgoing Changes
|
|
|
|
|
|
|
|
internal extension LibSession {
|
|
|
|
static func getMembers(
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws -> Set<GroupMember> {
|
|
|
|
return try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
guard case .object(let conf) = cache.config(for: .groupMembers, sessionId: groupSessionId) else {
|
|
|
|
throw LibSessionError.invalidConfigObject
|
|
|
|
}
|
|
|
|
|
|
|
|
return try extractMembers(
|
|
|
|
from: conf,
|
|
|
|
groupSessionId: groupSessionId
|
|
|
|
)
|
|
|
|
} ?? { throw LibSessionError.failedToRetrieveConfigData }()
|
|
|
|
}
|
|
|
|
|
|
|
|
static func getPendingMemberRemovals(
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws -> [String: Bool] {
|
|
|
|
return try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
guard case .object(let conf) = cache.config(for: .groupMembers, sessionId: groupSessionId) else {
|
|
|
|
throw LibSessionError.invalidConfigObject
|
|
|
|
}
|
|
|
|
|
|
|
|
return try extractPendingRemovals(
|
|
|
|
from: conf,
|
|
|
|
groupSessionId: groupSessionId
|
|
|
|
)
|
|
|
|
} ?? { throw LibSessionError.failedToRetrieveConfigData }()
|
|
|
|
}
|
|
|
|
|
|
|
|
static func addMembers(
|
|
|
|
_ db: Database,
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
members: [(id: String, profile: Profile?)],
|
|
|
|
allowAccessToHistoricMessages: Bool,
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws {
|
|
|
|
try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
try cache.performAndPushChange(db, for: .groupMembers, sessionId: groupSessionId) { config in
|
|
|
|
guard case .object(let conf) = config else { throw LibSessionError.invalidConfigObject }
|
|
|
|
|
|
|
|
try members.forEach { memberId, profile in
|
|
|
|
var cMemberId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
|
|
|
|
var member: config_group_member = config_group_member()
|
|
|
|
|
|
|
|
guard groups_members_get_or_construct(conf, &member, &cMemberId) else {
|
|
|
|
throw LibSessionError(
|
|
|
|
conf,
|
|
|
|
fallbackError: .getOrConstructFailedUnexpectedly,
|
|
|
|
logMessage: "Failed to add member to group: \(groupSessionId), error"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't override the existing name with an empty one
|
|
|
|
if let memberName: String = profile?.name, !memberName.isEmpty {
|
|
|
|
member.set(\.name, to: memberName)
|
|
|
|
}
|
|
|
|
|
|
|
|
if
|
|
|
|
let picUrl: String = profile?.profilePictureUrl,
|
|
|
|
let picKey: Data = profile?.profileEncryptionKey,
|
|
|
|
!picUrl.isEmpty,
|
|
|
|
picKey.count == DisplayPictureManager.aes256KeyByteLength
|
|
|
|
{
|
|
|
|
member.set(\.profile_pic.url, to: picUrl)
|
|
|
|
member.set(\.profile_pic.key, to: picKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
member.set(\.invited, to: GroupMember.RoleStatus.notSentYet.libSessionValue)
|
|
|
|
member.set(\.supplement, to: allowAccessToHistoricMessages)
|
|
|
|
groups_members_set(conf, &member)
|
|
|
|
try LibSessionError.throwIfNeeded(conf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static func updateMemberStatus(
|
|
|
|
_ db: Database,
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
memberId: String,
|
|
|
|
role: GroupMember.Role,
|
|
|
|
status: GroupMember.RoleStatus,
|
|
|
|
profile: Profile?,
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws {
|
|
|
|
try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
try cache.performAndPushChange(db, for: .groupMembers, sessionId: groupSessionId) { config in
|
|
|
|
try LibSession.updateMemberStatus(memberId: memberId, role: role, status: status, in: config)
|
|
|
|
try LibSession.updateMemberProfile(memberId: memberId, profile: profile, in: config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static func updateMemberStatus(
|
|
|
|
memberId: String,
|
|
|
|
role: GroupMember.Role,
|
|
|
|
status: GroupMember.RoleStatus,
|
|
|
|
in config: Config?
|
|
|
|
) throws {
|
|
|
|
guard case .object(let conf) = config else { throw LibSessionError.invalidConfigObject }
|
|
|
|
|
|
|
|
// Only update members if they already exist in the group
|
|
|
|
var cMemberId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
|
|
|
|
var groupMember: config_group_member = config_group_member()
|
|
|
|
|
|
|
|
// If the member doesn't exist then do nothing
|
|
|
|
guard groups_members_get(conf, &groupMember, &cMemberId) else { return }
|
|
|
|
|
|
|
|
switch role {
|
|
|
|
case .standard: groupMember.invited = status.libSessionValue
|
|
|
|
case .admin:
|
|
|
|
groupMember.admin = (groupMember.admin || status == .accepted)
|
|
|
|
groupMember.promoted = status.libSessionValue
|
|
|
|
|
|
|
|
default: break
|
|
|
|
}
|
|
|
|
|
|
|
|
groups_members_set(conf, &groupMember)
|
|
|
|
try LibSessionError.throwIfNeeded(conf)
|
|
|
|
}
|
|
|
|
|
|
|
|
static func updateMemberProfile(
|
|
|
|
_ db: Database,
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
memberId: String,
|
|
|
|
profile: Profile?,
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws {
|
|
|
|
try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
try cache.performAndPushChange(db, for: .groupMembers, sessionId: groupSessionId) { config in
|
|
|
|
try LibSession.updateMemberProfile(memberId: memberId, profile: profile, in: config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static func updateMemberProfile(
|
|
|
|
memberId: String,
|
|
|
|
profile: Profile?,
|
|
|
|
in config: Config?
|
|
|
|
) throws {
|
|
|
|
guard let profile: Profile = profile else { return }
|
|
|
|
guard case .object(let conf) = config else { throw LibSessionError.invalidConfigObject }
|
|
|
|
|
|
|
|
// Only update members if they already exist in the group
|
|
|
|
var cMemberId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
|
|
|
|
var groupMember: config_group_member = config_group_member()
|
|
|
|
|
|
|
|
// If the member doesn't exist then do nothing
|
|
|
|
guard groups_members_get(conf, &groupMember, &cMemberId) else { return }
|
|
|
|
|
|
|
|
groupMember.set(\.name, to: profile.name)
|
|
|
|
|
|
|
|
if profile.profilePictureUrl != nil && profile.profileEncryptionKey != nil {
|
|
|
|
groupMember.set(\.profile_pic.url, to: profile.profilePictureUrl)
|
|
|
|
groupMember.set(\.profile_pic.key, to: profile.profileEncryptionKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
groups_members_set(conf, &groupMember)
|
|
|
|
try? LibSessionError.throwIfNeeded(conf)
|
|
|
|
}
|
|
|
|
|
|
|
|
static func flagMembersForRemoval(
|
|
|
|
_ db: Database,
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
memberIds: Set<String>,
|
|
|
|
removeMessages: Bool,
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws {
|
|
|
|
try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
try cache.performAndPushChange(db, for: .groupMembers, sessionId: groupSessionId) { config in
|
|
|
|
guard case .object(let conf) = config else { throw LibSessionError.invalidConfigObject }
|
|
|
|
|
|
|
|
try memberIds.forEach { memberId in
|
|
|
|
// Only update members if they already exist in the group
|
|
|
|
var cMemberId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
|
|
|
|
var groupMember: config_group_member = config_group_member()
|
|
|
|
|
|
|
|
guard groups_members_get(conf, &groupMember, &cMemberId) else { return }
|
|
|
|
|
|
|
|
groupMember.removed = (removeMessages ? 2 : 1)
|
|
|
|
groups_members_set(conf, &groupMember)
|
|
|
|
try LibSessionError.throwIfNeeded(conf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static func removeMembers(
|
|
|
|
_ db: Database,
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
memberIds: Set<String>,
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws {
|
|
|
|
try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
try cache.performAndPushChange(db, for: .groupMembers, sessionId: groupSessionId) { config in
|
|
|
|
guard case .object(let conf) = config else { throw LibSessionError.invalidConfigObject }
|
|
|
|
|
|
|
|
try memberIds.forEach { memberId in
|
|
|
|
var cMemberId: [CChar] = try memberId.cString(using: .utf8) ?? { throw LibSessionError.invalidCConversion }()
|
|
|
|
|
|
|
|
groups_members_erase(conf, &cMemberId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static func updatingGroupMembers<T>(
|
|
|
|
_ db: Database,
|
|
|
|
_ updated: [T],
|
|
|
|
using dependencies: Dependencies
|
|
|
|
) throws -> [T] {
|
|
|
|
guard let updatedMembers: [GroupMember] = updated as? [GroupMember] else { throw StorageError.generic }
|
|
|
|
|
|
|
|
// Exclude legacy groups as they aren't managed via SessionUtil
|
|
|
|
let targetMembers: [GroupMember] = updatedMembers
|
|
|
|
.filter { (try? SessionId(from: $0.groupId))?.prefix == .group }
|
|
|
|
|
|
|
|
// If we only updated the current user contact then no need to continue
|
|
|
|
guard
|
|
|
|
!targetMembers.isEmpty,
|
|
|
|
let groupSessionId: SessionId = targetMembers.first.map({ try? SessionId(from: $0.groupId) }),
|
|
|
|
groupSessionId.prefix == .group
|
|
|
|
else { return updated }
|
|
|
|
|
|
|
|
// Loop through each of the groups and update their settings
|
|
|
|
try targetMembers.forEach { member in
|
|
|
|
try dependencies.mutate(cache: .libSession) { cache in
|
|
|
|
try cache.performAndPushChange(db, for: .groupMembers, sessionId: groupSessionId) { config in
|
|
|
|
guard case .object(let conf) = config else { throw LibSessionError.invalidConfigObject }
|
|
|
|
|
|
|
|
// Only update members if they already exist in the group
|
|
|
|
var cMemberId: [CChar] = try member.profileId.cString(using: .utf8) ?? {
|
|
|
|
throw LibSessionError.invalidCConversion
|
|
|
|
}()
|
|
|
|
var groupMember: config_group_member = config_group_member()
|
|
|
|
|
|
|
|
guard groups_members_get(conf, &groupMember, &cMemberId) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the role and status to match
|
|
|
|
switch member.role {
|
|
|
|
case .admin:
|
|
|
|
groupMember.admin = true
|
|
|
|
groupMember.invited = 0
|
|
|
|
groupMember.promoted = member.roleStatus.libSessionValue
|
|
|
|
|
|
|
|
default:
|
|
|
|
groupMember.admin = false
|
|
|
|
groupMember.invited = member.roleStatus.libSessionValue
|
|
|
|
groupMember.promoted = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
groups_members_set(conf, &groupMember)
|
|
|
|
try LibSessionError.throwIfNeeded(conf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return updated
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - MemberData
|
|
|
|
|
|
|
|
private struct MemberData {
|
|
|
|
let memberId: String
|
|
|
|
let profile: Profile?
|
|
|
|
let admin: Bool
|
|
|
|
let invited: Int32
|
|
|
|
let promoted: Int32
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Convenience
|
|
|
|
|
|
|
|
internal extension LibSession {
|
|
|
|
static func extractMembers(
|
|
|
|
from conf: UnsafeMutablePointer<config_object>?,
|
|
|
|
groupSessionId: SessionId
|
|
|
|
) throws -> Set<GroupMember> {
|
|
|
|
var infiniteLoopGuard: Int = 0
|
|
|
|
var result: [GroupMember] = []
|
|
|
|
var member: config_group_member = config_group_member()
|
|
|
|
let membersIterator: UnsafeMutablePointer<groups_members_iterator> = groups_members_iterator_new(conf)
|
|
|
|
|
|
|
|
while !groups_members_iterator_done(membersIterator, &member) {
|
|
|
|
try LibSession.checkLoopLimitReached(&infiniteLoopGuard, for: .groupMembers)
|
|
|
|
|
|
|
|
// Ignore members pending removal
|
|
|
|
guard member.removed == 0 else { continue }
|
|
|
|
|
|
|
|
result.append(
|
|
|
|
GroupMember(
|
|
|
|
groupId: groupSessionId.hexString,
|
|
|
|
profileId: member.get(\.session_id),
|
|
|
|
role: (member.admin || (member.promoted > 0) ? .admin : .standard),
|
|
|
|
roleStatus: {
|
|
|
|
switch (member.invited, member.promoted, member.admin) {
|
|
|
|
case (2, _, _), (_, 2, _): return .failed // Explicitly failed
|
|
|
|
case (3, _, _), (_, 3, _): return .notSentYet // Explicitly notSentYet
|
|
|
|
case (1..., _, _), (_, 1..., _): return .pending // Pending if not one of the above
|
|
|
|
default: return .accepted // Otherwise it's accepted
|
|
|
|
}
|
|
|
|
}(),
|
|
|
|
isHidden: false
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
groups_members_iterator_advance(membersIterator)
|
|
|
|
}
|
|
|
|
groups_members_iterator_free(membersIterator) // Need to free the iterator
|
|
|
|
|
|
|
|
return result.asSet()
|
|
|
|
}
|
|
|
|
|
|
|
|
static func extractPendingRemovals(
|
|
|
|
from conf: UnsafeMutablePointer<config_object>?,
|
|
|
|
groupSessionId: SessionId
|
|
|
|
) throws -> [String: Bool] {
|
|
|
|
var infiniteLoopGuard: Int = 0
|
|
|
|
var result: [String: Bool] = [:]
|
|
|
|
var member: config_group_member = config_group_member()
|
|
|
|
let membersIterator: UnsafeMutablePointer<groups_members_iterator> = groups_members_iterator_new(conf)
|
|
|
|
|
|
|
|
while !groups_members_iterator_done(membersIterator, &member) {
|
|
|
|
try LibSession.checkLoopLimitReached(&infiniteLoopGuard, for: .groupMembers)
|
|
|
|
|
|
|
|
guard member.removed > 0 else {
|
|
|
|
groups_members_iterator_advance(membersIterator)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
result[member.get(\.session_id)] = (member.removed == 2)
|
|
|
|
groups_members_iterator_advance(membersIterator)
|
|
|
|
}
|
|
|
|
groups_members_iterator_free(membersIterator) // Need to free the iterator
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
static func extractProfiles(
|
|
|
|
from conf: UnsafeMutablePointer<config_object>?,
|
|
|
|
groupSessionId: SessionId,
|
|
|
|
serverTimestampMs: Int64
|
|
|
|
) throws -> Set<Profile> {
|
|
|
|
var infiniteLoopGuard: Int = 0
|
|
|
|
var result: [Profile] = []
|
|
|
|
var member: config_group_member = config_group_member()
|
|
|
|
let membersIterator: UnsafeMutablePointer<groups_members_iterator> = groups_members_iterator_new(conf)
|
|
|
|
|
|
|
|
while !groups_members_iterator_done(membersIterator, &member) {
|
|
|
|
try LibSession.checkLoopLimitReached(&infiniteLoopGuard, for: .groupMembers)
|
|
|
|
|
|
|
|
// Ignore members pending removal
|
|
|
|
guard member.removed == 0 else { continue }
|
|
|
|
|
|
|
|
result.append(
|
|
|
|
Profile(
|
|
|
|
id: member.get(\.session_id),
|
|
|
|
name: member.get(\.name),
|
|
|
|
lastNameUpdate: TimeInterval(Double(serverTimestampMs) / 1000),
|
|
|
|
nickname: nil,
|
|
|
|
profilePictureUrl: member.get(\.profile_pic.url, nullIfEmpty: true),
|
|
|
|
profileEncryptionKey: (member.get(\.profile_pic.url, nullIfEmpty: true) == nil ? nil :
|
|
|
|
member.get(\.profile_pic.key)
|
|
|
|
),
|
|
|
|
lastProfilePictureUpdate: TimeInterval(Double(serverTimestampMs) / 1000),
|
|
|
|
lastBlocksCommunityMessageRequests: nil
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
groups_members_iterator_advance(membersIterator)
|
|
|
|
}
|
|
|
|
groups_members_iterator_free(membersIterator) // Need to free the iterator
|
|
|
|
|
|
|
|
return result.asSet()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate extension GroupMember.RoleStatus {
|
|
|
|
var libSessionValue: Int32 {
|
|
|
|
switch self {
|
|
|
|
case .accepted: return 0
|
|
|
|
case .pending: return Int32(INVITE_SENT.rawValue)
|
|
|
|
case .failed: return Int32(INVITE_FAILED.rawValue)
|
|
|
|
case .notSentYet: return Int32(INVITE_NOT_SENT.rawValue)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|