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.
		
		
		
		
		
			
		
			
				
	
	
		
			328 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			328 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Objective-C
		
	
| //
 | |
| //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "OWSBlockingManager.h"
 | |
| #import "AppContext.h"
 | |
| #import "AppReadiness.h"
 | |
| #import "NSNotificationCenter+OWS.h"
 | |
| #import "OWSPrimaryStorage.h"
 | |
| #import "SSKEnvironment.h"
 | |
| #import "TSContactThread.h"
 | |
| #import "TSGroupThread.h"
 | |
| #import "YapDatabaseConnection+OWS.h"
 | |
| #import <SessionMessagingKit/SessionMessagingKit-Swift.h>
 | |
| 
 | |
| NS_ASSUME_NONNULL_BEGIN
 | |
| 
 | |
| NSString *const kNSNotificationName_BlockListDidChange = @"kNSNotificationName_BlockListDidChange";
 | |
| 
 | |
| NSString *const kOWSBlockingManager_BlockListCollection = @"kOWSBlockingManager_BlockedPhoneNumbersCollection";
 | |
| 
 | |
| // These keys are used to persist the current local "block list" state.
 | |
| NSString *const kOWSBlockingManager_BlockedPhoneNumbersKey = @"kOWSBlockingManager_BlockedPhoneNumbersKey";
 | |
| NSString *const kOWSBlockingManager_BlockedGroupMapKey = @"kOWSBlockingManager_BlockedGroupMapKey";
 | |
| 
 | |
| // These keys are used to persist the most recently synced remote "block list" state.
 | |
| NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockingManager_SyncedBlockedPhoneNumbersKey";
 | |
| NSString *const kOWSBlockingManager_SyncedBlockedGroupIdsKey = @"kOWSBlockingManager_SyncedBlockedGroupIdsKey";
 | |
| 
 | |
| @interface OWSBlockingManager ()
 | |
| 
 | |
| @property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
 | |
| 
 | |
| // We don't store the phone numbers as instances of PhoneNumber to avoid
 | |
| // consistency issues between clients, but these should all be valid e164
 | |
| // phone numbers.
 | |
| @property (atomic, readonly) NSMutableSet<NSString *> *blockedPhoneNumberSet;
 | |
| @property (atomic, readonly) NSMutableDictionary<NSData *, TSGroupModel *> *blockedGroupMap;
 | |
| 
 | |
| @end
 | |
| 
 | |
| #pragma mark -
 | |
| 
 | |
| @implementation OWSBlockingManager
 | |
| 
 | |
| + (instancetype)sharedManager
 | |
| {
 | |
|     return SSKEnvironment.shared.blockingManager;
 | |
| }
 | |
| 
 | |
