From 448936d156050b9560e382c3a302fe153ca2ad01 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 10 Sep 2018 17:17:51 -0500 Subject: [PATCH] BlockListCache block manager synchronizes on self for coherent read/writes to blocking state across threads, but we want to be able to have performant reads on the main thread. --- Signal.xcodeproj/project.pbxproj | 4 + .../HomeView/HomeViewController.m | 20 +++- SignalMessaging/utils/BlockListCache.swift | 98 +++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 SignalMessaging/utils/BlockListCache.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index be57dca33..c890e4fbc 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -442,6 +442,7 @@ 4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; }; 4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; }; 4C858A52212DC5E1001B45D3 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */; }; + 4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C948FF62146EB4800349F0D /* BlockListCache.swift */; }; 4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; }; 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; }; 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; }; @@ -1131,6 +1132,7 @@ 4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = ""; }; 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalUBSan.supp; sourceTree = ""; }; 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+OWS.swift"; sourceTree = ""; }; + 4C948FF62146EB4800349F0D /* BlockListCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListCache.swift; sourceTree = ""; }; 4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = ""; }; 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = ""; }; @@ -1563,6 +1565,7 @@ B97940261832BD2400BD66CB /* UIUtil.m */, 45F170D51E315310003FC1F2 /* Weak.swift */, 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */, + 4C948FF62146EB4800349F0D /* BlockListCache.swift */, ); path = utils; sourceTree = ""; @@ -3269,6 +3272,7 @@ 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */, 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */, 34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */, + 4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */, 4551DB5A205C562300C8AE75 /* Collection+OWS.swift in Sources */, 34AC09ED211B39B100997B47 /* ContactFieldView.swift in Sources */, 346129AF1FD1F5D900532771 /* SystemContactsFetcher.swift in Sources */, diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index dcb735a7f..c893449e4 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -21,9 +21,9 @@ #import #import #import +#import #import #import -#import #import #import #import @@ -64,7 +64,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations UITableViewDataSource, UIViewControllerPreviewingDelegate, UISearchBarDelegate, - ConversationSearchViewDelegate> + ConversationSearchViewDelegate, + OWSBlockListCacheDelegate> @property (nonatomic) UITableView *tableView; @property (nonatomic) UILabel *emptyBoxLabel; @@ -89,7 +90,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations @property (nonatomic, readonly) AccountManager *accountManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; -@property (nonatomic, readonly) OWSBlockingManager *blockingManager; +@property (nonatomic, readonly) OWSBlockListCache *blocklistCache; // Views @@ -147,7 +148,8 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations _accountManager = SignalApp.sharedApp.accountManager; _contactsManager = [Environment current].contactsManager; _messageSender = [Environment current].messageSender; - _blockingManager = [OWSBlockingManager sharedManager]; + _blocklistCache = [OWSBlockListCache new]; + [_blocklistCache startObservingAndSyncStateWithDelegate:self]; _threadViewModelCache = [NSCache new]; // Ensure ExperienceUpgradeFinder has been initialized. @@ -835,7 +837,7 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations ThreadViewModel *thread = [self threadViewModelForIndexPath:indexPath]; - BOOL isBlocked = [self.blockingManager isThreadBlocked:thread.threadRecord]; + BOOL isBlocked = [self.blocklistCache isThreadBlocked:thread.threadRecord]; [cell configureWithThread:thread contactsManager:self.contactsManager isBlocked:isBlocked]; return cell; @@ -1544,6 +1546,14 @@ NSString *const kArchivedConversationsReuseIdentifier = @"kArchivedConversations } } +#pragma mark - OWSBlockListCacheDelegate + +- (void)blockListCacheDidUpdate:(OWSBlockListCache *_Nonnull)blocklistCache +{ + DDLogVerbose(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__); + [self reloadTableViewData]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/utils/BlockListCache.swift b/SignalMessaging/utils/BlockListCache.swift new file mode 100644 index 000000000..0e24b9d61 --- /dev/null +++ b/SignalMessaging/utils/BlockListCache.swift @@ -0,0 +1,98 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc(OWSBlockListCacheDelegate) +public protocol BlockListCacheDelegate: class { + func blockListCacheDidUpdate(_ blocklistCache: BlockListCache) +} + +@objc(OWSBlockListCache) +public class BlockListCache: NSObject { + + private var blockedRecipientIds: Set = Set() + private var blockedGroupIds: Set = Set() + private let serialQueue: DispatchQueue = DispatchQueue(label: "BlockListCache") + weak var delegate: BlockListCacheDelegate? + + private var blockingManager: OWSBlockingManager { + return OWSBlockingManager.shared() + } + + /// Generally something which wants to use this cache wants to do 3 things + /// 1. get the cache on the latest state + /// 2. update the cache whenever the blockingManager's state changes + /// 3. be notified when the cache updates + /// This method does all three. + @objc + public func startObservingAndSyncState(delegate: BlockListCacheDelegate) { + self.delegate = delegate + NotificationCenter.default.addObserver(self, + selector: #selector(blockListDidChange), + name: NSNotification.Name(rawValue: kNSNotificationName_BlockListDidChange), + object: nil) + updateWithoutNotifyingDelegate() + } + + // MARK: - + + @objc + func blockListDidChange() { + self.update() + } + + @objc(isRecipientIdBlocked:) + public func isBlocked(recipientId: String) -> Bool { + return serialQueue.sync { + blockedRecipientIds.contains(recipientId) + } + } + + @objc(isGroupIdBlocked:) + public func isBlocked(groupId: Data) -> Bool { + return serialQueue.sync { + blockedGroupIds.contains(groupId) + } + } + + @objc(isThreadBlocked:) + public func isBlocked(thread: TSThread) -> Bool { + switch thread { + case let contactThread as TSContactThread: + return serialQueue.sync { + blockedRecipientIds.contains(contactThread.contactIdentifier()) + } + case let groupThread as TSGroupThread: + return serialQueue.sync { + blockedGroupIds.contains(groupThread.groupModel.groupId) + } + default: + owsFail("\(self.logTag) in \(#function) unexepected thread type: \(type(of: thread))") + return false + } + } + + // MARK: - + + public func update() { + updateWithoutNotifyingDelegate() + DispatchQueue.main.async { + self.delegate?.blockListCacheDidUpdate(self) + } + } + + private func updateWithoutNotifyingDelegate() { + let blockedRecipientIds = Set(blockingManager.blockedPhoneNumbers()) + let blockedGroupIds = Set(blockingManager.blockedGroupIds) + update(blockedRecipientIds: blockedRecipientIds, blockedGroupIds: blockedGroupIds) + } + + private func update(blockedRecipientIds: Set, blockedGroupIds: Set) { + serialQueue.sync { + self.blockedRecipientIds = blockedRecipientIds + self.blockedGroupIds = blockedGroupIds + } + } +}