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:
parent
fe7171dd93
commit
98d1c59bfc
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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]];
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
extern NSString *const OWSMimeTypeApplicationOctetStream;
|
||||
|
||||
@interface MIMETypeUtil : NSObject
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue