mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			271 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Matlab
		
	
		
		
			
		
	
	
			271 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Matlab
		
	
| 
								 
											5 years ago
										 
									 | 
							
								//
							 | 
						||
| 
								 | 
							
								//  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#import "TSGroupThread.h"
							 | 
						||
| 
								 | 
							
								#import "TSAttachmentStream.h"
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								#import <SignalCoreKit/NSData+OWS.h>
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								#import <YapDatabase/YapDatabase.h>
							 | 
						||
| 
								 | 
							
								#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
							 | 
						||
| 
								 | 
							
								#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
							 | 
						||
| 
								 | 
							
								#import <Curve25519Kit/Curve25519.h>
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 | 
							
								NS_ASSUME_NONNULL_BEGIN
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								NSString *const TSGroupThreadAvatarChangedNotification = @"TSGroupThreadAvatarChangedNotification";
							 | 
						||
| 
								 | 
							
								NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_NotificationKey_UniqueId";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@implementation TSGroupThread
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#define TSGroupThreadPrefix @"g"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (instancetype)initWithGroupModel:(TSGroupModel *)groupModel
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    NSString *uniqueIdentifier = [[self class] threadIdFromGroupId:groupModel.groupId];
							 | 
						||
| 
								 | 
							
								    self = [super initWithUniqueId:uniqueIdentifier];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!self) {
							 | 
						||
| 
								 | 
							
								        return self;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _groupModel = groupModel;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return self;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (instancetype)initWithGroupId:(NSData *)groupId groupType:(GroupType)groupType
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    NSString *localNumber = [TSAccountManager localNumber];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:nil
							 | 
						||
| 
								 | 
							
								                                                         memberIds:@[ localNumber ]
							 | 
						||
| 
								 | 
							
								                                                             image:nil
							 | 
						||
| 
								 | 
							
								                                                           groupId:groupId
							 | 
						||
| 
								 | 
							
								                                                         groupType:groupType
							 | 
						||
| 
								 | 
							
								                                                          adminIds:@[ localNumber ]];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self = [self initWithGroupModel:groupModel];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!self) {
							 | 
						||
| 
								 | 
							
								        return self;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return self;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (nullable instancetype)threadWithGroupId:(NSData *)groupId transaction:(YapDatabaseReadTransaction *)transaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    return [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId] transaction:transaction];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (instancetype)getOrCreateThreadWithGroupId:(NSData *)groupId
							 | 
						||
| 
								 | 
							
								                                   groupType:(GroupType)groupType
							 | 
						||
