Sync Contacts with Desktop

* TODO refactor attachment sending to work without thread/message
* TODO de-dupe attachment pointer building code

// FREEBIE
This commit is contained in:
Michael Kirk 2016-08-25 19:01:35 -04:00
parent fe7171dd93
commit 98d1c59bfc
10 changed files with 240 additions and 43 deletions

View File

@ -39,27 +39,18 @@ NS_ASSUME_NONNULL_BEGIN
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
{
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
OWSSignalServiceProtosSyncMessageSentBuilder *sentBuilder = [OWSSignalServiceProtosSyncMessageSentBuilder new];
[sentBuilder setTimestamp:self.message.timestamp];
[sentBuilder setDestination:self.message.recipientIdentifier];
[sentBuilder setMessage:[self.message buildDataMessage]];
OWSSignalServiceProtosDataMessage *dataMessage = [self.message buildDataMessage];
[sentBuilder setMessage:dataMessage];
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
[syncMessageBuilder setSent:[sentBuilder build]];
return [syncMessageBuilder build];
}
- (NSData *)buildPlainTextData
{
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
[contentBuilder setSyncMessage:[self buildSyncMessage]];
return [[contentBuilder build] data];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,6 +1,7 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSOutgoingSyncMessage.h"
#import "OWSSignalServiceProtos.pb.h"
NS_ASSUME_NONNULL_BEGIN
@ -11,6 +12,23 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
{
NSAssert(NO, @"buildSyncMessage must be overridden in suclass");
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
return [syncMessageBuilder build];
}
- (NSData *)buildPlainTextData
{
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
[contentBuilder setSyncMessage:[self buildSyncMessage]];
return [[contentBuilder build] data];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,12 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "ContactsManagerProtocol.h"
#import "OWSOutgoingSyncMessage.h"
@interface OWSSyncContactsMessage : OWSOutgoingSyncMessage
- (instancetype)initWithContactsManager:(id<ContactsManagerProtocol>)contactsManager;
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (NSData *)buildPlainTextAttachmentData;
@end

View File

@ -0,0 +1,124 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSSyncContactsMessage.h"
#import "Contact.h"
#import "NSDate+millisecondTimeStamp.h"
#import "OWSSignalServiceProtos.pb.h"
#import "TSAttachment.h"
#import "TSAttachmentStream.h"
#import <ProtocolBuffers/CodedOutputStream.h>
@interface OWSSyncContactsMessage ()
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
@end
@implementation OWSSyncContactsMessage
- (instancetype)initWithContactsManager:(id<ContactsManagerProtocol>)contactsManager
{
self = [super initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:nil messageBody:nil attachmentIds:@[]];
if (!self) {
return self;
}
_contactsManager = contactsManager;
return self;
}
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
// no-op
// There's no need to save this message, since it's not displayed to the user.
// Furthermore if we did save it, we probably don't want to save the conctactsManager property.
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
{
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
if (self.attachmentIds.count != 1) {
DDLogError(@"expected sync contact message to have exactly one attachment, but found %lu",
(unsigned long)self.attachmentIds.count);
}
TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:self.attachmentIds[0]];
OWSSignalServiceProtosAttachmentPointerBuilder *attachmentBuilder =
[OWSSignalServiceProtosAttachmentPointerBuilder new];
[attachmentBuilder setId:[attachment.identifier unsignedLongLongValue]];
[attachmentBuilder setContentType:attachment.contentType];
[attachmentBuilder setKey:attachment.encryptionKey];
OWSSignalServiceProtosSyncMessageContactsBuilder *contactsBuilder =
[OWSSignalServiceProtosSyncMessageContactsBuilder new];
[contactsBuilder setBlob:[attachmentBuilder build]];
[syncMessageBuilder setContacts:[contactsBuilder build]];
return [syncMessageBuilder build];
}
- (NSData *)buildPlainTextAttachmentData
{
NSString *fileName =
[NSString stringWithFormat:@"%@_%@", [[NSProcessInfo processInfo] globallyUniqueString], @"contacts.dat"];
NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
NSOutputStream *fileOutputStream = [NSOutputStream outputStreamWithURL:fileURL append:NO];
[fileOutputStream open];
PBCodedOutputStream *outputStream = [PBCodedOutputStream streamWithOutputStream:fileOutputStream];
DDLogInfo(@"Writing contacts data to %@", fileURL);
for (Contact *contact in self.contactsManager.signalContacts) {
OWSSignalServiceProtosContactDetailsBuilder *contactBuilder = [OWSSignalServiceProtosContactDetailsBuilder new];
[contactBuilder setName:contact.fullName];
[contactBuilder setNumber:contact.textSecureIdentifiers.firstObject];
NSData *avatarPng;
if (contact.image) {
OWSSignalServiceProtosContactDetailsAvatarBuilder *avatarBuilder =
[OWSSignalServiceProtosContactDetailsAvatarBuilder new];
[avatarBuilder setContentType:@"image/png"];
avatarPng = UIImagePNGRepresentation(contact.image);
// TODO check datasize and safely cast to int
[avatarBuilder setLength:(uint32_t)avatarPng.length];
[contactBuilder setAvatar:[avatarBuilder build]];
}
NSData *contactData = [[contactBuilder build] data];
uint32_t contactDataLength = (uint32_t)contactData.length;
[outputStream writeRawVarint32:contactDataLength];
[outputStream writeRawData:contactData];
if (contact.image) {
[outputStream writeRawData:avatarPng];
}
}
[outputStream flush];
[fileOutputStream close];
// TODO pass stream to builder rather than data as a singular hulk.
[NSInputStream inputStreamWithURL:fileURL];
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:fileURL options:NSDataReadingMappedIfSafe error:&error];
if (error) {
DDLogError(@"Failed to read back contact data after writing it to %@ with error:%@", fileURL, error);
}
return data;
// TODO delete contacts file.
// NSError *error;
// NSFileManager *manager = [NSFileManager defaultManager];
// [manager removeItemAtURL:fileURL error:&error];
// if (error) {
// DDLogError(@"Failed removing temp file at url:%@ with error:%@", fileURL, error);
// }
}
@end

