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.
		
		
		
		
		
			
		
			
				
	
	
		
			276 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			276 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Objective-C
		
	
| //
 | |
| //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "TSGroupThread.h"
 | |
| #import "TSAttachmentStream.h"
 | |
| #import <SignalCoreKit/NSData+OWS.h>
 | |
| #import <YapDatabase/YapDatabase.h>
 | |
| #import <SessionMessagingKit/SessionMessagingKit-Swift.h>
 | |
| #import <SessionUtilitiesKit/SessionUtilitiesKit.h>
 | |
| #import <Curve25519Kit/Curve25519.h>
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| - (BOOL)isClosedGroup
 | |
| {
 | |
|     return (self.groupModel.groupType == closedGroup);
 | |
| }
 | |
| 
 | |
| - (BOOL)isOpenGroup
 | |
| {
 | |
|     return (self.groupModel.groupType == openGroup);
 | |
| }
 | |
| 
 | |
| - (BOOL)isCurrentUserMemberInGroup
 | |
| {
 | |
|     NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey];
 | |
|     return [self isUserMemberInGroup:userPublicKey];
 | |
| }
 | |
| 
 | |
| - (BOOL)isUserMemberInGroup:(NSString *)publicKey
 | |
| {
 | |
|     if (publicKey == nil) { return NO; }
 | |
|     return [self.groupModel.groupMemberIds containsObject:publicKey];
 | |
| }
 | |
| 
 | |
| - (BOOL)isUserAdminInGroup:(NSString *)publicKey
 | |
| {
 | |
|     if (publicKey == nil) { return NO; }
 | |
|     return [self.groupModel.groupAdminIds containsObject:publicKey];
 | |
| }
 | |
| 
 | |
| - (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
 | |
| {
 | |
|     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];
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (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
 |