mirror of https://github.com/oxen-io/session-ios
Merge branch 'mkirk/dedupe-incoming-messages'
commit
168639597f
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
Loading…
Reference in New Issue