View File

@ -85,25 +85,19 @@ dispatch_queue_t attachmentsQueue() {
TSAttachmentEncryptionResult *result =
[Cryptography encryptAttachment:attachmentData contentType:contentType identifier:attachmentId];
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
result.pointer.isDownloaded = NO;
[result.pointer saveWithTransaction:transaction];
}];
result.pointer.isDownloaded = NO;
[result.pointer save];
outgoingMessage.body = nil;
[outgoingMessage.attachmentIds addObject:attachmentId];
if (outgoingMessage.groupMetaMessage != TSGroupMessageNew &&
outgoingMessage.groupMetaMessage != TSGroupMessageUpdate) {
[outgoingMessage setMessageState:TSOutgoingMessageStateAttemptingOut];
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[outgoingMessage saveWithTransaction:transaction];
}];
[outgoingMessage save];
}
BOOL success = [self uploadDataWithProgress:result.body location:location attachmentID:attachmentId];
if (success) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
result.pointer.isDownloaded = YES;
[result.pointer saveWithTransaction:transaction];
}];
result.pointer.isDownloaded = YES;
[result.pointer save];
[self sendMessage:outgoingMessage
inThread:thread
success:^{
@ -248,7 +242,7 @@ dispatch_queue_t attachmentsQueue() {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
[manager.requestSerializer setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
[manager.requestSerializer setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.completionQueue = dispatch_get_main_queue();
@ -285,7 +279,7 @@ dispatch_queue_t attachmentsQueue() {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:location]];
request.HTTPMethod = @"PUT";
request.HTTPBody = cipherText;
[request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
[request setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]
initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

View File

@ -122,16 +122,23 @@ dispatch_queue_t sendingQueue() {
[self saveMessage:message withState:TSOutgoingMessageStateUnsent];
}];
} else if ([thread isKindOfClass:[TSContactThread class]]) {
} else if ([thread isKindOfClass:[TSContactThread class]] ||
[message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
[self saveMessage:message withState:TSOutgoingMessageStateAttemptingOut];
if (![contactThread.contactIdentifier isEqualToString:[TSAccountManager localNumber]]) {
if (![contactThread.contactIdentifier isEqualToString:[TSAccountManager localNumber]] ||
[message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
NSString *recipientContactId = [message isKindOfClass:[OWSOutgoingSyncMessage class]]
? [TSAccountManager localNumber]
: contactThread.contactIdentifier;
__block SignalRecipient *recipient;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
recipient = [SignalRecipient recipientWithTextSecureIdentifier:contactThread.contactIdentifier
withTransaction:transaction];
recipient = [SignalRecipient recipientWithTextSecureIdentifier:recipientContactId
withTransaction:transaction];
}];
if (!recipient) {

View File

@ -2,8 +2,11 @@
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
#import "TSMessagesManager.h"
#import "ContactsManagerProtocol.h"
#import "MimeTypeUtil.h"
#import "NSData+messagePadding.h"
#import "OWSIncomingSentMessageTranscript.h"
#import "OWSSyncContactsMessage.h"
#import "TSAccountManager.h"
#import "TSAttachmentStream.h"
#import "TSCall.h"
@ -19,10 +22,6 @@
#import <AxolotlKit/AxolotlExceptions.h>
#import <AxolotlKit/SessionCipher.h>
@interface TSMessagesManager ()
@end
@implementation TSMessagesManager
+ (instancetype)sharedManager {
@ -231,10 +230,33 @@
withSyncMessage:(OWSSignalServiceProtosSyncMessage *)syncMessage
{
if (syncMessage.hasSent) {
DDLogInfo(@"Received sent message transcription");
DDLogInfo(@"Received `sent` syncMessage, recording message transcript.");
OWSIncomingSentMessageTranscript *transcript =
[[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent relay:messageEnvelope.relay];
[transcript record];
}
if (syncMessage.hasRequest) {
if (syncMessage.request.type == OWSSignalServiceProtosSyncMessageRequestTypeContacts) {
DDLogInfo(@"Received Contacts `request` syncMessage.");
OWSSyncContactsMessage *syncContactsMessage =
[[OWSSyncContactsMessage alloc] initWithContactsManager:[TextSecureKitEnv sharedEnv].contactsManager];
[self sendAttachment:[syncContactsMessage buildPlainTextAttachmentData]
contentType:OWSMimeTypeApplicationOctetStream
inMessage:syncContactsMessage
thread:nil
success:^{
DDLogInfo(@"Successfully sent Contacts response syncMessage.");
}
failure:^{
DDLogError(@"Failed to send Contacts response syncMessage.");
}];
} else if (syncMessage.request.type == OWSSignalServiceProtosSyncMessageRequestTypeGroups) {
DDLogInfo(@"Received Contacts `groups` syncMessage.");
// TODO
}
} else {
DDLogWarn(@"Ignoring unsupported sync message.");
}

View File

@ -1,18 +1,13 @@
//
// ContactsManagerProtocol.h
// Pods
//
// Created by Frederic Jacobs on 05/12/15.
//
//
#import <Foundation/Foundation.h>
// Copyright © 2016 Open Whisper Systems. All rights reserved.
@class PhoneNumber;
@class Contact;
@protocol ContactsManagerProtocol <NSObject>
- (NSString *)nameStringForPhoneIdentifier:(NSString *)phoneNumber;
- (NSArray<Contact *> *)signalContacts;
+ (BOOL)name:(NSString *)nameString matchesQuery:(NSString *)queryString;
#if TARGET_OS_IPHONE

View File

@ -1,4 +1,6 @@
#import <Foundation/Foundation.h>
// Copyright © 2016 Open Whisper Systems. All rights reserved.
extern NSString *const OWSMimeTypeApplicationOctetStream;
@interface MIMETypeUtil : NSObject

View File

@ -3,6 +3,7 @@
#import "UIImage+contentTypes.h"
#endif
NSString *const OWSMimeTypeApplicationOctetStream = @"application/octet-stream";
@implementation MIMETypeUtil
@ -56,6 +57,13 @@
};
}
+ (NSDictionary *)supportedBinaryDataMIMETypesToExtensionTypes
{
return @{
OWSMimeTypeApplicationOctetStream : @"dat",
};
}
+ (NSDictionary *)supportedVideoExtensionTypesToMIMETypes {
return @{
@"3gp" : @"video/3gpp",
@ -130,6 +138,11 @@
return [[self supportedAnimatedMIMETypesToExtensionTypes] objectForKey:contentType] != nil;
}
+ (BOOL)isSupportedBinaryDataMIMEType:(NSString *)contentType
{
return [[self supportedBinaryDataMIMETypesToExtensionTypes] objectForKey:contentType] != nil;
}
+ (BOOL)isSupportedMIMEType:(NSString *)contentType {
return [self isSupportedImageMIMEType:contentType] || [self isSupportedAudioMIMEType:contentType] ||
[self isSupportedVideoMIMEType:contentType] || [self isSupportedAnimatedMIMEType:contentType];
@ -167,6 +180,11 @@
return [[self supportedAnimatedMIMETypesToExtensionTypes] objectForKey:supportedMIMEType];
}
+ (NSString *)getSupportedExtensionFromBinaryDataMIMEType:(NSString *)supportedMIMEType
{
return [[self supportedBinaryDataMIMETypesToExtensionTypes] objectForKey:supportedMIMEType];
}
+ (NSString *)getSupportedMIMETypeFromVideoFile:(NSString *)supportedVideoFile {
return [[self supportedVideoExtensionTypesToMIMETypes] objectForKey:[supportedVideoFile pathExtension]];
}
@ -187,6 +205,12 @@
+ (BOOL)isAnimated:(NSString *)contentType {
return [MIMETypeUtil isSupportedAnimatedMIMEType:contentType];
}
+ (BOOL)isBinaryData:(NSString *)contentType
{
return [MIMETypeUtil isSupportedBinaryDataMIMEType:contentType];
}
+ (BOOL)isImage:(NSString *)contentType {
return [MIMETypeUtil isSupportedImageMIMEType:contentType];
}
@ -210,6 +234,8 @@
return [MIMETypeUtil filePathForImage:uniqueId ofMIMEType:contentType inFolder:folder];
} else if ([self isAnimated:contentType]) {
return [MIMETypeUtil filePathForAnimated:uniqueId ofMIMEType:contentType inFolder:folder];
} else if ([self isBinaryData:contentType]) {
return [MIMETypeUtil filePathForBinaryData:uniqueId ofMIMEType:contentType inFolder:folder];
}
DDLogError(@"Got asked for path of file %@ which is unsupported", contentType);
@ -257,6 +283,12 @@
stringByAppendingPathExtension:[self getSupportedExtensionFromAnimatedMIMEType:contentType]];
}
+ (NSString *)filePathForBinaryData:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder
{
return [[folder stringByAppendingFormat:@"/%@", uniqueId]
stringByAppendingPathExtension:[self getSupportedExtensionFromBinaryDataMIMEType:contentType]];
}
#if TARGET_OS_IPHONE
+ (NSString *)getSupportedImageMIMETypeFromImage:(UIImage *)image {