| - (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
 | |
| {
 | |
|     self = [super init];
 | |
| 
 | |
|     if (!self) {
 | |
|         return self;
 | |
|     }
 | |
| 
 | |
|     _dbConnection = primaryStorage.newDatabaseConnection;
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| - (void)dealloc
 | |
| {
 | |
|     [[NSNotificationCenter defaultCenter] removeObserver:self];
 | |
| }
 | |
| 
 | |
| - (void)observeNotifications
 | |
| {
 | |
|     [[NSNotificationCenter defaultCenter] addObserver:self
 | |
|                                              selector:@selector(applicationDidBecomeActive:)
 | |
|                                                  name:OWSApplicationDidBecomeActiveNotification
 | |
|                                                object:nil];
 | |
| }
 | |
| 
 | |
| #pragma mark -
 | |
| 
 | |
| - (BOOL)isThreadBlocked:(TSThread *)thread
 | |
| {
 | |
|     if ([thread isKindOfClass:[TSContactThread class]]) {
 | |
|         TSContactThread *contactThread = (TSContactThread *)thread;
 | |
|         return [self isRecipientIdBlocked:contactThread.contactSessionID];
 | |
|     } else if ([thread isKindOfClass:[TSGroupThread class]]) {
 | |
|         TSGroupThread *groupThread = (TSGroupThread *)thread;
 | |
|         return [self isGroupIdBlocked:groupThread.groupModel.groupId];
 | |
|     } else {
 | |
|         return NO;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #pragma mark - Contact Blocking
 | |
| 
 | |
| - (void)addBlockedPhoneNumber:(NSString *)phoneNumber
 | |
| {
 | |
|     @synchronized(self)
 | |
|     {
 | |
|         [self ensureLazyInitialization];
 | |
| 
 | |
|         if ([_blockedPhoneNumberSet containsObject:phoneNumber]) {
 | |
|             // Ignore redundant changes.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         [_blockedPhoneNumberSet addObject:phoneNumber];
 | |
|     }
 | |
| 
 | |
|     [self handleUpdate];
 | |
| }
 | |
| 
 | |
| - (void)removeBlockedPhoneNumber:(NSString *)phoneNumber
 | |
| {
 | |
|     @synchronized(self)
 | |
|     {
 | |
|         [self ensureLazyInitialization];
 | |
| 
 | |
|         if (![_blockedPhoneNumberSet containsObject:phoneNumber]) {
 | |
|             // Ignore redundant changes.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         [_blockedPhoneNumberSet removeObject:phoneNumber];
 | |
|     }
 | |
| 
 | |
|     [self handleUpdate];
 | |
| }
 | |
| 
 | |
| - (void)setBlockedPhoneNumbers:(NSArray<NSString *> *)blockedPhoneNumbers sendSyncMessage:(BOOL)sendSyncMessage
 | |
| {
 | |
|     @synchronized(self)
 | |
|     {
 | |
|         [self ensureLazyInitialization];
 | |
| 
 | |
|         NSSet *newSet = [NSSet setWithArray:blockedPhoneNumbers];
 | |
|         if ([_blockedPhoneNumberSet isEqualToSet:newSet]) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         _blockedPhoneNumberSet = [newSet mutableCopy];
 | |
|     }
 | |
| 
 | |
|     [self handleUpdate:sendSyncMessage];
 | |
| }
 | |
| 
 | |
| - (NSArray<NSString *> *)blockedPhoneNumbers
 | |
| {
 | |
|     @synchronized(self)
 | |
|     {
 | |
|         [self ensureLazyInitialization];
 | |
| 
 | |
|         return [_blockedPhoneNumberSet.allObjects sortedArrayUsingSelector:@selector(compare:)];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (BOOL)isRecipientIdBlocked:(NSString *)recipientId
 | |
| {
 | |
|     return [self.blockedPhoneNumbers containsObject:recipientId];
 | |
| }
 | |
| 
 | |
| #pragma mark - Group Blocking
 | |
| 
 | |
| - (NSArray<NSData *> *)blockedGroupIds
 | |
| {
 | |
|     @synchronized(self) {
 | |
|         [self ensureLazyInitialization];
 | |
|         return self.blockedGroupMap.allKeys;
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSArray<TSGroupModel *> *)blockedGroups
 | |
| {
 | |
|     @synchronized(self) {
 | |
|         [self ensureLazyInitialization];
 | |
|         return self.blockedGroupMap.allValues;
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (BOOL)isGroupIdBlocked:(NSData *)groupId
 | |
| {
 | |
|     return self.blockedGroupMap[groupId] != nil;
 | |
| }
 | |
| 
 | |
| - (nullable TSGroupModel *)cachedGroupDetailsWithGroupId:(NSData *)groupId
 | |
| {
 | |
|     @synchronized(self) {
 | |
|         return self.blockedGroupMap[groupId];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (void)addBlockedGroup:(TSGroupModel *)groupModel
 | |
| {
 | |
|     NSData *groupId = groupModel.groupId;
 | |
| 
 | |
|     @synchronized(self) {
 | |
|         [self ensureLazyInitialization];
 | |
| 
 | |
|         if ([self isGroupIdBlocked:groupId]) {
 | |
|             // Ignore redundant changes.
 | |
|             return;
 | |
|         }
 | |
|         self.blockedGroupMap[groupId] = groupModel;
 | |
|     }
 | |
| 
 | |
|     [self handleUpdate];
 | |
| }
 | |
| 
 | |
| - (void)removeBlockedGroupId:(NSData *)groupId
 | |
| {
 | |
|     @synchronized(self) {
 | |
|         [self ensureLazyInitialization];
 | |
| 
 | |
|         if (![self isGroupIdBlocked:groupId]) {
 | |
|             // Ignore redundant changes.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         [self.blockedGroupMap removeObjectForKey:groupId];
 | |
|     }
 | |
| 
 | |
|     [self handleUpdate];
 | |
| }
 | |
| 
 | |
| 
 | |
| #pragma mark - Updates
 | |
| 
 | |
| // This should be called every time the block list changes.
 | |
| 
 | |
| - (void)handleUpdate
 | |
| {
 | |
|     // By default, always send a sync message when the block list changes.
 | |
|     [self handleUpdate:YES];
 | |
| }
 | |
| 
 | |
| // TODO label the `sendSyncMessage` param
 | |
| - (void)handleUpdate:(BOOL)sendSyncMessage
 | |
| {
 | |
|     NSArray<NSString *> *blockedPhoneNumbers = [self blockedPhoneNumbers];
 | |
| 
 | |
|     [self.dbConnection setObject:blockedPhoneNumbers
 | |
|                           forKey:kOWSBlockingManager_BlockedPhoneNumbersKey
 | |
|                     inCollection:kOWSBlockingManager_BlockListCollection];
 | |
| 
 | |
|     NSDictionary *blockedGroupMap;
 | |
|     @synchronized(self) {
 | |
|         blockedGroupMap = [self.blockedGroupMap copy];
 | |
|     }
 | |
|     NSArray<NSData *> *blockedGroupIds = blockedGroupMap.allKeys;
 | |
| 
 | |
|     [self.dbConnection setObject:blockedGroupMap
 | |
|                           forKey:kOWSBlockingManager_BlockedGroupMapKey
 | |
|                     inCollection:kOWSBlockingManager_BlockListCollection];
 | |
| 
 | |
| 
 | |
|     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 | |
|         if (sendSyncMessage) {
 | |
|             
 | |
|         } else {
 | |
|             // If this update came from an incoming block list sync message,
 | |
|             // update the "synced blocked list" state immediately,
 | |
|             // since we're now in sync.
 | |
|             //
 | |
|             // There could be data loss if both clients modify the block list
 | |
|             // at the same time, but:
 | |
|             //
 | |
|             // a) Block list changes will be rare.
 | |
|             // b) Conflicting block list changes will be even rarer.
 | |
|             // c) It's unlikely a user will make conflicting changes on two
 | |
|             //    devices around the same time.
 | |
|             // d) There isn't a good way to avoid this.
 | |
|             [self saveSyncedBlockListWithPhoneNumbers:blockedPhoneNumbers groupIds:blockedGroupIds];
 | |
|         }
 | |
| 
 | |
|         [[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_BlockListDidChange
 | |
|                                                                  object:nil
 | |
|                                                                userInfo:nil];
 | |
|     });
 | |
| }
 | |
| 
 | |
| // This method should only be called from within a synchronized block.
 | |
| - (void)ensureLazyInitialization
 | |
| {
 | |
|     if (_blockedPhoneNumberSet) {
 | |
| 
 | |
|         // already loaded
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     NSArray<NSString *> *blockedPhoneNumbers =
 | |
|         [self.dbConnection objectForKey:kOWSBlockingManager_BlockedPhoneNumbersKey
 | |
|                            inCollection:kOWSBlockingManager_BlockListCollection];
 | |
|     _blockedPhoneNumberSet = [[NSMutableSet alloc] initWithArray:(blockedPhoneNumbers ?: [NSArray new])];
 | |
| 
 | |
|     NSDictionary<NSData *, TSGroupModel *> *storedBlockedGroupMap =
 | |
|         [self.dbConnection objectForKey:kOWSBlockingManager_BlockedGroupMapKey
 | |
|                            inCollection:kOWSBlockingManager_BlockListCollection];
 | |
|     if ([storedBlockedGroupMap isKindOfClass:[NSDictionary class]]) {
 | |
|         _blockedGroupMap = [storedBlockedGroupMap mutableCopy];
 | |
|     } else {
 | |
|         _blockedGroupMap = [NSMutableDictionary new];
 | |
|     }
 | |
| 
 | |
|     [self observeNotifications];
 | |
| }
 | |
| 
 | |
| /// Records the last block list which we successfully synced.
 | |
| - (void)saveSyncedBlockListWithPhoneNumbers:(NSArray<NSString *> *)blockedPhoneNumbers
 | |
|                                    groupIds:(NSArray<NSData *> *)blockedGroupIds
 | |
| {
 | |
|     [self.dbConnection setObject:blockedPhoneNumbers
 | |
|                           forKey:kOWSBlockingManager_SyncedBlockedPhoneNumbersKey
 | |
|                     inCollection:kOWSBlockingManager_BlockListCollection];
 | |
| 
 | |
|     [self.dbConnection setObject:blockedGroupIds
 | |
|                           forKey:kOWSBlockingManager_SyncedBlockedGroupIdsKey
 | |
|                     inCollection:kOWSBlockingManager_BlockListCollection];
 | |
| }
 | |
| 
 | |
| #pragma mark - Notifications
 | |
| 
 | |
| - (void)applicationDidBecomeActive:(NSNotification *)notification
 | |
| {
 | |
|     
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| NS_ASSUME_NONNULL_END
 |