Merge branch 'charlesmchen/lazyContact'
This commit is contained in:
commit
8c6eb791a2
@ -55,17 +55,29 @@ class AddContactShareToExistingContactViewController: ContactsPicker, ContactsPi
|
||||
navigationController.popViewController(animated: true)
|
||||
}
|
||||
|
||||
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) {
|
||||
func contactsPicker(_: ContactsPicker, didSelectContact oldContact: Contact) {
|
||||
Logger.debug("\(self.logTag) in \(#function)")
|
||||
|
||||
guard let mergedContact: CNContact = self.contactShare.cnContact(mergedWithExistingContact: contact) else {
|
||||
owsFail("\(logTag) in \(#function) mergedContact was unexpectedly nil")
|
||||
let contactsManager = Environment.current().contactsManager
|
||||
guard let oldCNContact = contactsManager?.cnContact(withId: oldContact.cnContactId) else {
|
||||
owsFail("\(logTag) could not load old CNContact.")
|
||||
return
|
||||
}
|
||||
guard let newCNContact = OWSContacts.systemContact(for: self.contactShare.dbRecord, imageData: self.contactShare.avatarImageData) else {
|
||||
owsFail("\(logTag) could not load new CNContact.")
|
||||
return
|
||||
}
|
||||
merge(oldCNContact: oldCNContact, newCNContact: newCNContact)
|
||||
}
|
||||
|
||||
func merge(oldCNContact: CNContact, newCNContact: CNContact) {
|
||||
Logger.debug("\(self.logTag) in \(#function)")
|
||||
|
||||
let mergedCNContact: CNContact = Contact.merge(cnContact: oldCNContact, newCNContact: newCNContact)
|
||||
|
||||
// Not actually a "new" contact, but this brings up the edit form rather than the "Read" form
|
||||
// saving our users a tap in some cases when we already know they want to edit.
|
||||
let contactViewController: CNContactViewController = CNContactViewController(forNewContact: mergedContact)
|
||||
let contactViewController: CNContactViewController = CNContactViewController(forNewContact: mergedCNContact)
|
||||
|
||||
// Default title is "New Contact". We could give a more descriptive title, but anything
|
||||
// seems redundant - the context is sufficiently clear.
|
||||
|
||||
@ -5118,18 +5118,23 @@ interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransiti
|
||||
- (void)contactsPicker:(ContactsPicker *)contactsPicker didSelectContact:(Contact *)contact
|
||||
{
|
||||
OWSAssert(contact);
|
||||
OWSAssert(contact.cnContact);
|
||||
|
||||
CNContact *_Nullable cnContact = [self.contactsManager cnContactWithId:contact.cnContactId];
|
||||
if (!cnContact) {
|
||||
OWSFail(@"%@ Could not load system contact.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
DDLogDebug(@"%@ in %s with contact: %@", self.logTag, __PRETTY_FUNCTION__, contact);
|
||||
|
||||
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:contact.cnContact];
|
||||
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:cnContact];
|
||||
if (!contactShareRecord) {
|
||||
DDLogError(@"%@ Could not convert system contact.", self.logTag);
|
||||
OWSFail(@"%@ Could not convert system contact.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isProfileAvatar = NO;
|
||||
NSData *_Nullable avatarImageData = contact.imageData;
|
||||
NSData *_Nullable avatarImageData = [self.contactsManager avatarDataForCNContactId:cnContact.identifier];
|
||||
for (NSString *recipientId in contact.textSecureIdentifiers) {
|
||||
if (avatarImageData) {
|
||||
break;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DebugUIContacts.h"
|
||||
@ -1164,7 +1164,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSAssert(count > 0);
|
||||
OWSAssert(batchCompletionHandler);
|
||||
|
||||
DDLogDebug(@"createRandomContactsBatch: %zd", count);
|
||||
DDLogDebug(@"createRandomContactsBatch: %zu", count);
|
||||
|
||||
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
|
||||
if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
|
||||
@ -1219,7 +1219,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
}
|
||||
|
||||
DDLogError(@"Saving fake contacts: %zd", contacts.count);
|
||||
DDLogError(@"Saving fake contacts: %zu", contacts.count);
|
||||
|
||||
NSError *saveError = nil;
|
||||
if (![store executeSaveRequest:request error:&saveError]) {
|
||||
|
||||
@ -133,6 +133,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
}]];
|
||||
|
||||
[items addObject:[OWSTableItem itemWithTitle:@"Fetch system contacts"
|
||||
actionBlock:^() {
|
||||
[Environment.current.contactsManager requestSystemContactsOnce];
|
||||
}]];
|
||||
|
||||
return [OWSTableSection sectionWithTitle:self.name items:items];
|
||||
}
|
||||
|
||||
|
||||
@ -161,8 +161,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@"EDIT_GROUP_CONTACTS_SECTION_TITLE", @"a title for the contacts section of the 'new/update group' view.");
|
||||
|
||||
for (Contact *contact in self.contactsViewHelper.contactsManager.allContacts) {
|
||||
OWSAssert(contact.cnContact);
|
||||
|
||||
NSString *_Nullable displayName = [self displayNameForContact:contact];
|
||||
if (displayName.length < 1) {
|
||||
continue;
|
||||
@ -198,10 +196,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSFail(@"%@ Contact editing not supported", self.logTag);
|
||||
return;
|
||||
}
|
||||
CNContact *_Nullable cnContact = [self.contactsManager cnContactWithId:contact.cnContactId];
|
||||
if (!cnContact) {
|
||||
OWSFail(@"%@ Could not load system contact.", self.logTag);
|
||||
return;
|
||||
}
|
||||
[self.contactsViewHelper presentContactViewControllerForRecipientId:self.recipientId
|
||||
fromViewController:self
|
||||
editImmediately:YES
|
||||
addToExistingCnContact:contact.cnContact];
|
||||
addToExistingCnContact:cnContact];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -74,10 +74,11 @@ class ContactCell: UITableViewCell {
|
||||
self.contact = contact
|
||||
self.showsWhenSelected = showsWhenSelected
|
||||
|
||||
titleLabel.attributedText = contact.cnContact?.formattedFullName(font: titleLabel.font)
|
||||
let cnContact = contactsManager.cnContact(withId: contact.cnContactId)
|
||||
titleLabel.attributedText = cnContact?.formattedFullName(font: titleLabel.font)
|
||||
updateSubtitle(subtitleType: subtitleType, contact: contact)
|
||||
|
||||
if let contactImage = contact.image {
|
||||
if let contactImage = contactsManager.avatarImage(forCNContactId: contact.cnContactId) {
|
||||
contactImageView.image = contactImage
|
||||
} else {
|
||||
let contactIdForDeterminingBackgroundColor: String
|
||||
|
||||
@ -148,17 +148,6 @@ public class ContactShareViewModel: NSObject {
|
||||
return dbRecord.isProfileAvatar
|
||||
}
|
||||
|
||||
@objc
|
||||
public func cnContact(mergedWithExistingContact existingContact: Contact) -> CNContact? {
|
||||
|
||||
guard let newCNContact = OWSContacts.systemContact(for: self.dbRecord, imageData: self.avatarImageData) else {
|
||||
owsFail("\(logTag) in \(#function) newCNContact was unexpectedly nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
return existingContact.buildCNContact(mergedWithNewContact: newCNContact)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func copy(withName name: OWSContactName) -> ContactShareViewModel {
|
||||
|
||||
@ -177,5 +166,4 @@ public class ContactShareViewModel: NSObject {
|
||||
// If we want to keep the avatar image, the caller will need to re-apply it.
|
||||
return ContactShareViewModel(contactShareRecord: newDbRecord, avatarImageData: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -355,7 +355,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
cnContact = updatedContact;
|
||||
}
|
||||
if (signalAccount && !cnContact) {
|
||||
cnContact = signalAccount.contact.cnContact;
|
||||
cnContact = [self.contactsManager cnContactWithId:signalAccount.contact.cnContactId];
|
||||
}
|
||||
if (cnContact) {
|
||||
if (shouldEditImmediately) {
|
||||
|
||||
@ -149,37 +149,7 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
|
||||
self.thread = thread;
|
||||
|
||||
if (self.attachment.isConvertibleToContactShare) {
|
||||
NSData *data = self.attachment.data;
|
||||
|
||||
Contact *_Nullable contact = [Contact contactWithVCardData:data];
|
||||
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:contact.cnContact];
|
||||
if (!contactShareRecord) {
|
||||
DDLogError(@"%@ Could not convert system contact.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isProfileAvatar = NO;
|
||||
NSData *_Nullable avatarImageData = contact.imageData;
|
||||
for (NSString *recipientId in contact.textSecureIdentifiers) {
|
||||
if (avatarImageData) {
|
||||
break;
|
||||
}
|
||||
avatarImageData = [self.contactsManager profileImageDataForPhoneIdentifier:recipientId];
|
||||
if (avatarImageData) {
|
||||
isProfileAvatar = YES;
|
||||
}
|
||||
}
|
||||
contactShareRecord.isProfileAvatar = isProfileAvatar;
|
||||
|
||||
ContactShareViewModel *contactShare =
|
||||
[[ContactShareViewModel alloc] initWithContactShareRecord:contactShareRecord
|
||||
avatarImageData:avatarImageData];
|
||||
|
||||
ContactShareApprovalViewController *approvalVC =
|
||||
[[ContactShareApprovalViewController alloc] initWithContactShare:contactShare
|
||||
contactsManager:self.contactsManager
|
||||
delegate:self];
|
||||
[self.navigationController pushViewController:approvalVC animated:YES];
|
||||
[self showContactShareApproval];
|
||||
return;
|
||||
}
|
||||
|
||||
@ -200,6 +170,45 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showContactShareApproval
|
||||
{
|
||||
OWSAssert(self.attachment);
|
||||
OWSAssert(self.thread);
|
||||
OWSAssert(self.attachment.isConvertibleToContactShare);
|
||||
|
||||
NSData *data = self.attachment.data;
|
||||
|
||||
CNContact *_Nullable cnContact = [Contact cnContactWithVCardData:data];
|
||||
Contact *_Nullable contact = [[Contact alloc] initWithSystemContact:cnContact];
|
||||
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:cnContact];
|
||||
if (!contactShareRecord) {
|
||||
DDLogError(@"%@ Could not convert system contact.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isProfileAvatar = NO;
|
||||
NSData *_Nullable avatarImageData = [self.contactsManager avatarDataForCNContactId:contact.cnContactId];
|
||||
for (NSString *recipientId in contact.textSecureIdentifiers) {
|
||||
if (avatarImageData) {
|
||||
break;
|
||||
}
|
||||
avatarImageData = [self.contactsManager profileImageDataForPhoneIdentifier:recipientId];
|
||||
if (avatarImageData) {
|
||||
isProfileAvatar = YES;
|
||||
}
|
||||
}
|
||||
contactShareRecord.isProfileAvatar = isProfileAvatar;
|
||||
|
||||
ContactShareViewModel *contactShare =
|
||||
[[ContactShareViewModel alloc] initWithContactShareRecord:contactShareRecord avatarImageData:avatarImageData];
|
||||
|
||||
ContactShareApprovalViewController *approvalVC =
|
||||
[[ContactShareApprovalViewController alloc] initWithContactShare:contactShare
|
||||
contactsManager:self.contactsManager
|
||||
delegate:self];
|
||||
[self.navigationController pushViewController:approvalVC animated:YES];
|
||||
}
|
||||
|
||||
// override
|
||||
- (void)dismissPressed:(id)sender
|
||||
{
|
||||
|
||||
@ -36,6 +36,8 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
@property (nonatomic, readonly) SystemContactsFetcher *systemContactsFetcher;
|
||||
@property (nonatomic, readonly) YapDatabaseConnection *dbReadConnection;
|
||||
@property (nonatomic, readonly) YapDatabaseConnection *dbWriteConnection;
|
||||
@property (nonatomic, readonly) NSCache<NSString *, CNContact *> *cnContactCache;
|
||||
@property (nonatomic, readonly) NSCache<NSString *, UIImage *> *cnContactAvatarCache;
|
||||
|
||||
@end
|
||||
|
||||
@ -60,6 +62,10 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
_signalAccounts = @[];
|
||||
_systemContactsFetcher = [SystemContactsFetcher new];
|
||||
_systemContactsFetcher.delegate = self;
|
||||
_cnContactCache = [NSCache new];
|
||||
_cnContactCache.countLimit = 50;
|
||||
_cnContactAvatarCache = [NSCache new];
|
||||
_cnContactAvatarCache.countLimit = 25;
|
||||
|
||||
OWSSingletonAssert();
|
||||
|
||||
@ -133,6 +139,63 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
return self.systemContactsFetcher.supportsContactEditing;
|
||||
}
|
||||
|
||||
#pragma mark - CNContacts
|
||||
|
||||
- (nullable CNContact *)cnContactWithId:(nullable NSString *)contactId
|
||||
{
|
||||
OWSAssert(contactId.length > 0);
|
||||
OWSAssert(self.cnContactCache);
|
||||
|
||||
if (!contactId) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CNContact *_Nullable cnContact;
|
||||
@synchronized(self.cnContactCache) {
|
||||
cnContact = [self.cnContactCache objectForKey:contactId];
|
||||
if (!cnContact) {
|
||||
cnContact = [self.systemContactsFetcher fetchCNContactWithContactId:contactId];
|
||||
if (cnContact) {
|
||||
[self.cnContactCache setObject:cnContact forKey:contactId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cnContact;
|
||||
}
|
||||
|
||||
- (nullable NSData *)avatarDataForCNContactId:(nullable NSString *)contactId
|
||||
{
|
||||
// Don't bother to cache avatar data.
|
||||
CNContact *_Nullable cnContact = [self cnContactWithId:contactId];
|
||||
return [Contact avatarDataForCNContact:cnContact];
|
||||
}
|
||||
|
||||
- (nullable UIImage *)avatarImageForCNContactId:(nullable NSString *)contactId
|
||||
{
|
||||
OWSAssert(self.cnContactAvatarCache);
|
||||
|
||||
if (!contactId) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *_Nullable avatarImage;
|
||||
@synchronized(self.cnContactAvatarCache) {
|
||||
avatarImage = [self.cnContactAvatarCache objectForKey:contactId];
|
||||
if (!avatarImage) {
|
||||
NSData *_Nullable avatarData = [self avatarDataForCNContactId:contactId];
|
||||
if (avatarData) {
|
||||
avatarImage = [UIImage imageWithData:avatarData];
|
||||
}
|
||||
if (avatarImage) {
|
||||
[self.cnContactAvatarCache setObject:avatarImage forKey:contactId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return avatarImage;
|
||||
}
|
||||
|
||||
#pragma mark - SystemContactsFetcherDelegate
|
||||
|
||||
- (void)systemContactsFetcher:(SystemContactsFetcher *)systemsContactsFetcher
|
||||
@ -232,6 +295,8 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.allContacts = contacts;
|
||||
self.allContactsMap = [allContactsMap copy];
|
||||
[self.cnContactCache removeAllObjects];
|
||||
[self.cnContactAvatarCache removeAllObjects];
|
||||
|
||||
[self.avatarCache removeAllImages];
|
||||
|
||||
@ -584,7 +649,8 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
NSAttributedString *lastName =
|
||||
[[NSAttributedString alloc] initWithString:cachedLastName attributes:lastNameAttributes];
|
||||
|
||||
CNContact *_Nullable cnContact = self.allContactsMap[recipientId].cnContact;
|
||||
NSString *_Nullable cnContactId = self.allContactsMap[recipientId].cnContactId;
|
||||
CNContact *_Nullable cnContact = [self cnContactWithId:cnContactId];
|
||||
if (!cnContact) {
|
||||
// If we don't have a CNContact for this recipient id, make one.
|
||||
// Presumably [CNContactFormatter nameOrderForContact:] tries
|
||||
@ -765,7 +831,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
||||
contact = [self signalAccountForRecipientId:identifier].contact;
|
||||
}
|
||||
|
||||
return contact.image;
|
||||
return [self avatarImageForCNContactId:contact.cnContactId];
|
||||
}
|
||||
|
||||
- (nullable UIImage *)profileImageForPhoneIdentifier:(nullable NSString *)identifier
|
||||
|
||||
@ -17,12 +17,12 @@ protocol ContactStoreAdaptee {
|
||||
var supportsContactEditing: Bool { get }
|
||||
func requestAccess(completionHandler: @escaping (Bool, Error?) -> Void)
|
||||
func fetchContacts() -> Result<[Contact], Error>
|
||||
func fetchCNContact(contactId: String) -> CNContact?
|
||||
func startObservingChanges(changeHandler: @escaping () -> Void)
|
||||
}
|
||||
|
||||
public
|
||||
class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
|
||||
let TAG = "[ContactsFrameworkContactStoreAdaptee]"
|
||||
class ContactsFrameworkContactStoreAdaptee: NSObject, ContactStoreAdaptee {
|
||||
private let contactStore = CNContactStore()
|
||||
private var changeHandler: (() -> Void)?
|
||||
private var initializedObserver = false
|
||||
@ -72,7 +72,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
|
||||
return
|
||||
}
|
||||
|
||||
Logger.info("\(self.TAG) sort order changed: \(String(describing: self.lastSortOrder)) -> \(String(describing: currentSortOrder))")
|
||||
Logger.info("\(self.logTag) sort order changed: \(String(describing: self.lastSortOrder)) -> \(String(describing: currentSortOrder))")
|
||||
self.lastSortOrder = currentSortOrder
|
||||
self.runChangeHandler()
|
||||
}
|
||||
@ -81,7 +81,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
|
||||
@objc
|
||||
func runChangeHandler() {
|
||||
guard let changeHandler = self.changeHandler else {
|
||||
owsFail("\(TAG) trying to run change handler before it was registered")
|
||||
owsFail("\(self.logTag) trying to run change handler before it was registered")
|
||||
return
|
||||
}
|
||||
changeHandler()
|
||||
@ -100,13 +100,35 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
|
||||
systemContacts.append(contact)
|
||||
}
|
||||
} catch let error as NSError {
|
||||
owsFail("\(self.TAG) Failed to fetch contacts with error:\(error)")
|
||||
owsFail("\(self.logTag) Failed to fetch contacts with error:\(error)")
|
||||
return .error(error)
|
||||
}
|
||||
|
||||
let contacts = systemContacts.map { Contact(systemContact: $0) }
|
||||
return .success(contacts)
|
||||
}
|
||||
|
||||
func fetchCNContact(contactId: String) -> CNContact? {
|
||||
var result: CNContact?
|
||||
do {
|
||||
let contactFetchRequest = CNContactFetchRequest(keysToFetch: ContactsFrameworkContactStoreAdaptee.allowedContactKeys)
|
||||
contactFetchRequest.sortOrder = .userDefault
|
||||
contactFetchRequest.predicate = CNContact.predicateForContacts(withIdentifiers: [contactId])
|
||||
|
||||
try self.contactStore.enumerateContacts(with: contactFetchRequest) { (contact, _) -> Void in
|
||||
guard result == nil else {
|
||||
owsFail("\(self.logTag) More than one contact with contact id.")
|
||||
return
|
||||
}
|
||||
result = contact
|
||||
}
|
||||
} catch let error as NSError {
|
||||
owsFail("\(self.logTag) Failed to fetch contact with error:\(error)")
|
||||
return nil
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
@ -125,8 +147,6 @@ public enum ContactStoreAuthorizationStatus: UInt {
|
||||
@objc
|
||||
public class SystemContactsFetcher: NSObject {
|
||||
|
||||
private let TAG = "[SystemContactsFetcher]"
|
||||
|
||||
private let serialQueue = DispatchQueue(label: "SystemContactsFetcherQueue")
|
||||
|
||||
var lastContactUpdateHash: Int?
|
||||
@ -207,20 +227,20 @@ public class SystemContactsFetcher: NSObject {
|
||||
switch authorizationStatus {
|
||||
case .notDetermined:
|
||||
if CurrentAppContext().isInBackground() {
|
||||
Logger.error("\(self.TAG) do not request contacts permission when app is in background")
|
||||
Logger.error("\(self.logTag) do not request contacts permission when app is in background")
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
self.contactStoreAdapter.requestAccess { (granted, error) in
|
||||
if let error = error {
|
||||
Logger.error("\(self.TAG) error fetching contacts: \(error)")
|
||||
Logger.error("\(self.logTag) error fetching contacts: \(error)")
|
||||
completion(error)
|
||||
return
|
||||
}
|
||||
|
||||
guard granted else {
|
||||
// This case should have been caught by the error guard a few lines up.
|
||||
owsFail("\(self.TAG) declined contact access.")
|
||||
owsFail("\(self.logTag) declined contact access.")
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
@ -232,7 +252,7 @@ public class SystemContactsFetcher: NSObject {
|
||||
case .authorized:
|
||||
self.updateContacts(completion: completion)
|
||||
case .denied, .restricted:
|
||||
Logger.debug("\(TAG) contacts were \(self.authorizationStatus)")
|
||||
Logger.debug("\(logTag) contacts were \(self.authorizationStatus)")
|
||||
self.delegate?.systemContactsFetcher(self, hasAuthorizationStatus: authorizationStatus)
|
||||
completion(nil)
|
||||
}
|
||||
@ -292,7 +312,7 @@ public class SystemContactsFetcher: NSObject {
|
||||
guard let _ = self else {
|
||||
return
|
||||
}
|
||||
Logger.error("background task time ran out contacts fetch completed.")
|
||||
Logger.error("background task time ran out before contacts fetch completed.")
|
||||
})
|
||||
|
||||
// Ensure completion is invoked on main thread.
|
||||
@ -310,7 +330,7 @@ public class SystemContactsFetcher: NSObject {
|
||||
|
||||
serialQueue.async {
|
||||
|
||||
Logger.info("\(self.TAG) fetching contacts")
|
||||
Logger.info("\(self.logTag) fetching contacts")
|
||||
|
||||
var fetchedContacts: [Contact]?
|
||||
switch self.contactStoreAdapter.fetchContacts() {
|
||||
@ -322,21 +342,21 @@ public class SystemContactsFetcher: NSObject {
|
||||
}
|
||||
|
||||
guard let contacts = fetchedContacts else {
|
||||
owsFail("\(self.TAG) contacts was unexpectedly not set.")
|
||||
owsFail("\(self.logTag) contacts was unexpectedly not set.")
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
Logger.info("\(self.TAG) fetched \(contacts.count) contacts.")
|
||||
Logger.info("\(self.logTag) fetched \(contacts.count) contacts.")
|
||||
let contactsHash = HashableArray(contacts).hashValue
|
||||
|
||||
DispatchQueue.main.async {
|
||||
var shouldNotifyDelegate = false
|
||||
|
||||
if self.lastContactUpdateHash != contactsHash {
|
||||
Logger.info("\(self.TAG) contact hash changed. new contactsHash: \(contactsHash)")
|
||||
Logger.info("\(self.logTag) contact hash changed. new contactsHash: \(contactsHash)")
|
||||
shouldNotifyDelegate = true
|
||||
} else if isUserRequested {
|
||||
Logger.info("\(self.TAG) ignoring debounce due to user request")
|
||||
Logger.info("\(self.logTag) ignoring debounce due to user request")
|
||||
shouldNotifyDelegate = true
|
||||
} else {
|
||||
|
||||
@ -346,19 +366,19 @@ public class SystemContactsFetcher: NSObject {
|
||||
|
||||
let expiresAtDate = Date(timeInterval: kDebounceInterval, since: lastDelegateNotificationDate)
|
||||
if Date() > expiresAtDate {
|
||||
Logger.info("\(self.TAG) debounce interval expired at: \(expiresAtDate)")
|
||||
Logger.info("\(self.logTag) debounce interval expired at: \(expiresAtDate)")
|
||||
shouldNotifyDelegate = true
|
||||
} else {
|
||||
Logger.info("\(self.TAG) ignoring since debounce interval hasn't expired")
|
||||
Logger.info("\(self.logTag) ignoring since debounce interval hasn't expired")
|
||||
}
|
||||
} else {
|
||||
Logger.info("\(self.TAG) first contact fetch. contactsHash: \(contactsHash)")
|
||||
Logger.info("\(self.logTag) first contact fetch. contactsHash: \(contactsHash)")
|
||||
shouldNotifyDelegate = true
|
||||
}
|
||||
}
|
||||
|
||||
guard shouldNotifyDelegate else {
|
||||
Logger.info("\(self.TAG) no reason to notify delegate.")
|
||||
Logger.info("\(self.logTag) no reason to notify delegate.")
|
||||
|
||||
completion(nil)
|
||||
|
||||
@ -373,6 +393,18 @@ public class SystemContactsFetcher: NSObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func fetchCNContact(contactId: String) -> CNContact? {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
guard authorizationStatus == .authorized else {
|
||||
Logger.error("\(logTag) contact fetch failed; no access.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return contactStoreAdapter.fetchCNContact(contactId: contactId)
|
||||
}
|
||||
}
|
||||
|
||||
struct HashableArray<Element: Hashable>: Hashable {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
@objc
|
||||
public class AnyLRUCache: NSObject {
|
||||
|
||||
let backingCache: LRUCache<NSObject, NSObject>
|
||||
private let backingCache: LRUCache<NSObject, NSObject>
|
||||
|
||||
@objc
|
||||
public init(maxSize: Int) {
|
||||
@ -21,29 +21,63 @@ public class AnyLRUCache: NSObject {
|
||||
public func set(key: NSObject, value: NSObject) {
|
||||
self.backingCache.set(key: key, value: value)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func clear() {
|
||||
self.backingCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
// A simple LRU cache bounded by the number of entries.
|
||||
//
|
||||
// TODO: We might want to observe memory pressure notifications.
|
||||
public class LRUCache<KeyType: Hashable & Equatable, ValueType> {
|
||||
|
||||
private var cacheMap: [KeyType: ValueType] = [:]
|
||||
private var cacheOrder: [KeyType] = []
|
||||
private let maxSize: Int
|
||||
|
||||
@objc
|
||||
public init(maxSize: Int) {
|
||||
self.maxSize = maxSize
|
||||
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(didReceiveMemoryWarning),
|
||||
name: NSNotification.Name.UIApplicationDidReceiveMemoryWarning,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(didEnterBackground),
|
||||
name: NSNotification.Name.OWSApplicationDidEnterBackground,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
@objc func didEnterBackground() {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
clear()
|
||||
}
|
||||
|
||||
@objc func didReceiveMemoryWarning() {
|
||||
SwiftAssertIsOnMainThread(#function)
|
||||
|
||||
clear()
|
||||
}
|
||||
|
||||
private func updateCacheOrder(key: KeyType) {
|
||||
cacheOrder = cacheOrder.filter { $0 != key }
|
||||
cacheOrder.append(key)
|
||||
}
|
||||
|
||||
public func get(key: KeyType) -> ValueType? {
|
||||
guard let value = cacheMap[key] else {
|
||||
// Miss
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update cache order.
|
||||
cacheOrder = cacheOrder.filter { $0 != key }
|
||||
cacheOrder.append(key)
|
||||
// Hit
|
||||
updateCacheOrder(key: key)
|
||||
|
||||
return value
|
||||
}
|
||||
@ -51,9 +85,7 @@ public class LRUCache<KeyType: Hashable & Equatable, ValueType> {
|
||||
public func set(key: KeyType, value: ValueType) {
|
||||
cacheMap[key] = value
|
||||
|
||||
// Update cache order.
|
||||
cacheOrder = cacheOrder.filter { $0 != key }
|
||||
cacheOrder.append(key)
|
||||
updateCacheOrder(key: key)
|
||||
|
||||
while cacheOrder.count > maxSize {
|
||||
guard let staleKey = cacheOrder.first else {
|
||||
@ -64,4 +96,10 @@ public class LRUCache<KeyType: Hashable & Equatable, ValueType> {
|
||||
cacheMap.removeValue(forKey: staleKey)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func clear() {
|
||||
cacheMap.removeAll()
|
||||
cacheOrder.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,11 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (readonly, nonatomic) NSArray<NSString *> *emails;
|
||||
@property (readonly, nonatomic) NSString *uniqueId;
|
||||
@property (nonatomic, readonly) BOOL isSignalContact;
|
||||
#if TARGET_OS_IOS
|
||||
@property (nullable, readonly, nonatomic) UIImage *image;
|
||||
@property (nullable, readonly, nonatomic) NSData *imageData;
|
||||
@property (nullable, nonatomic, readonly) CNContact *cnContact;
|
||||
#endif // TARGET_OS_IOS
|
||||
@property (nonatomic, readonly) NSString *cnContactId;
|
||||
|
||||
- (NSArray<SignalRecipient *> *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction;
|
||||
// TODO: Remove this method.
|
||||
@ -40,8 +36,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
- (instancetype)initWithSystemContact:(CNContact *)contact NS_AVAILABLE_IOS(9_0);
|
||||
- (instancetype)initWithSystemContact:(CNContact *)cnContact NS_AVAILABLE_IOS(9_0);
|
||||
+ (nullable Contact *)contactWithVCardData:(NSData *)data;
|
||||
+ (nullable CNContact *)cnContactWithVCardData:(NSData *)data;
|
||||
|
||||
- (NSString *)nameForPhoneNumber:(NSString *)recipientId;
|
||||
|
||||
@ -51,7 +48,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
+ (NSString *)formattedFullNameWithCNContact:(CNContact *)cnContact NS_SWIFT_NAME(formattedFullName(cnContact:));
|
||||
+ (nullable NSString *)localizedStringForCNLabel:(nullable NSString *)cnLabel;
|
||||
|
||||
- (CNContact *)buildCNContactMergedWithNewContact:(CNContact *)newCNContact NS_SWIFT_NAME(buildCNContact(mergedWithNewContact:));
|
||||
+ (CNContact *)mergeCNContact:(CNContact *)oldCNContact
|
||||
newCNContact:(CNContact *)newCNContact NS_SWIFT_NAME(merge(cnContact:newCNContact:));
|
||||
|
||||
+ (nullable NSData *)avatarDataForCNContact:(nullable CNContact *)cnContact;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#import "PhoneNumber.h"
|
||||
#import "SignalRecipient.h"
|
||||
#import "TSAccountManager.h"
|
||||
#import "TextSecureKitEnv.h"
|
||||
|
||||
@import Contacts;
|
||||
|
||||
@ -16,7 +17,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Contact ()
|
||||
|
||||
@property (readonly, nonatomic) NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap;
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap;
|
||||
@property (nonatomic, readonly) NSUInteger imageHash;
|
||||
|
||||
@end
|
||||
|
||||
@ -26,26 +28,24 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@synthesize comparableNameFirstLast = _comparableNameFirstLast;
|
||||
@synthesize comparableNameLastFirst = _comparableNameLastFirst;
|
||||
@synthesize image = _image;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
- (instancetype)initWithSystemContact:(CNContact *)contact
|
||||
- (instancetype)initWithSystemContact:(CNContact *)cnContact
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_cnContact = contact;
|
||||
_firstName = contact.givenName.ows_stripped;
|
||||
_lastName = contact.familyName.ows_stripped;
|
||||
_fullName = [Contact formattedFullNameWithCNContact:contact];
|
||||
_uniqueId = contact.identifier;
|
||||
_cnContactId = cnContact.identifier;
|
||||
_firstName = cnContact.givenName.ows_stripped;
|
||||
_lastName = cnContact.familyName.ows_stripped;
|
||||
_fullName = [Contact formattedFullNameWithCNContact:cnContact];
|
||||
|
||||
NSMutableArray<NSString *> *phoneNumbers = [NSMutableArray new];
|
||||
NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap = [NSMutableDictionary new];
|
||||
for (CNLabeledValue *phoneNumberField in contact.phoneNumbers) {
|
||||
for (CNLabeledValue *phoneNumberField in cnContact.phoneNumbers) {
|
||||
if ([phoneNumberField.value isKindOfClass:[CNPhoneNumber class]]) {
|
||||
CNPhoneNumber *phoneNumber = (CNPhoneNumber *)phoneNumberField.value;
|
||||
[phoneNumbers addObject:phoneNumber.stringValue];
|
||||
@ -97,23 +97,31 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[self parsedPhoneNumbersFromUserTextPhoneNumbers:phoneNumbers phoneNumberNameMap:phoneNumberNameMap];
|
||||
|
||||
NSMutableArray<NSString *> *emailAddresses = [NSMutableArray new];
|
||||
for (CNLabeledValue *emailField in contact.emailAddresses) {
|
||||
for (CNLabeledValue *emailField in cnContact.emailAddresses) {
|
||||
if ([emailField.value isKindOfClass:[NSString class]]) {
|
||||
[emailAddresses addObject:(NSString *)emailField.value];
|
||||
}
|
||||
}
|
||||
_emails = [emailAddresses copy];
|
||||
|
||||
if (contact.thumbnailImageData) {
|
||||
_imageData = [contact.thumbnailImageData copy];
|
||||
} else if (contact.imageData) {
|
||||
// This only occurs when sharing a contact via the share extension
|
||||
_imageData = [contact.imageData copy];
|
||||
NSData *_Nullable avatarData = [Contact avatarDataForCNContact:cnContact];
|
||||
if (avatarData) {
|
||||
NSUInteger hashValue = 0;
|
||||
NSData *hashData = [Cryptography computeSHA256Digest:avatarData truncatedToBytes:sizeof(hashValue)];
|
||||
[hashData getBytes:&hashValue length:sizeof(hashValue)];
|
||||
_imageHash = hashValue;
|
||||
} else {
|
||||
_imageHash = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)uniqueId
|
||||
{
|
||||
return self.cnContactId;
|
||||
}
|
||||
|
||||
+ (nullable Contact *)contactWithVCardData:(NSData *)data
|
||||
{
|
||||
CNContact *_Nullable cnContact = [self cnContactWithVCardData:data];
|
||||
@ -125,29 +133,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return [[self alloc] initWithSystemContact:cnContact];
|
||||
}
|
||||
|
||||
- (nullable UIImage *)image
|
||||
{
|
||||
if (_image) {
|
||||
return _image;
|
||||
}
|
||||
|
||||
if (!self.imageData) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_image = [UIImage imageWithData:self.imageData];
|
||||
return _image;
|
||||
}
|
||||
|
||||
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
|
||||
{
|
||||
if ([propertyKey isEqualToString:@"cnContact"] || [propertyKey isEqualToString:@"image"]) {
|
||||
return MTLPropertyStorageTransitory;
|
||||
} else {
|
||||
return [super storageBehaviorForPropertyWithKey:propertyKey];
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TARGET_OS_IOS
|
||||
|
||||
- (NSArray<PhoneNumber *> *)parsedPhoneNumbersFromUserTextPhoneNumbers:(NSArray<NSString *> *)userTextPhoneNumbers
|
||||
@ -278,6 +263,20 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return value;
|
||||
}
|
||||
|
||||
+ (nullable NSData *)avatarDataForCNContact:(nullable CNContact *)cnContact
|
||||
{
|
||||
if (cnContact.thumbnailImageData) {
|
||||
return cnContact.thumbnailImageData.copy;
|
||||
} else if (cnContact.imageData) {
|
||||
// This only occurs when sharing a contact via the share extension
|
||||
return cnContact.imageData.copy;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// This method is used to de-bounce system contact fetch notifications
|
||||
// by checking for changes in the contact data.
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
// base hash is some arbitrary number
|
||||
@ -285,13 +284,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
hash = hash ^ self.fullName.hash;
|
||||
|
||||
if (self.imageData) {
|
||||
NSUInteger thumbnailHash = 0;
|
||||
NSData *thumbnailHashData =
|
||||
[Cryptography computeSHA256Digest:self.imageData truncatedToBytes:sizeof(thumbnailHash)];
|
||||
[thumbnailHashData getBytes:&thumbnailHash length:sizeof(thumbnailHash)];
|
||||
hash = hash ^ thumbnailHash;
|
||||
}
|
||||
hash = hash ^ self.imageHash;
|
||||
|
||||
for (PhoneNumber *phoneNumber in self.parsedPhoneNumbers) {
|
||||
hash = hash ^ phoneNumber.toE164.hash;
|
||||
@ -326,9 +319,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return contacts.firstObject;
|
||||
}
|
||||
|
||||
- (CNContact *)buildCNContactMergedWithNewContact:(CNContact *)newCNContact
|
||||
+ (CNContact *)mergeCNContact:(CNContact *)oldCNContact newCNContact:(CNContact *)newCNContact
|
||||
{
|
||||
CNMutableContact *_Nullable mergedCNContact = [self.cnContact mutableCopy];
|
||||
OWSAssert(oldCNContact);
|
||||
OWSAssert(newCNContact);
|
||||
|
||||
Contact *oldContact = [[Contact alloc] initWithSystemContact:oldCNContact];
|
||||
|
||||
CNMutableContact *_Nullable mergedCNContact = [oldCNContact mutableCopy];
|
||||
if (!mergedCNContact) {
|
||||
OWSFail(@"%@ in %s mergedCNContact was unexpectedly nil", self.logTag, __PRETTY_FUNCTION__);
|
||||
return [CNContact new];
|
||||
@ -351,8 +349,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
|
||||
// Phone Numbers
|
||||
NSSet<PhoneNumber *> *existingParsedPhoneNumberSet = [NSSet setWithArray:self.parsedPhoneNumbers];
|
||||
NSSet<NSString *> *existingUnparsedPhoneNumberSet = [NSSet setWithArray:self.userTextPhoneNumbers];
|
||||
NSSet<PhoneNumber *> *existingParsedPhoneNumberSet = [NSSet setWithArray:oldContact.parsedPhoneNumbers];
|
||||
NSSet<NSString *> *existingUnparsedPhoneNumberSet = [NSSet setWithArray:oldContact.userTextPhoneNumbers];
|
||||
|
||||
NSMutableArray<CNLabeledValue<CNPhoneNumber *> *> *mergedPhoneNumbers = [mergedCNContact.phoneNumbers mutableCopy];
|
||||
for (CNLabeledValue<CNPhoneNumber *> *labeledPhoneNumber in newCNContact.phoneNumbers) {
|
||||
@ -371,7 +369,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
mergedCNContact.phoneNumbers = mergedPhoneNumbers;
|
||||
|
||||
// Emails
|
||||
NSSet<NSString *> *existingEmailSet = [NSSet setWithArray:self.emails];
|
||||
NSSet<NSString *> *existingEmailSet = [NSSet setWithArray:oldContact.emails];
|
||||
NSMutableArray<CNLabeledValue<NSString *> *> *mergedEmailAddresses = [mergedCNContact.emailAddresses mutableCopy];
|
||||
for (CNLabeledValue<NSString *> *labeledEmail in newCNContact.emailAddresses) {
|
||||
NSString *normalizedValue = labeledEmail.value.ows_stripped;
|
||||
|
||||
@ -1,19 +1,22 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSChunkedOutputStream.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SignalAccount;
|
||||
@class OWSRecipientIdentity;
|
||||
@class SignalAccount;
|
||||
|
||||
@protocol ContactsManagerProtocol;
|
||||
|
||||
@interface OWSContactsOutputStream : OWSChunkedOutputStream
|
||||
|
||||
- (void)writeSignalAccount:(SignalAccount *)signalAccount
|
||||
recipientIdentity:(nullable OWSRecipientIdentity *)recipientIdentity
|
||||
profileKeyData:(nullable NSData *)profileKeyData;
|
||||
profileKeyData:(nullable NSData *)profileKeyData
|
||||
contactsManager:(id<ContactsManagerProtocol>)contactsManager;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#import "OWSContactsOutputStream.h"
|
||||
#import "Contact.h"
|
||||
#import "ContactsManagerProtocol.h"
|
||||
#import "Cryptography.h"
|
||||
#import "MIMETypeUtil.h"
|
||||
#import "NSData+keyVersionByte.h"
|
||||
@ -22,9 +23,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)writeSignalAccount:(SignalAccount *)signalAccount
|
||||
recipientIdentity:(nullable OWSRecipientIdentity *)recipientIdentity
|
||||
profileKeyData:(nullable NSData *)profileKeyData
|
||||
contactsManager:(id<ContactsManagerProtocol>)contactsManager
|
||||
{
|
||||
OWSAssert(signalAccount);
|
||||
OWSAssert(signalAccount.contact);
|
||||
OWSAssert(contactsManager);
|
||||
|
||||
OWSSignalServiceProtosContactDetailsBuilder *contactBuilder = [OWSSignalServiceProtosContactDetailsBuilder new];
|
||||
[contactBuilder setName:signalAccount.contact.fullName];
|
||||
@ -38,15 +41,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
contactBuilder.verifiedBuilder = verifiedBuilder;
|
||||
}
|
||||
|
||||
NSData *avatarPng;
|
||||
if (signalAccount.contact.image) {
|
||||
OWSSignalServiceProtosContactDetailsAvatarBuilder *avatarBuilder =
|
||||
[OWSSignalServiceProtosContactDetailsAvatarBuilder new];
|
||||
UIImage *_Nullable rawAvatar = [contactsManager avatarImageForCNContactId:signalAccount.contact.cnContactId];
|
||||
NSData *_Nullable avatarPng;
|
||||
if (rawAvatar) {
|
||||
avatarPng = UIImagePNGRepresentation(rawAvatar);
|
||||
if (avatarPng) {
|
||||
OWSSignalServiceProtosContactDetailsAvatarBuilder *avatarBuilder =
|
||||
[OWSSignalServiceProtosContactDetailsAvatarBuilder new];
|
||||
|
||||
[avatarBuilder setContentType:OWSMimeTypeImagePng];
|
||||
avatarPng = UIImagePNGRepresentation(signalAccount.contact.image);
|
||||
[avatarBuilder setLength:(uint32_t)avatarPng.length];
|
||||
[contactBuilder setAvatarBuilder:avatarBuilder];
|
||||
[avatarBuilder setContentType:OWSMimeTypeImagePng];
|
||||
[avatarBuilder setLength:(uint32_t)avatarPng.length];
|
||||
[contactBuilder setAvatarBuilder:avatarBuilder];
|
||||
}
|
||||
}
|
||||
|
||||
if (profileKeyData) {
|
||||
@ -79,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[self.delegateStream writeRawVarint32:contactDataLength];
|
||||
[self.delegateStream writeRawData:contactData];
|
||||
|
||||
if (signalAccount.contact.image) {
|
||||
if (avatarPng) {
|
||||
[self.delegateStream writeRawData:avatarPng];
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#import "SignalAccount.h"
|
||||
#import "TSAttachment.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TextSecureKitEnv.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@ -71,6 +72,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (NSData *)buildPlainTextAttachmentData
|
||||
{
|
||||
id<ContactsManagerProtocol> contactsManager = TextSecureKitEnv.sharedEnv.contactsManager;
|
||||
|
||||
// TODO use temp file stream to avoid loading everything into memory at once
|
||||
// First though, we need to re-engineer our attachment process to accept streams (encrypting with stream,
|
||||
// and uploading with streams).
|
||||
@ -82,10 +85,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSRecipientIdentity *_Nullable recipientIdentity =
|
||||
[self.identityManager recipientIdentityForRecipientId:signalAccount.recipientId];
|
||||
NSData *_Nullable profileKeyData = [self.profileManager profileKeyDataForRecipientId:signalAccount.recipientId];
|
||||
|
||||
|
||||
[contactsOutputStream writeSignalAccount:signalAccount
|
||||
recipientIdentity:recipientIdentity
|
||||
profileKeyData:profileKeyData];
|
||||
profileKeyData:profileKeyData
|
||||
contactsManager:contactsManager];
|
||||
}
|
||||
|
||||
[contactsOutputStream flush];
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CNContact;
|
||||
@class Contact;
|
||||
@class PhoneNumber;
|
||||
@class SignalAccount;
|
||||
@ -20,6 +21,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (NSComparisonResult)compareSignalAccount:(SignalAccount *)left
|
||||
withSignalAccount:(SignalAccount *)right NS_SWIFT_NAME(compare(signalAccount:with:));
|
||||
|
||||
#pragma mark - CNContacts
|
||||
|
||||
- (nullable CNContact *)cnContactWithId:(nullable NSString *)contactId;
|
||||
- (nullable NSData *)avatarDataForCNContactId:(nullable NSString *)contactId;
|
||||
- (nullable UIImage *)avatarImageForCNContactId:(nullable NSString *)contactId;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
Loading…
Reference in New Issue
Block a user