mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'mkirk/dedupe-incoming-messages'
This commit is contained in:
commit
168639597f
14 changed files with 393 additions and 73 deletions
|
@ -37,6 +37,7 @@
|
|||
45B840211D988DA100F9E938 /* OWSReadReceiptTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45B840201D988DA100F9E938 /* OWSReadReceiptTest.m */; };
|
||||
45C6A09A1D2F029B007D8AC0 /* TSMessageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45C6A0991D2F029B007D8AC0 /* TSMessageTest.m */; };
|
||||
45D7243F1D67899F00E0CA54 /* OWSDeviceProvisionerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45D7243E1D67899F00E0CA54 /* OWSDeviceProvisionerTest.m */; };
|
||||
45E741B61E5D14E800735842 /* OWSIncomingMessageFinderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45E741B51E5D14E800735842 /* OWSIncomingMessageFinderTest.m */; };
|
||||
51520592F83F2440F2DE4D67 /* libPods-TSKitiOSTestApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8362AB8E280E0F64352F08A /* libPods-TSKitiOSTestApp.a */; };
|
||||
6323E1F7730289398452E5C5 /* OWSFingerprintTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6323E02A33682A8838FE3F27 /* OWSFingerprintTest.m */; };
|
||||
6323E339D5B8F4CB77EB3ED4 /* SignalRecipientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6323E3E540CF763D71DACB59 /* SignalRecipientTest.m */; };
|
||||
|
@ -99,6 +100,7 @@
|
|||
45B840201D988DA100F9E938 /* OWSReadReceiptTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSReadReceiptTest.m; path = ../../../tests/Devices/OWSReadReceiptTest.m; sourceTree = "<group>"; };
|
||||
45C6A0991D2F029B007D8AC0 /* TSMessageTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSMessageTest.m; path = ../../../tests/Messages/Interactions/TSMessageTest.m; sourceTree = "<group>"; };
|
||||
45D7243E1D67899F00E0CA54 /* OWSDeviceProvisionerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDeviceProvisionerTest.m; path = ../../../tests/Devices/OWSDeviceProvisionerTest.m; sourceTree = "<group>"; };
|
||||
45E741B51E5D14E800735842 /* OWSIncomingMessageFinderTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSIncomingMessageFinderTest.m; path = ../../../tests/Messages/OWSIncomingMessageFinderTest.m; sourceTree = "<group>"; };
|
||||
6323E02A33682A8838FE3F27 /* OWSFingerprintTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSFingerprintTest.m; path = ../../../tests/Security/OWSFingerprintTest.m; sourceTree = "<group>"; };
|
||||
6323E3E540CF763D71DACB59 /* SignalRecipientTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SignalRecipientTest.m; path = ../../tests/Contacts/SignalRecipientTest.m; sourceTree = "<group>"; };
|
||||
B6273DD11C13A2E500738558 /* TSKitiOSTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TSKitiOSTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -237,6 +239,7 @@
|
|||
45046FDF1D95A6130015EFF2 /* TSMessagesManagerTest.m */,
|
||||
454021EC1D960ABF00F2126D /* OWSDisappearingMessageFinderTest.m */,
|
||||
453E1FCE1DA8313100DDD7B7 /* OWSMessageSenderTest.m */,
|
||||
45E741B51E5D14E800735842 /* OWSIncomingMessageFinderTest.m */,
|
||||
);
|
||||
name = Messages;
|
||||
sourceTree = "<group>";
|
||||
|
@ -561,6 +564,7 @@
|
|||
45D7243F1D67899F00E0CA54 /* OWSDeviceProvisionerTest.m in Sources */,
|
||||
4516E3E81DD153CC00DC4206 /* TSGroupThreadTest.m in Sources */,
|
||||
45458B791CC342B600A02153 /* TSStoragePreKeyStoreTests.m in Sources */,
|
||||
45E741B61E5D14E800735842 /* OWSIncomingMessageFinderTest.m in Sources */,
|
||||
452EE6D51D4AC43300E934BA /* OWSOrphanedDataCleanerTest.m in Sources */,
|
||||
450E3C9A1D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m in Sources */,
|
||||
452EE6CF1D4A754C00E934BA /* TSThreadTest.m in Sources */,
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TSYapDatabaseObject.h"
|
||||
#import <Mantle/MTLJSONAdapter.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern uint32_t const OWSDevicePrimaryDeviceId;
|
||||
|
||||
@interface OWSDevice : TSYapDatabaseObject <MTLJSONSerializing>
|
||||
|
||||
@property (nonatomic, readonly) NSInteger deviceId;
|
||||
|
@ -22,6 +26,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
*/
|
||||
+ (void)replaceAll:(NSArray<OWSDevice *> *)devices;
|
||||
|
||||
/**
|
||||
* The id of the device currently running this application
|
||||
*/
|
||||
+ (uint32_t)currentDeviceId;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transaction
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDevice.h"
|
||||
#import "NSDate+millisecondTimeStamp.h"
|
||||
|
@ -10,7 +12,7 @@
|
|||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
static MTLValueTransformer *_millisecondTimestampToDateTransformer;
|
||||
static int const OWSDevicePrimaryDeviceId = 1;
|
||||
uint32_t const OWSDevicePrimaryDeviceId = 1;
|
||||
|
||||
@interface OWSDevice ()
|
||||
|
||||
|
@ -108,6 +110,13 @@ static int const OWSDevicePrimaryDeviceId = 1;
|
|||
return _millisecondTimestampToDateTransformer;
|
||||
}
|
||||
|
||||
+ (uint32_t)currentDeviceId
|
||||
{
|
||||
// Someday it may be possible to have a non-primary iOS device, but for now
|
||||
// any iOS device must be the primary device.
|
||||
return OWSDevicePrimaryDeviceId;
|
||||
}
|
||||
|
||||
- (BOOL)isPrimaryDevice
|
||||
{
|
||||
return self.deviceId == OWSDevicePrimaryDeviceId;
|
||||
|
|
|
@ -23,6 +23,8 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
|
|||
* Thread to which the message belongs
|
||||
* @param authorId
|
||||
* Signal ID (i.e. e164) of the user who sent the message
|
||||
* @param sourceDeviceId
|
||||
* Numeric ID of the device used to send the message. Used to detect duplicate messages.
|
||||
* @param body
|
||||
* Body of the message
|
||||
*
|
||||
|
@ -31,30 +33,9 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
|
|||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
inThread:(TSThread *)thread
|
||||
authorId:(NSString *)authorId
|
||||
sourceDeviceId:(uint32_t)sourceDeviceId
|
||||
messageBody:(nullable NSString *)body;
|
||||
|
||||
/**
|
||||
* Inits an incoming group message with attachments
|
||||
*
|
||||
* @param timestamp
|
||||
* When the message was created in milliseconds since epoch
|
||||
* @param thread
|
||||
* Thread to which the message belongs
|
||||
* @param authorId
|
||||
* Signal ID (i.e. e164) of the user who sent the message
|
||||
* @param body
|
||||
* Body of the message
|
||||
* @param attachmentIds
|
||||
* The uniqueIds for the message's attachments
|
||||
*
|
||||
* @return initiated incoming group message
|
||||
*/
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
inThread:(TSThread *)thread
|
||||
authorId:(NSString *)authorId
|
||||
messageBody:(nullable NSString *)body
|
||||
attachmentIds:(NSArray<NSString *> *)attachmentIds;
|
||||
|
||||
/**
|
||||
* Inits an incoming group message that expires.
|
||||
*
|
||||
|
@ -64,6 +45,8 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
|
|||
* Thread to which the message belongs
|
||||
* @param authorId
|
||||
* Signal ID (i.e. e164) of the user who sent the message
|
||||
* @param sourceDeviceId
|
||||
* Numeric ID of the device used to send the message. Used to detect duplicate messages.
|
||||
* @param body
|
||||
* Body of the message
|
||||
* @param attachmentIds
|
||||
|
@ -76,6 +59,7 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
|
|||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
inThread:(TSThread *)thread
|
||||
authorId:(NSString *)authorId
|
||||
sourceDeviceId:(uint32_t)sourceDeviceId
|
||||
messageBody:(nullable NSString *)body
|
||||
attachmentIds:(NSArray<NSString *> *)attachmentIds
|
||||
expiresInSeconds:(uint32_t)expiresInSeconds NS_DESIGNATED_INITIALIZER;
|
||||
|
@ -84,8 +68,8 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
|
|||
|
||||
|
||||
/**
|
||||
* For sake of a smaller API, you must specify an author id for all incoming messages
|
||||
* though we technically could get the author id from a contact thread.
|
||||
* For sake of a smaller API, and simplifying assumptions elsewhere, you must specify an author id for *all* incoming
|
||||
* messages, even though we technically could infer the author id for a contact thread.
|
||||
*/
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp NS_UNAVAILABLE;
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(nullable TSThread *)thread NS_UNAVAILABLE;
|
||||
|
@ -120,6 +104,9 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
|
|||
+ (nullable instancetype)findMessageWithAuthorId:(NSString *)authorId timestamp:(uint64_t)timestamp;
|
||||
|
||||
@property (nonatomic, readonly) NSString *authorId;
|
||||
|
||||
// This will be 0 for messages created before we were tracking sourceDeviceId
|
||||
@property (nonatomic, readonly) UInt32 sourceDeviceId;
|
||||
@property (nonatomic, readonly, getter=wasRead) BOOL read;
|
||||
|
||||
/*
|
||||
|
|
|
@ -22,28 +22,22 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
|
|||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
inThread:(TSThread *)thread
|
||||
authorId:(NSString *)authorId
|
||||
sourceDeviceId:(uint32_t)sourceDeviceId
|
||||
messageBody:(nullable NSString *)body
|
||||
{
|
||||
return [self initWithTimestamp:timestamp inThread:thread authorId:authorId messageBody:body attachmentIds:@[]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
inThread:(TSThread *)thread
|
||||
authorId:(NSString *)authorId
|
||||
messageBody:(nullable NSString *)body
|
||||
attachmentIds:(NSArray<NSString *> *)attachmentIds
|
||||
{
|
||||
return [self initWithTimestamp:timestamp
|
||||
inThread:thread
|
||||
authorId:authorId
|
||||
sourceDeviceId:sourceDeviceId
|
||||
messageBody:body
|
||||
attachmentIds:attachmentIds
|
||||
attachmentIds:@[]
|
||||
expiresInSeconds:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
inThread:(TSThread *)thread
|
||||
authorId:(NSString *)authorId
|
||||
sourceDeviceId:(uint32_t)sourceDeviceId
|
||||
messageBody:(nullable NSString *)body
|
||||
attachmentIds:(NSArray<NSString *> *)attachmentIds
|
||||
expiresInSeconds:(uint32_t)expiresInSeconds
|
||||
|
@ -60,6 +54,7 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
|
|||
}
|
||||
|
||||
_authorId = authorId;
|
||||
_sourceDeviceId = sourceDeviceId;
|
||||
_read = NO;
|
||||
|
||||
OWSAssert(self.receivedAtDate);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#import "OWSMessageSender.h"
|
||||
#import "ContactsUpdater.h"
|
||||
#import "NSData+messagePadding.h"
|
||||
#import "OWSDevice.h"
|
||||
#import "OWSDisappearingMessagesJob.h"
|
||||
#import "OWSError.h"
|
||||
#import "OWSLegacyMessageServiceParams.h"
|
||||
|
@ -622,6 +623,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|||
[[TSIncomingMessage alloc] initWithTimestamp:(outgoingMessage.timestamp + 1)
|
||||
inThread:cThread
|
||||
authorId:[cThread contactIdentifier]
|
||||
sourceDeviceId:[OWSDevice currentDeviceId]
|
||||
messageBody:outgoingMessage.body
|
||||
attachmentIds:outgoingMessage.attachmentIds
|
||||
expiresInSeconds:outgoingMessage.expiresInSeconds];
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#import "OWSDisappearingMessagesConfiguration.h"
|
||||
#import "OWSDisappearingMessagesJob.h"
|
||||
#import "OWSError.h"
|
||||
#import "OWSIncomingMessageFinder.h"
|
||||
#import "OWSIncomingSentMessageTranscript.h"
|
||||
#import "OWSMessageSender.h"
|
||||
#import "OWSReadReceiptsProcessor.h"
|
||||
|
@ -46,6 +47,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic, readonly) TSStorageManager *storageManager;
|
||||
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
||||
@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob;
|
||||
@property (nonatomic, readonly) OWSIncomingMessageFinder *incomingMessageFinder;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -102,6 +104,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
_dbConnection = storageManager.newDatabaseConnection;
|
||||
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:storageManager];
|
||||
_incomingMessageFinder = [[OWSIncomingMessageFinder alloc] initWithDatabase:storageManager.database];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -281,6 +284,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)handleEnvelope:(OWSSignalServiceProtosEnvelope *)envelope plaintextData:(NSData *)plaintextData
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
OWSAssert(envelope.hasTimestamp && envelope.timestamp > 0);
|
||||
OWSAssert(envelope.hasSource && envelope.source.length > 0);
|
||||
OWSAssert(envelope.hasSourceDevice && envelope.sourceDevice > 0);
|
||||
|
||||
BOOL duplicateEnvelope = [self.incomingMessageFinder existsMessageWithTimestamp:envelope.timestamp
|
||||
sourceId:envelope.source
|
||||
sourceDeviceId:envelope.sourceDevice];
|
||||
if (duplicateEnvelope) {
|
||||
DDLogInfo(@"%@ Ignoring previously received envelope with timestamp: %llu", self.tag, envelope.timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (envelope.hasContent) {
|
||||
OWSSignalServiceProtosContent *content = [OWSSignalServiceProtosContent parseFromData:plaintextData];
|
||||
if (content.hasSyncMessage) {
|
||||
|
@ -290,7 +305,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
} else if (content.hasCallMessage) {
|
||||
[self handleIncomingEnvelope:envelope withCallMessage:content.callMessage];
|
||||
} else {
|
||||
DDLogWarn(@"%@ Ignoring envelope.Content with no known payload", self.tag);
|
||||
DDLogWarn(@"%@ Ignoring envelope. Content with no known payload", self.tag);
|
||||
}
|
||||
} else if (envelope.hasLegacyMessage) { // DEPRECATED - Remove after all clients have been upgraded.
|
||||
OWSSignalServiceProtosDataMessage *dataMessage =
|
||||
|
@ -611,6 +626,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timestamp
|
||||
inThread:gThread
|
||||
authorId:envelope.source
|
||||
sourceDeviceId:envelope.sourceDevice
|
||||
messageBody:body
|
||||
attachmentIds:attachmentIds
|
||||
expiresInSeconds:dataMessage.expireTimer];
|
||||
|
@ -632,6 +648,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timestamp
|
||||
inThread:cThread
|
||||
authorId:[cThread contactIdentifier]
|
||||
sourceDeviceId:envelope.sourceDevice
|
||||
messageBody:body
|
||||
attachmentIds:attachmentIds
|
||||
expiresInSeconds:dataMessage.expireTimer];
|
||||
|
@ -648,25 +665,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[incomingMessage markAsReadLocallyWithTransaction:transaction];
|
||||
}
|
||||
|
||||
// Android allows attachments to be sent with body.
|
||||
// Other clients allow attachments to be sent along with body, we want the text displayed as a separate
|
||||
// message
|
||||
if ([attachmentIds count] > 0 && body != nil && ![body isEqualToString:@""]) {
|
||||
// We want the text to be displayed under the attachment
|
||||
uint64_t textMessageTimestamp = timestamp + 1;
|
||||
TSIncomingMessage *textMessage;
|
||||
if ([thread isGroupThread]) {
|
||||
TSGroupThread *gThread = (TSGroupThread *)thread;
|
||||
textMessage = [[TSIncomingMessage alloc] initWithTimestamp:textMessageTimestamp
|
||||
inThread:gThread
|
||||
authorId:envelope.source
|
||||
messageBody:body];
|
||||
} else {
|
||||
TSContactThread *cThread = (TSContactThread *)thread;
|
||||
textMessage = [[TSIncomingMessage alloc] initWithTimestamp:textMessageTimestamp
|
||||
inThread:cThread
|
||||
authorId:[cThread contactIdentifier]
|
||||
messageBody:body];
|
||||
}
|
||||
textMessage.expiresInSeconds = dataMessage.expireTimer;
|
||||
TSIncomingMessage *textMessage = [[TSIncomingMessage alloc] initWithTimestamp:textMessageTimestamp
|
||||
inThread:thread
|
||||
authorId:envelope.source
|
||||
sourceDeviceId:envelope.sourceDevice
|
||||
messageBody:body
|
||||
attachmentIds:@[]
|
||||
expiresInSeconds:dataMessage.expireTimer];
|
||||
[textMessage saveWithTransaction:transaction];
|
||||
}
|
||||
}
|
||||
|
|
28
src/Storage/OWSIncomingMessageFinder.h
Normal file
28
src/Storage/OWSIncomingMessageFinder.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YapDatabase;
|
||||
@class YapDatabaseReadTransaction;
|
||||
|
||||
@interface OWSIncomingMessageFinder : NSObject
|
||||
|
||||
- (instancetype)initWithDatabase:(YapDatabase *)database NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Must be called before using this finder.
|
||||
*/
|
||||
- (void)asyncRegisterExtension;
|
||||
|
||||
/**
|
||||
* Detects existance of a duplicate incoming message.
|
||||
*/
|
||||
- (BOOL)existsMessageWithTimestamp:(uint64_t)timestamp
|
||||
sourceId:(NSString *)sourceId
|
||||
sourceDeviceId:(uint32_t)sourceDeviceId;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
159
src/Storage/OWSIncomingMessageFinder.m
Normal file
159
src/Storage/OWSIncomingMessageFinder.m
Normal file
|
@ -0,0 +1,159 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSIncomingMessageFinder.h"
|
||||
#import "TSIncomingMessage.h"
|
||||
#import "TSStorageManager.h"
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
#import <YapDatabase/YapDatabaseSecondaryIndex.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString *const OWSIncomingMessageFinderExtensionName = @"OWSIncomingMessageFinderExtensionName";
|
||||
|
||||
NSString *const OWSIncomingMessageFinderColumnTimestamp = @"OWSIncomingMessageFinderColumnTimestamp";
|
||||
NSString *const OWSIncomingMessageFinderColumnSourceId = @"OWSIncomingMessageFinderColumnSourceId";
|
||||
NSString *const OWSIncomingMessageFinderColumnSourceDeviceId = @"OWSIncomingMessageFinderColumnSourceDeviceId";
|
||||
|
||||
@interface OWSIncomingMessageFinder ()
|
||||
|
||||
@property (nonatomic, readonly) YapDatabase *database;
|
||||
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSIncomingMessageFinder
|
||||
|
||||
@synthesize dbConnection = _dbConnection;
|
||||
|
||||
#pragma mark - init
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
OWSAssert([TSStorageManager sharedManager].database != nil);
|
||||
|
||||
return [self initWithDatabase:[TSStorageManager sharedManager].database];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDatabase:(YapDatabase *)database
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_database = database;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - properties
|
||||
|
||||
- (YapDatabaseConnection *)dbConnection
|
||||
{
|
||||
@synchronized (self) {
|
||||
if (!_dbConnection) {
|
||||
_dbConnection = self.database.newConnection;
|
||||
}
|
||||
}
|
||||
return _dbConnection;
|
||||
}
|
||||
|
||||
#pragma mark - YAP integration
|
||||
|
||||
- (YapDatabaseSecondaryIndex *)indexExtension
|
||||
{
|
||||
YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new];
|
||||
|
||||
[setup addColumn:OWSIncomingMessageFinderColumnTimestamp withType:YapDatabaseSecondaryIndexTypeInteger];
|
||||
[setup addColumn:OWSIncomingMessageFinderColumnSourceId withType:YapDatabaseSecondaryIndexTypeText];
|
||||
[setup addColumn:OWSIncomingMessageFinderColumnSourceDeviceId withType:YapDatabaseSecondaryIndexTypeInteger];
|
||||
|
||||
YapDatabaseSecondaryIndexWithObjectBlock block = ^(YapDatabaseReadTransaction *transaction,
|
||||
NSMutableDictionary *dict,
|
||||
NSString *collection,
|
||||
NSString *key,
|
||||
id object) {
|
||||
if ([object isKindOfClass:[TSIncomingMessage class]]) {
|
||||
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)object;
|
||||
|
||||
[dict setObject:@(incomingMessage.timestamp) forKey:OWSIncomingMessageFinderColumnTimestamp];
|
||||
[dict setObject:incomingMessage.authorId forKey:OWSIncomingMessageFinderColumnSourceId];
|
||||
[dict setObject:@(incomingMessage.sourceDeviceId) forKey:OWSIncomingMessageFinderColumnSourceDeviceId];
|
||||
}
|
||||
};
|
||||
|
||||
YapDatabaseSecondaryIndexHandler *handler = [YapDatabaseSecondaryIndexHandler withObjectBlock:block];
|
||||
|
||||
return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler];
|
||||
}
|
||||
|
||||
- (void)asyncRegisterExtension
|
||||
{
|
||||
DDLogInfo(@"%@ registering async.", self.tag);
|
||||
[self.database asyncRegisterExtension:self.indexExtension
|
||||
withName:OWSIncomingMessageFinderExtensionName
|
||||
completionBlock:^(BOOL ready) {
|
||||
DDLogInfo(@"%@ finished registering async.", self.tag);
|
||||
}];
|
||||
}
|
||||
|
||||
// We should not normally hit this, as we should have prefer registering async, but it is useful for testing.
|
||||
- (void)registerExtension
|
||||
{
|
||||
DDLogError(@"%@ registering SYNC. We should prefer async when possible.", self.tag);
|
||||
[self.database registerExtension:self.indexExtension withName:OWSIncomingMessageFinderExtensionName];
|
||||
}
|
||||
|
||||
#pragma mark - instance methods
|
||||
|
||||
- (BOOL)existsMessageWithTimestamp:(uint64_t)timestamp
|
||||
sourceId:(NSString *)sourceId
|
||||
sourceDeviceId:(uint32_t)sourceDeviceId
|
||||
{
|
||||
if (![self.database registeredExtension:OWSIncomingMessageFinderExtensionName]) {
|
||||
DDLogError(@"%@ in %s but extension is not registered", self.tag, __PRETTY_FUNCTION__);
|
||||
OWSAssert(NO);
|
||||
|
||||
// we should be initializing this at startup rather than have an unexpectedly slow lazy setup at random.
|
||||
[self registerExtension];
|
||||
}
|
||||
|
||||
NSString *queryFormat = [NSString stringWithFormat:@"WHERE %@ = ? AND %@ = ? AND %@ = ?",
|
||||
OWSIncomingMessageFinderColumnTimestamp,
|
||||
OWSIncomingMessageFinderColumnSourceId,
|
||||
OWSIncomingMessageFinderColumnSourceDeviceId];
|
||||
// YapDatabaseQuery params must be objects
|
||||
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:queryFormat, @(timestamp), sourceId, @(sourceDeviceId)];
|
||||
|
||||
__block NSUInteger count;
|
||||
__block BOOL success;
|
||||
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
||||
success = [[transaction ext:OWSIncomingMessageFinderExtensionName] getNumberOfRows:&count matchingQuery:query];
|
||||
}];
|
||||
|
||||
if (!success) {
|
||||
OWSAssert(NO);
|
||||
return NO;
|
||||
}
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
+ (NSString *)tag
|
||||
{
|
||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||
}
|
||||
|
||||
- (NSString *)tag
|
||||
{
|
||||
return self.class.tag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -7,6 +7,7 @@
|
|||
#import "OWSAnalytics.h"
|
||||
#import "OWSDisappearingMessagesFinder.h"
|
||||
#import "OWSFailedMessagesJob.h"
|
||||
#import "OWSIncomingMessageFinder.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "SignalRecipient.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
|
@ -197,6 +198,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
|
|||
[self.database registerExtension:[TSDatabaseSecondaryIndexes registerTimeStampIndex] withName:@"idx"];
|
||||
|
||||
// Register extensions which aren't essential for rendering threads async
|
||||
[[OWSIncomingMessageFinder new] asyncRegisterExtension];
|
||||
[TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView];
|
||||
[OWSReadReceipt asyncRegisterIndexOnSenderIdAndTimestampWithDatabase:self.database];
|
||||
OWSDisappearingMessagesFinder *finder = [[OWSDisappearingMessagesFinder alloc] initWithStorageManager:self];
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDevice.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSContactThread.h"
|
||||
#import "TSIncomingMessage.h"
|
||||
|
@ -38,6 +41,7 @@
|
|||
TSIncomingMessage *incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:10000
|
||||
inThread:thread
|
||||
authorId:@"fake-author-id"
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"Incoming message body"];
|
||||
[incomingMessage save];
|
||||
|
||||
|
@ -70,12 +74,13 @@
|
|||
BOOL incomingFileWasCreated = [[NSFileManager defaultManager] fileExistsAtPath:[incomingAttachment filePath]];
|
||||
XCTAssert(incomingFileWasCreated);
|
||||
|
||||
TSIncomingMessage *incomingMessage =
|
||||
[[TSIncomingMessage alloc] initWithTimestamp:10000
|
||||
inThread:thread
|
||||
authorId:@"fake-author-id"
|
||||
messageBody:@"incoming message body"
|
||||
attachmentIds:[NSMutableArray arrayWithObject:incomingAttachment.uniqueId]];
|
||||
TSIncomingMessage *incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:10000
|
||||
inThread:thread
|
||||
authorId:@"fake-author-id"
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"incoming message body"
|
||||
attachmentIds:@[ incomingAttachment.uniqueId ]
|
||||
expiresInSeconds:0];
|
||||
[incomingMessage save];
|
||||
|
||||
TSAttachmentStream *outgoingAttachment = [[TSAttachmentStream alloc] initWithContentType:@"image/jpeg"];
|
||||
|
@ -86,11 +91,10 @@
|
|||
BOOL outgoingFileWasCreated = [[NSFileManager defaultManager] fileExistsAtPath:[outgoingAttachment filePath]];
|
||||
XCTAssert(outgoingFileWasCreated);
|
||||
|
||||
TSOutgoingMessage *outgoingMessage =
|
||||
[[TSOutgoingMessage alloc] initWithTimestamp:10000
|
||||
inThread:thread
|
||||
messageBody:@"outgoing message body"
|
||||
attachmentIds:[NSMutableArray arrayWithObject:outgoingAttachment.uniqueId]];
|
||||
TSOutgoingMessage *outgoingMessage = [[TSOutgoingMessage alloc] initWithTimestamp:10000
|
||||
inThread:thread
|
||||
messageBody:@"outgoing message body"
|
||||
attachmentIds:@[ outgoingAttachment.uniqueId ]];
|
||||
[outgoingMessage save];
|
||||
|
||||
// Sanity check
|
||||
|
|
105
tests/Messages/OWSIncomingMessageFinderTest.m
Normal file
105
tests/Messages/OWSIncomingMessageFinderTest.m
Normal file
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDevice.h"
|
||||
#import "OWSIncomingMessageFinder.h"
|
||||
#import "TSContactThread.h"
|
||||
#import "TSIncomingMessage.h"
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSIncomingMessageFinder (Testing)
|
||||
|
||||
- (void)registerExtension;
|
||||
|
||||
@end
|
||||
|
||||
@interface OWSIncomingMessageFinderTest : XCTestCase
|
||||
|
||||
@property (nonatomic) NSString *sourceId;
|
||||
@property (nonatomic) TSThread *thread;
|
||||
@property (nonatomic) OWSIncomingMessageFinder *finder;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSIncomingMessageFinderTest
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
self.sourceId = @"some-source-id";
|
||||
self.thread = [TSContactThread getOrCreateThreadWithContactId:self.sourceId];
|
||||
self.finder = [OWSIncomingMessageFinder new];
|
||||
[self.finder registerExtension];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testExistingMessages
|
||||
{
|
||||
|
||||
uint64_t timestamp = 1234;
|
||||
BOOL result = [self.finder existsMessageWithTimestamp:timestamp
|
||||
sourceId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId];
|
||||
|
||||
// Sanity check.
|
||||
XCTAssertFalse(result);
|
||||
|
||||
// Different timestamp
|
||||
[[[TSIncomingMessage alloc] initWithTimestamp:timestamp + 1
|
||||
inThread:self.thread
|
||||
authorId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"foo"] save];
|
||||
result = [self.finder existsMessageWithTimestamp:timestamp
|
||||
sourceId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId];
|
||||
XCTAssertFalse(result);
|
||||
|
||||
// Different authorId
|
||||
[[[TSIncomingMessage alloc] initWithTimestamp:timestamp
|
||||
inThread:self.thread
|
||||
authorId:@"some-other-author-id"
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"foo"] save];
|
||||
|
||||
result = [self.finder existsMessageWithTimestamp:timestamp
|
||||
sourceId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId];
|
||||
XCTAssertFalse(result);
|
||||
|
||||
// Different device
|
||||
[[[TSIncomingMessage alloc] initWithTimestamp:timestamp
|
||||
inThread:self.thread
|
||||
authorId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId + 1
|
||||
messageBody:@"foo"] save];
|
||||
|
||||
result = [self.finder existsMessageWithTimestamp:timestamp
|
||||
sourceId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId];
|
||||
XCTAssertFalse(result);
|
||||
|
||||
// The real deal...
|
||||
[[[TSIncomingMessage alloc] initWithTimestamp:timestamp
|
||||
inThread:self.thread
|
||||
authorId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"foo"] save];
|
||||
|
||||
result = [self.finder existsMessageWithTimestamp:timestamp
|
||||
sourceId:self.sourceId
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId];
|
||||
XCTAssertTrue(result);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,5 +1,8 @@
|
|||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDevice.h"
|
||||
#import "OWSOrphanedDataCleaner.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSContactThread.h"
|
||||
|
@ -45,6 +48,7 @@
|
|||
TSIncomingMessage *incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:1
|
||||
inThread:unsavedThread
|
||||
authorId:@"fake-author-id"
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"footch"];
|
||||
[incomingMessage save];
|
||||
XCTAssertEqual(1, [TSIncomingMessage numberOfKeysInCollection]);
|
||||
|
@ -61,6 +65,7 @@
|
|||
TSIncomingMessage *incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:1
|
||||
inThread:savedThread
|
||||
authorId:@"fake-author-id"
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"footch"];
|
||||
[incomingMessage save];
|
||||
XCTAssertEqual(1, [TSIncomingMessage numberOfKeysInCollection]);
|
||||
|
@ -99,8 +104,10 @@
|
|||
TSIncomingMessage *incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:1
|
||||
inThread:savedThread
|
||||
authorId:@"fake-author-id"
|
||||
sourceDeviceId:OWSDevicePrimaryDeviceId
|
||||
messageBody:@"footch"
|
||||
attachmentIds:@[ attachmentStream.uniqueId ]];
|
||||
attachmentIds:@[ attachmentStream.uniqueId ]
|
||||
expiresInSeconds:0];
|
||||
[incomingMessage save];
|
||||
|
||||
NSString *attachmentFilePath = [attachmentStream filePath];
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
//
|
||||
// TSMessageStorageTests.m
|
||||
// TextSecureKit
|
||||
//
|
||||
// Created by Frederic Jacobs on 16/11/14.
|
||||
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
@ -102,6 +98,7 @@
|
|||
TSIncomingMessage *newMessage = [[TSIncomingMessage alloc] initWithTimestamp:timestamp
|
||||
inThread:self.thread
|
||||
authorId:[self.thread contactIdentifier]
|
||||
sourceDeviceId:1
|
||||
messageBody:body];
|
||||
[[TSStorageManager sharedManager].newDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[newMessage saveWithTransaction:transaction];
|
||||
|
@ -126,6 +123,7 @@
|
|||
TSIncomingMessage *newMessage = [[TSIncomingMessage alloc] initWithTimestamp:i
|
||||
inThread:self.thread
|
||||
authorId:[self.thread contactIdentifier]
|
||||
sourceDeviceId:1
|
||||
messageBody:body];
|
||||
[messages addObject:newMessage];
|
||||
[newMessage save];
|
||||
|
@ -174,6 +172,7 @@
|
|||
TSIncomingMessage *newMessage = [[TSIncomingMessage alloc] initWithTimestamp:i
|
||||
inThread:thread
|
||||
authorId:@"Ed"
|
||||
sourceDeviceId:1
|
||||
messageBody:body];
|
||||
[newMessage save];
|
||||
[messages addObject:newMessage];
|
||||
|
|
Loading…
Reference in a new issue