| 
								 | 
							
								                                 transaction:(YapDatabaseReadWriteTransaction *)transaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    TSGroupThread *thread = [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId] transaction:transaction];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!thread) {
							 | 
						||
| 
								 | 
							
								        thread = [[self alloc] initWithGroupId:groupId groupType:groupType];
							 | 
						||
| 
								 | 
							
								        [thread saveWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return thread;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (instancetype)getOrCreateThreadWithGroupId:(NSData *)groupId groupType:(GroupType)groupType
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    __block TSGroupThread *thread;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
							 | 
						||
| 
								 | 
							
								        thread = [self getOrCreateThreadWithGroupId:groupId groupType:groupType transaction:transaction];
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return thread;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel
							 | 
						||
| 
								 | 
							
								                                    transaction:(YapDatabaseReadWriteTransaction *)transaction {
							 | 
						||
| 
								 | 
							
								    TSGroupThread *thread =
							 | 
						||
| 
								 | 
							
								        [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupModel.groupId] transaction:transaction];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!thread) {
							 | 
						||
| 
								 | 
							
								        thread = [[TSGroupThread alloc] initWithGroupModel:groupModel];
							 | 
						||
| 
								 | 
							
								        [thread saveWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return thread;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    __block TSGroupThread *thread;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
							 | 
						||
| 
								 | 
							
								        thread = [self getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return thread;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (NSString *)threadIdFromGroupId:(NSData *)groupId
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    return [TSGroupThreadPrefix stringByAppendingString:[[LKGroupUtilities getDecodedGroupIDAsData:groupId] base64EncodedString]];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (NSData *)groupIdFromThreadId:(NSString *)threadId
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (NSArray<NSString *> *)recipientIdentifiers
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    NSMutableArray<NSString *> *groupMemberIds = [self.groupModel.groupMemberIds mutableCopy];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (groupMemberIds == nil) {
							 | 
						||
| 
								 | 
							
								        return @[];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [groupMemberIds removeObject:TSAccountManager.localNumber];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return [groupMemberIds copy];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// @returns all threads to which the recipient is a member.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// @note If this becomes a hotspot we can extract into a YapDB View.
							 | 
						||
| 
								 | 
							
								// As is, the number of groups should be small (dozens, *maybe* hundreds), and we only enumerate them upon SN changes.
							 | 
						||
| 
								 | 
							
								+ (NSArray<TSGroupThread *> *)groupThreadsWithRecipientId:(NSString *)recipientId
							 | 
						||
| 
								 | 
							
								                                              transaction:(YapDatabaseReadWriteTransaction *)transaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    NSMutableArray<TSGroupThread *> *groupThreads = [NSMutableArray new];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [self enumerateCollectionObjectsWithTransaction:transaction usingBlock:^(id obj, BOOL *stop) {
							 | 
						||
| 
								 | 
							
								        if ([obj isKindOfClass:[TSGroupThread class]]) {
							 | 
						||
| 
								 | 
							
								            TSGroupThread *groupThread = (TSGroupThread *)obj;
							 | 
						||
| 
								 | 
							
								            if ([groupThread.groupModel.groupMemberIds containsObject:recipientId]) {
							 | 
						||
| 
								 | 
							
								                [groupThreads addObject:groupThread];
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return [groupThreads copy];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (BOOL)isGroupThread
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    return true;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								- (BOOL)isOpenGroup
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								{
							 | 
						||
| 
								 | 
							
								    return (self.groupModel.groupType == openGroup);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								- (BOOL)isCurrentUserMemberInGroup
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								{
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								    NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
							 | 
						||
| 
								 | 
							
								    return [self isUserMemberInGroup:userPublicKey];
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								- (BOOL)isUserMemberInGroup:(NSString *)publicKey
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								{
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								    if (publicKey == nil) { return NO; }
							 | 
						||
| 
								 | 
							
								    return [self.groupModel.groupMemberIds containsObject:publicKey];
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								- (BOOL)isUserAdminInGroup:(NSString *)publicKey
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								{
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								    if (publicKey == nil) { return NO; }
							 | 
						||
| 
								 | 
							
								    return [self.groupModel.groupAdminIds containsObject:publicKey];
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (NSString *)name
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    // TODO sometimes groupName is set to the empty string. I'm hesitent to change
							 | 
						||
| 
								 | 
							
								    // the semantics here until we have time to thouroughly test the fallout.
							 | 
						||
| 
								 | 
							
								    // Instead, see the `groupNameOrDefault` which is appropriate for use when displaying
							 | 
						||
| 
								 | 
							
								    // text corresponding to a group.
							 | 
						||
| 
								 | 
							
								    return self.groupModel.groupName ?: self.class.defaultGroupName;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								+ (NSString *)defaultGroupName
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    return NSLocalizedString(@"NEW_GROUP_DEFAULT_TITLE", @"");
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (void)setGroupModel:(TSGroupModel *)newGroupModel withTransaction:(YapDatabaseReadWriteTransaction *)transaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    self.groupModel = newGroupModel;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [self saveWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [transaction addCompletionQueue:dispatch_get_main_queue() completionBlock:^{
							 | 
						||
| 
								 | 
							
								        [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.groupThreadUpdated object:self.uniqueId];
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (void)leaveGroupWithSneakyTransaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
							 | 
						||
| 
								 | 
							
								        [self leaveGroupWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (void)leaveGroupWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								    NSMutableSet<NSString *> *newGroupMemberIDs = [NSMutableSet setWithArray:self.groupModel.groupMemberIds];
							 | 
						||
| 
								 | 
							
								    NSString *userPublicKey = TSAccountManager.localNumber;
							 | 
						||
| 
								 | 
							
								    if (userPublicKey == nil) { return; }
							 | 
						||
| 
								 | 
							
								    [newGroupMemberIDs removeObject:userPublicKey];
							 | 
						||
| 
								 | 
							
								    self.groupModel.groupMemberIds = newGroupMemberIDs.allObjects;
							 | 
						||
| 
								 | 
							
								    [self saveWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								    [transaction addCompletionQueue:dispatch_get_main_queue() completionBlock:^{
							 | 
						||
| 
								 | 
							
								        [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.groupThreadUpdated object:self.uniqueId];
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 
											5 years ago
										 
									 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (void)softDeleteGroupThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    [self removeAllThreadInteractionsWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								    self.shouldThreadBeVisible = NO;
							 | 
						||
| 
								 | 
							
								    [self saveWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#pragma mark - Avatar
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
							 | 
						||
| 
								 | 
							
								        [self updateAvatarWithAttachmentStream:attachmentStream transaction:transaction];
							 | 
						||
| 
								 | 
							
								    }];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream
							 | 
						||
| 
								 | 
							
								                             transaction:(YapDatabaseReadWriteTransaction *)transaction
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    self.groupModel.groupImage = [attachmentStream thumbnailImageSmallSync];
							 | 
						||
| 
								 | 
							
								    [self saveWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [transaction addCompletionQueue:nil
							 | 
						||
| 
								 | 
							
								                    completionBlock:^{
							 | 
						||
| 
								 | 
							
								                        [self fireAvatarChangedNotification];
							 | 
						||
| 
								 | 
							
								                    }];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Avatars are stored directly in the database, so there's no need
							 | 
						||
| 
								 | 
							
								    // to keep the attachment around after assigning the image.
							 | 
						||
| 
								 | 
							
								    [attachmentStream removeWithTransaction:transaction];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								- (void)fireAvatarChangedNotification
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    NSDictionary *userInfo = @{ TSGroupThread_NotificationKey_UniqueId : self.uniqueId };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    [[NSNotificationCenter defaultCenter] postNotificationName:TSGroupThreadAvatarChangedNotification
							 | 
						||
| 
								 | 
							
								                                                        object:self.uniqueId
							 | 
						||
| 
								 | 
							
								                                                      userInfo:userInfo];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								NS_ASSUME_NONNULL_END
							 |