diff --git a/src/Messages/Interactions/TSMessage.m b/src/Messages/Interactions/TSMessage.m index 0f5834b8..4b893c7b 100644 --- a/src/Messages/Interactions/TSMessage.m +++ b/src/Messages/Interactions/TSMessage.m @@ -1,5 +1,6 @@ -// Created by Frederic Jacobs on 12/11/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "TSMessage.h" #import "NSDate+millisecondTimeStamp.h" @@ -172,7 +173,7 @@ - (NSString *)debugDescription NSString *attachmentId = self.attachmentIds[0]; return [NSString stringWithFormat:@"Media Message with attachmentId:%@", attachmentId]; } else { - return [NSString stringWithFormat:@"%@ with body:%@", [self class], self.body]; + return [NSString stringWithFormat:@"%@ with body: %@", [self class], self.body]; } } diff --git a/src/Messages/OWSFailedMessagesJob.h b/src/Messages/OWSFailedMessagesJob.h new file mode 100644 index 00000000..49d8c67d --- /dev/null +++ b/src/Messages/OWSFailedMessagesJob.h @@ -0,0 +1,28 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@class TSStorageManager; + +@interface OWSFailedMessagesJob : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager NS_DESIGNATED_INITIALIZER; + +- (void)run; + +/** + * Database extensions required for class to work. + */ +- (void)asyncRegisterDatabaseExtensions; + +/** + * Only use the sync version for testing, generally we'll want to register extensions async + */ +- (void)blockingRegisterDatabaseExtensions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSFailedMessagesJob.m b/src/Messages/OWSFailedMessagesJob.m new file mode 100644 index 00000000..d787961e --- /dev/null +++ b/src/Messages/OWSFailedMessagesJob.m @@ -0,0 +1,151 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSFailedMessagesJob.h" +#import "TSMessage.h" +#import "TSOutgoingMessage.h" +#import "TSStorageManager.h" +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +static NSString *const OWSFailedMessagesJobMessageStateColumn = @"message_state"; +static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_messages_on_message_state"; + +@interface OWSFailedMessagesJob () + +@property (nonatomic, readonly) TSStorageManager *storageManager; + +@end + +@implementation OWSFailedMessagesJob + +- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager +{ + self = [super init]; + if (!self) { + return self; + } + + _storageManager = storageManager; + + return self; +} + +- (NSArray *)fetchAttemptingOutMessageIds:(YapDatabaseConnection *)dbConnection +{ + NSMutableArray *messageIds = [NSMutableArray new]; + + NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ == %d", + OWSFailedMessagesJobMessageStateColumn, + (int)TSOutgoingMessageStateAttemptingOut]; + YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString]; + [dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + [[transaction ext:OWSFailedMessagesJobMessageStateIndex] + enumerateKeysMatchingQuery:query + usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) { + [messageIds addObject:key]; + }]; + }]; + + return [messageIds copy]; +} + +- (void)enumerateAttemptingOutMessagesWithBlock:(void (^_Nonnull)(TSOutgoingMessage *message))block +{ + YapDatabaseConnection *dbConnection = [self.storageManager newDatabaseConnection]; + + // Since we can't directly mutate the enumerated "attempting out" expired messages, we store only their ids in hopes + // of saving a little memory and then enumerate the (larger) TSMessage objects one at a time. + for (NSString *expiredMessageId in [self fetchAttemptingOutMessageIds:dbConnection]) { + TSOutgoingMessage *_Nullable message = [TSOutgoingMessage fetchObjectWithUniqueID:expiredMessageId]; + if ([message isKindOfClass:[TSOutgoingMessage class]]) { + block(message); + } else { + DDLogError(@"%@ unexpected object: %@", self.tag, message); + } + } +} + +- (void)run +{ + __block uint count = 0; + [self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) { + // sanity check + OWSAssert(message.messageState == TSOutgoingMessageStateAttemptingOut); + if (message.messageState != TSOutgoingMessageStateAttemptingOut) { + DDLogError(@"%@ Refusing to mark as unsent message with state: %d", self.tag, (int)message.messageState); + return; + } + + DDLogDebug(@"%@ marking message as unsent", self.tag); + message.messageState = TSOutgoingMessageStateUnsent; + [message save]; + count++; + }]; + + DDLogDebug(@"%@ Marked %u messages as unsent", self.tag, count); +} + +#pragma mark - YapDatabaseExtension + +- (YapDatabaseSecondaryIndex *)indexDatabaseExtension +{ + YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new]; + [setup addColumn:OWSFailedMessagesJobMessageStateColumn withType:YapDatabaseSecondaryIndexTypeInteger]; + + YapDatabaseSecondaryIndexHandler *handler = + [YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction, + NSMutableDictionary *dict, + NSString *collection, + NSString *key, + id object) { + if (![object isKindOfClass:[TSOutgoingMessage class]]) { + return; + } + TSOutgoingMessage *message = (TSOutgoingMessage *)object; + + dict[OWSFailedMessagesJobMessageStateColumn] = @(message.messageState); + }]; + + return [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler]; +} + +// Useful for tests, don't use in app startup path because it's slow. +- (void)blockingRegisterDatabaseExtensions +{ + [self.storageManager.database registerExtension:[self indexDatabaseExtension] + withName:OWSFailedMessagesJobMessageStateIndex]; +} + +- (void)asyncRegisterDatabaseExtensions +{ + [self.storageManager.database asyncRegisterExtension:[self indexDatabaseExtension] + withName:OWSFailedMessagesJobMessageStateIndex + completionBlock:^(BOOL ready) { + if (ready) { + DDLogDebug(@"%@ completed registering extension async.", self.tag); + } else { + DDLogError(@"%@ failed registering extension async.", self.tag); + } + }]; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/Messages/OWSMessageSender.m b/src/Messages/OWSMessageSender.m index 4b1ee0ce..70ac96b9 100644 --- a/src/Messages/OWSMessageSender.m +++ b/src/Messages/OWSMessageSender.m @@ -409,6 +409,8 @@ - (void)sendMessage:(TSOutgoingMessage *)message success:(void (^)())successHandler failure:(void (^)(NSError *error))failureHandler { + DDLogDebug(@"%@ sending message to service: %@", self.tag, message.debugDescription); + if (remainingAttempts <= 0) { // We should always fail with a specific error. DDLogError(@"%@ Unexpected generic failure.", self.tag); @@ -463,6 +465,9 @@ - (void)sendMessage:(TSOutgoingMessage *)message }); } failure:^(NSURLSessionDataTask *task, NSError *error) { + DDLogDebug(@"%@ failure sending to service: %@", self.tag, message.debugDescription); + [DDLog flushLog]; + NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response; long statuscode = response.statusCode; NSData *responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; @@ -473,6 +478,7 @@ - (void)sendMessage:(TSOutgoingMessage *)message } dispatch_async([OWSDispatch sendingQueue], ^{ + DDLogDebug(@"%@ Retrying: %@", self.tag, message.debugDescription); [self sendMessage:message recipient:recipient thread:thread @@ -554,6 +560,9 @@ - (void)handleMessageSentLocally:(TSOutgoingMessage *)message { [self saveMessage:message withState:TSOutgoingMessageStateSent]; if (message.shouldSyncTranscript) { + // TODO: I suspect we shouldn't optimistically set hasSyncedTranscript. + // We could set this in a success handler for [sendSyncTranscriptForMessage:]. + message.hasSyncedTranscript = YES; [self sendSyncTranscriptForMessage:message]; } diff --git a/src/Storage/TSStorageManager.m b/src/Storage/TSStorageManager.m index 34903386..83303ec4 100644 --- a/src/Storage/TSStorageManager.m +++ b/src/Storage/TSStorageManager.m @@ -6,6 +6,7 @@ #import "NSData+Base64.h" #import "OWSAnalytics.h" #import "OWSDisappearingMessagesFinder.h" +#import "OWSFailedMessagesJob.h" #import "OWSReadReceipt.h" #import "SignalRecipient.h" #import "TSAttachmentStream.h" @@ -200,6 +201,8 @@ - (void)setupDatabase [OWSReadReceipt asyncRegisterIndexOnSenderIdAndTimestampWithDatabase:self.database]; OWSDisappearingMessagesFinder *finder = [[OWSDisappearingMessagesFinder alloc] initWithStorageManager:self]; [finder asyncRegisterDatabaseExtensions]; + OWSFailedMessagesJob *failedMessagesJob = [[OWSFailedMessagesJob alloc] initWithStorageManager:self]; + [failedMessagesJob asyncRegisterDatabaseExtensions]; } - (void)protectSignalFiles {