Add OWSAttachmentDownloads.

This commit is contained in:
Matthew Chen 2018-11-07 17:49:25 -05:00
parent b25f17a27c
commit 3daf7d4744
16 changed files with 458 additions and 205 deletions

View File

@ -86,7 +86,6 @@
#import <SignalServiceKit/NSTimer+OWS.h>
#import <SignalServiceKit/OWSAnalytics.h>
#import <SignalServiceKit/OWSAnalyticsEvents.h>
#import <SignalServiceKit/OWSAttachmentsProcessor.h>
#import <SignalServiceKit/OWSBackgroundTask.h>
#import <SignalServiceKit/OWSCallMessageHandler.h>
#import <SignalServiceKit/OWSContactsOutputStream.h>

View File

@ -234,7 +234,6 @@ public class ConversationMediaView: UIView {
AssertIsOnMainThread()
guard let loadBlock = loadBlock else {
owsFailDebug("Missing loadBlock")
return
}
loadBlock()
@ -245,7 +244,6 @@ public class ConversationMediaView: UIView {
AssertIsOnMainThread()
guard let unloadBlock = unloadBlock else {
owsFailDebug("Missing unloadBlock")
return
}
unloadBlock()

View File

@ -39,8 +39,7 @@ extern const UIDataDetectorTypes kOWSAllowedDataDetectorTypes;
- (void)didTapTruncatedTextMessage:(id<ConversationViewItem>)conversationItem;
- (void)didTapFailedIncomingAttachment:(id<ConversationViewItem>)viewItem
attachmentPointer:(TSAttachmentPointer *)attachmentPointer;
- (void)didTapFailedIncomingAttachment:(id<ConversationViewItem>)viewItem;
- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem quotedReply:(OWSQuotedReplyModel *)quotedReply;
- (void)didTapConversationItem:(id<ConversationViewItem>)viewItem

View File

@ -1402,7 +1402,7 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes
OWSAssertDebug(attachmentPointer);
if (attachmentPointer.state == TSAttachmentPointerStateFailed) {
[self.delegate didTapFailedIncomingAttachment:self.viewItem attachmentPointer:attachmentPointer];
[self.delegate didTapFailedIncomingAttachment:self.viewItem];
}
break;
}

View File

@ -67,7 +67,7 @@
#import <SignalServiceKit/NSTimer+OWS.h>
#import <SignalServiceKit/OWSAddToContactsOfferMessage.h>
#import <SignalServiceKit/OWSAddToProfileWhitelistOfferMessage.h>
#import <SignalServiceKit/OWSAttachmentsProcessor.h>
#import <SignalServiceKit/OWSAttachmentDownloads.h>
#import <SignalServiceKit/OWSBlockingManager.h>
#import <SignalServiceKit/OWSDisappearingMessagesConfiguration.h>
#import <SignalServiceKit/OWSIdentityManager.h>
@ -297,6 +297,11 @@ typedef enum : NSUInteger {
return SSKEnvironment.shared.typingIndicators;
}
- (OWSAttachmentDownloads *)attachmentDownloads
{
return SSKEnvironment.shared.attachmentDownloads;
}
#pragma mark -
- (void)addNotificationListeners
@ -1644,13 +1649,11 @@ typedef enum : NSUInteger {
#pragma mark Bubble User Actions
- (void)handleFailedDownloadTapForMessage:(TSMessage *)message
attachmentPointer:(TSAttachmentPointer *)attachmentPointer
{
OWSAttachmentsProcessor *processor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:@[ attachmentPointer ]];
OWSAssert(message);
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[processor fetchAttachmentsForMessage:message
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.attachmentDownloads downloadAttachmentsForMessage:message
transaction:transaction
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSLogInfo(@"Successfully redownloaded attachment in thread: %@", message.thread);
@ -2159,15 +2162,13 @@ typedef enum : NSUInteger {
}
- (void)didTapFailedIncomingAttachment:(id<ConversationViewItem>)viewItem
attachmentPointer:(TSAttachmentPointer *)attachmentPointer
{
OWSAssertIsOnMainThread();
OWSAssertDebug(viewItem);
OWSAssertDebug(attachmentPointer);
// Restart failed downloads
TSMessage *message = (TSMessage *)viewItem.interaction;
[self handleFailedDownloadTapForMessage:message attachmentPointer:attachmentPointer];
[self handleFailedDownloadTapForMessage:message];
}
- (void)didTapFailedOutgoingMessage:(TSOutgoingMessage *)message
@ -2192,17 +2193,13 @@ typedef enum : NSUInteger {
return;
}
OWSAttachmentsProcessor *processor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:@[ attachmentPointer ]];
[self.editingDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[processor fetchAttachmentsForMessage:nil
transaction:transaction
[self.uiDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.attachmentDownloads downloadAttachmentPointer:attachmentPointer
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
[self.editingDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *postSuccessTransaction) {
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *postSuccessTransaction) {
[message setQuotedMessageThumbnailAttachmentStream:attachmentStream];
[message saveWithTransaction:postSuccessTransaction];
}];
@ -2210,8 +2207,8 @@ typedef enum : NSUInteger {
failure:^(NSError *error) {
OWSLogWarn(@"Failed to redownload thumbnail with error: %@", error);
[self.editingDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *postSuccessTransaction) {
[message touchWithTransaction:transaction];
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *postSuccessTransaction) {
[message touchWithTransaction:postSuccessTransaction];
}];
}];
}];

View File

@ -694,7 +694,7 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
navigationController.pushViewController(viewController, animated: true)
}
func didTapFailedIncomingAttachment(_ viewItem: ConversationViewItem, attachmentPointer: TSAttachmentPointer) {
func didTapFailedIncomingAttachment(_ viewItem: ConversationViewItem) {
// no - op
}

View File

@ -11,6 +11,7 @@
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/ContactDiscoveryService.h>
#import <SignalServiceKit/OWS2FAManager.h>
#import <SignalServiceKit/OWSAttachmentDownloads.h>
#import <SignalServiceKit/OWSBackgroundTask.h>
#import <SignalServiceKit/OWSBatchMessageProcessor.h>
#import <SignalServiceKit/OWSBlockingManager.h>
@ -86,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSSyncManager *syncManager = [[OWSSyncManager alloc] initDefault];
id<SSKReachabilityManager> reachabilityManager = [SSKReachabilityManagerImpl new];
id<OWSTypingIndicators> typingIndicators = [[OWSTypingIndicatorsImpl alloc] init];
OWSAttachmentDownloads *attachmentDownloads = [[OWSAttachmentDownloads alloc] init];
OWSAudioSession *audioSession = [OWSAudioSession new];
OWSSounds *sounds = [[OWSSounds alloc] initWithPrimaryStorage:primaryStorage];
@ -123,7 +125,8 @@ NS_ASSUME_NONNULL_BEGIN
outgoingReceiptManager:outgoingReceiptManager
reachabilityManager:reachabilityManager
syncManager:syncManager
typingIndicators:typingIndicators]];
typingIndicators:typingIndicators
attachmentDownloads:attachmentDownloads]];
appSpecificSingletonBlock();

View File

@ -5,24 +5,14 @@
NS_ASSUME_NONNULL_BEGIN
@class OWSIncomingSentMessageTranscript;
@class OWSPrimaryStorage;
@class OWSReadReceiptManager;
@class TSAttachmentStream;
@class TSNetworkManager;
@class YapDatabaseReadWriteTransaction;
@protocol ContactsManagerProtocol;
// This job is used to process "outgoing message" notifications from linked devices.
@interface OWSRecordTranscriptJob : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript;
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript
networkManager:(TSNetworkManager *)networkManager
primaryStorage:(OWSPrimaryStorage *)primaryStorage
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
contactsManager:(id<ContactsManagerProtocol>)contactsManager
NS_DESIGNATED_INITIALIZER;
- (void)runWithAttachmentHandler:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))attachmentHandler

View File

@ -3,7 +3,7 @@
//
#import "OWSRecordTranscriptJob.h"
#import "OWSAttachmentsProcessor.h"
#import "OWSAttachmentDownloads.h"
#import "OWSDisappearingMessagesJob.h"
#import "OWSIncomingSentMessageTranscript.h"
#import "OWSPrimaryStorage+SessionStore.h"
@ -19,31 +19,15 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSRecordTranscriptJob ()
@property (nonatomic, readonly) TSNetworkManager *networkManager;
@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage;
@property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager;
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
@property (nonatomic, readonly) OWSIncomingSentMessageTranscript *incomingSentMessageTranscript;
@end
#pragma mark -
@implementation OWSRecordTranscriptJob
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript
{
return [self initWithIncomingSentMessageTranscript:incomingSentMessageTranscript
networkManager:TSNetworkManager.sharedManager
primaryStorage:OWSPrimaryStorage.sharedManager
readReceiptManager:OWSReadReceiptManager.sharedManager
contactsManager:SSKEnvironment.shared.contactsManager];
}
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript
networkManager:(TSNetworkManager *)networkManager
primaryStorage:(OWSPrimaryStorage *)primaryStorage
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
contactsManager:(id<ContactsManagerProtocol>)contactsManager
{
self = [super init];
if (!self) {
@ -51,14 +35,47 @@ NS_ASSUME_NONNULL_BEGIN
}
_incomingSentMessageTranscript = incomingSentMessageTranscript;
_networkManager = networkManager;
_primaryStorage = primaryStorage;
_readReceiptManager = readReceiptManager;
_contactsManager = contactsManager;
return self;
}
#pragma mark - Dependencies
- (OWSPrimaryStorage *)primaryStorage
{
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
}
- (TSNetworkManager *)networkManager
{
OWSAssertDebug(SSKEnvironment.shared.networkManager);
return SSKEnvironment.shared.networkManager;
}
- (OWSReadReceiptManager *)readReceiptManager
{
OWSAssert(SSKEnvironment.shared.readReceiptManager);
return SSKEnvironment.shared.readReceiptManager;
}
- (id<ContactsManagerProtocol>)contactsManager
{
OWSAssertDebug(SSKEnvironment.shared.contactsManager);
return SSKEnvironment.shared.contactsManager;
}
- (OWSAttachmentDownloads *)attachmentDownloads
{
return SSKEnvironment.shared.attachmentDownloads;
}
#pragma mark -
- (void)runWithAttachmentHandler:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))attachmentHandler
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
@ -107,22 +124,19 @@ NS_ASSUME_NONNULL_BEGIN
transaction:transaction];
if ([attachmentPointer isKindOfClass:[TSAttachmentPointer class]]) {
OWSAttachmentsProcessor *attachmentProcessor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:@[ attachmentPointer ]];
OWSLogDebug(@"downloading attachments for transcript: %lu", (unsigned long)transcript.timestamp);
OWSLogDebug(@"downloading thumbnail for transcript: %lu", (unsigned long)transcript.timestamp);
[attachmentProcessor fetchAttachmentsForMessage:outgoingMessage
transaction:transaction
[self.attachmentDownloads downloadAttachmentPointer:attachmentPointer
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
[self.primaryStorage.newDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[outgoingMessage setQuotedMessageThumbnailAttachmentStream:attachmentStream];
[outgoingMessage saveWithTransaction:transaction];
}];
}
failure:^(NSError *_Nonnull error) {
failure:^(NSError *error) {
OWSLogWarn(@"failed to fetch thumbnail for transcript: %lu with error: %@",
(unsigned long)transcript.timestamp,
error);
@ -161,15 +175,14 @@ NS_ASSUME_NONNULL_BEGIN
[self.readReceiptManager applyEarlyReadReceiptsForOutgoingMessageFromLinkedDevice:outgoingMessage
transaction:transaction];
OWSAttachmentsProcessor *attachmentsProcessor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:attachmentPointers];
[attachmentsProcessor
fetchAttachmentsForMessage:outgoingMessage
transaction:transaction
success:attachmentHandler
failure:^(NSError *_Nonnull error) {
OWSLogError(@"failed to fetch transcripts attachments for message: %@", outgoingMessage);
}];
[self.attachmentDownloads
downloadAttachmentsForMessage:outgoingMessage
transaction:transaction
success:attachmentHandler
failure:^(NSError *error) {
OWSLogError(
@"failed to fetch transcripts attachments for message: %@", outgoingMessage);
}];
}
@end

View File

@ -0,0 +1,46 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kAttachmentDownloadProgressNotification;
extern NSString *const kAttachmentDownloadProgressKey;
extern NSString *const kAttachmentDownloadAttachmentIDKey;
@class SSKProtoAttachmentPointer;
@class TSAttachment;
@class TSAttachmentPointer;
@class TSAttachmentStream;
@class TSMessage;
@class YapDatabaseReadTransaction;
@class YapDatabaseReadWriteTransaction;
#pragma mark -
/**
* Given incoming attachment protos, determines which we support.
* It can download those that we support and notifies threads when it receives unsupported attachments.
*/
@interface OWSAttachmentDownloads : NSObject
// This will try to download all un-downloaded attachments for a given message.
// Any attachments for the message which are already downloaded are skipped BUT
// they are included in the success callback.
//
// success/failure are always called on a worker queue.
- (void)downloadAttachmentsForMessage:(TSMessage *)message
transaction:(YapDatabaseReadTransaction *)transaction
success:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))success
failure:(void (^)(NSError *error))failure;
// This will try to download a single attachment.
//
// success/failure are always called on a worker queue.
- (void)downloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer
success:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))success
failure:(void (^)(NSError *error))failure;
@end
NS_ASSUME_NONNULL_END

View File

@ -2,7 +2,7 @@
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSAttachmentsProcessor.h"
#import "OWSAttachmentDownloads.h"
#import "AppContext.h"
#import "MIMETypeUtil.h"
#import "NSNotificationCenter+OWS.h"
@ -36,103 +36,307 @@ NSString *const kAttachmentDownloadAttachmentIDKey = @"kAttachmentDownloadAttach
// indicator shows up as quickly as possible.
static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
@interface OWSAttachmentsProcessor ()
typedef void (^AttachmentDownloadSuccess)(TSAttachmentStream *attachmentStream);
typedef void (^AttachmentDownloadFailure)(NSError *error);
@property (nonatomic, readonly) TSNetworkManager *networkManager;
@interface OWSAttachmentDownloadJob : NSObject
@property (nonatomic, readonly) TSAttachmentPointer *attachmentPointer;
@property (nonatomic, readonly, nullable) TSMessage *message;
@property (nonatomic, readonly) AttachmentDownloadSuccess success;
@property (nonatomic, readonly) AttachmentDownloadFailure failure;
@end
@implementation OWSAttachmentsProcessor
#pragma mark -
- (instancetype)initWithAttachmentPointers:(NSArray<TSAttachmentPointer *> *)attachmentPointers
@implementation OWSAttachmentDownloadJob
- (instancetype)initWithAttachmentPointer:(TSAttachmentPointer *)attachmentPointer
message:(nullable TSMessage *)message
success:(AttachmentDownloadSuccess)success
failure:(AttachmentDownloadFailure)failure
{
self = [super init];
if (!self) {
return self;
}
_attachmentPointers = attachmentPointers;
_attachmentPointer = attachmentPointer;
_message = message;
_success = success;
_failure = failure;
return self;
}
@end
#pragma mark -
@interface OWSAttachmentDownloads ()
// This property should only be accessed while synchronized on this class.
@property (nonatomic, readonly) NSMutableSet<NSString *> *downloadingAttachmentIdSet;
// This property should only be accessed while synchronized on this class.
@property (nonatomic, readonly) NSMutableArray<OWSAttachmentDownloadJob *> *attachmentDownloadJobQueue;
@end
#pragma mark -
@implementation OWSAttachmentDownloads
#pragma mark - Dependencies
- (OWSPrimaryStorage *)primaryStorage
{
return SSKEnvironment.shared.primaryStorage;
}
- (TSNetworkManager *)networkManager
{
return SSKEnvironment.shared.networkManager;
}
#pragma mark
- (void)fetchAttachmentsForMessage:(nullable TSMessage *)message
transaction:(YapDatabaseReadWriteTransaction *)transaction
success:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))successHandler
failure:(void (^)(NSError *error))failureHandler
#pragma mark -
- (instancetype)init
{
OWSAssertDebug(transaction);
OWSAssertDebug(self.attachmentPointers.count > 0);
NSMutableArray<AnyPromise *> *promises = [NSMutableArray array];
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray array];
for (TSAttachmentPointer *attachmentPointer in self.attachmentPointers) {
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self retrieveAttachment:attachmentPointer
message:message
transaction:transaction
success:^(TSAttachmentStream *attachmentStream) {
OWSLogVerbose(@"Attachment download succeeded.");
@synchronized(attachmentStreams) {
[attachmentStreams addObject:attachmentStream];
}
resolve(@(1));
}
failure:^(NSError *error) {
OWSLogError(@"Attachment download failed with error: %@", error);
resolve(error);
}];
}];
[promises addObject:promise];
self = [super init];
if (!self) {
return self;
}
// We use PMKJoin(), not PMKWhen(), because we don't want the
// completion promise to execute until _all_ promises
// have either succeeded or failed. PMKWhen() executes as
// soon as any of its input promises fail.
AnyPromise *completionPromise
= PMKJoin(promises)
.then(^(id value) {
NSArray<TSAttachmentStream *> *attachmentStreamsCopy;
@synchronized(attachmentStreams) {
attachmentStreamsCopy = [attachmentStreams copy];
}
OWSLogInfo(@"Attachment downloads succeeded: %lu.", (unsigned long)attachmentStreamsCopy.count);
successHandler(attachmentStreamsCopy);
})
.catch(^(NSError *error) {
failureHandler(error);
});
[completionPromise retainUntilComplete];
_downloadingAttachmentIdSet = [NSMutableSet new];
_attachmentDownloadJobQueue = [NSMutableArray new];
return self;
}
#pragma mark -
- (void)downloadAttachmentsForMessage:(TSMessage *)message
transaction:(YapDatabaseReadTransaction *)transaction
success:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))success
failure:(void (^)(NSError *error))failure
{
OWSAssertDebug(transaction);
OWSAssertDebug(message);
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray array];
NSMutableArray<TSAttachmentPointer *> *attachmentPointers = [NSMutableArray new];
for (TSAttachment *attachment in [message attachmentsWithTransaction:transaction]) {
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
[attachmentStreams addObject:attachmentStream];
} else if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
TSAttachmentPointer *attachmentPointer = (TSAttachmentPointer *)attachment;
[attachmentPointers addObject:attachmentPointer];
} else {
OWSFailDebug(@"Unexpected attachment type: %@", attachment.class);
}
}
[self enqueueJobsForAttachmentStreams:attachmentStreams
attachmentPointers:attachmentPointers
message:message
success:success
failure:failure];
}
- (void)downloadAttachmentPointer:(TSAttachmentPointer *)attachmentPointer
success:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))success
failure:(void (^)(NSError *error))failure
{
OWSAssertDebug(attachmentPointer);
[self enqueueJobsForAttachmentStreams:@[]
attachmentPointers:@[
attachmentPointer,
]
message:nil
success:success
failure:failure];
}
- (void)enqueueJobsForAttachmentStreams:(NSArray<TSAttachmentStream *> *)attachmentStreamsParam
attachmentPointers:(NSArray<TSAttachmentPointer *> *)attachmentPointers
message:(nullable TSMessage *)message
success:(void (^)(NSArray<TSAttachmentStream *> *attachmentStreams))successHandler
failure:(void (^)(NSError *error))failureHandler
{
OWSAssertDebug(attachmentStreamsParam);
OWSAssertDebug(attachmentPointers.count > 0);
// To avoid deadlocks, synchronize on self outside of the transaction.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (attachmentPointers.count < 1) {
OWSAssertDebug(attachmentStreamsParam.count > 0);
successHandler(attachmentStreamsParam);
return;
}
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [attachmentStreamsParam mutableCopy];
NSMutableArray<AnyPromise *> *promises = [NSMutableArray array];
for (TSAttachmentPointer *attachmentPointer in attachmentPointers) {
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[self enqueueJobForAttachmentPointer:attachmentPointer
message:message
success:^(TSAttachmentStream *attachmentStream) {
@synchronized(attachmentStreams) {
[attachmentStreams addObject:attachmentStream];
}
resolve(@(1));
}
failure:^(NSError *error) {
resolve(error);
}];
}];
[promises addObject:promise];
}
// We use PMKJoin(), not PMKWhen(), because we don't want the
// completion promise to execute until _all_ promises
// have either succeeded or failed. PMKWhen() executes as
// soon as any of its input promises fail.
AnyPromise *completionPromise
= PMKJoin(promises)
.then(^(id value) {
NSArray<TSAttachmentStream *> *attachmentStreamsCopy;
@synchronized(attachmentStreams) {
attachmentStreamsCopy = [attachmentStreams copy];
}
OWSLogInfo(@"Attachment downloads succeeded: %lu.", (unsigned long)attachmentStreamsCopy.count);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
successHandler(attachmentStreamsCopy);
});
})
.catch(^(NSError *error) {
OWSLogError(@"Attachment downloads failed.");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
failureHandler(error);
});
});
[completionPromise retainUntilComplete];
});
}
- (void)enqueueJobForAttachmentPointer:(TSAttachmentPointer *)attachmentPointer
message:(nullable TSMessage *)message
success:(void (^)(TSAttachmentStream *attachmentStream))success
failure:(void (^)(NSError *error))failure
{
OWSAssertDebug(attachmentPointer);
OWSAttachmentDownloadJob *job = [[OWSAttachmentDownloadJob alloc] initWithAttachmentPointer:attachmentPointer
message:message
success:success
failure:failure];
@synchronized(self) {
[self.attachmentDownloadJobQueue addObject:job];
}
[self startDownloadIfPossible];
}
- (void)startDownloadIfPossible
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSAttachmentDownloadJob *_Nullable job;
@synchronized(self) {
const NSUInteger kMaxSimultaneousDownloads = 4;
if (self.downloadingAttachmentIdSet.count >= kMaxSimultaneousDownloads) {
return;
}
job = self.attachmentDownloadJobQueue.firstObject;
if (!job) {
return;
}
if ([self.downloadingAttachmentIdSet containsObject:job.attachmentPointer.uniqueId]) {
// Ensure we only have one download in flight at a time for a given attachment.
OWSLogWarn(@"Ignoring duplicate download.");
return;
}
[self.attachmentDownloadJobQueue removeObjectAtIndex:0];
[self.downloadingAttachmentIdSet addObject:job.attachmentPointer.uniqueId];
}
[self.primaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
job.attachmentPointer.state = TSAttachmentPointerStateDownloading;
[job.attachmentPointer saveWithTransaction:transaction];
if (job.message) {
[job.message touchWithTransaction:transaction];
}
}];
[self retrieveAttachment:job.attachmentPointer
success:^(TSAttachmentStream *attachmentStream) {
OWSLogVerbose(@"Attachment download succeeded.");
[self.primaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[attachmentStream saveWithTransaction:transaction];
if (job.message) {
[job.message touchWithTransaction:transaction];
}
}];
job.success(attachmentStream);
@synchronized(self) {
[self.downloadingAttachmentIdSet removeObject:job.attachmentPointer.uniqueId];
}
[self startDownloadIfPossible];
}
failure:^(NSError *error) {
OWSLogError(@"Attachment download failed with error: %@", error);
[self.primaryStorage.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
job.attachmentPointer.mostRecentFailureLocalizedText = error.localizedDescription;
job.attachmentPointer.state = TSAttachmentPointerStateFailed;
[job.attachmentPointer saveWithTransaction:transaction];
if (job.message) {
[job.message touchWithTransaction:transaction];
}
}];
@synchronized(self) {
[self.downloadingAttachmentIdSet removeObject:job.attachmentPointer.uniqueId];
}
job.failure(error);
[self startDownloadIfPossible];
}];
});
}
#pragma mark -
- (void)retrieveAttachment:(TSAttachmentPointer *)attachment
message:(nullable TSMessage *)message
transaction:(YapDatabaseReadWriteTransaction *)transaction
success:(void (^)(TSAttachmentStream *attachmentStream))successHandler
failure:(void (^)(NSError *error))failureHandler
{
OWSAssertDebug(transaction);
OWSAssertDebug(attachment);
__block OWSBackgroundTask *_Nullable backgroundTask =
[OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
[self setAttachment:attachment isDownloadingInMessage:message transaction:transaction];
void (^markAndHandleFailure)(NSError *) = ^(NSError *error) {
// Ensure enclosing transaction is complete.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self setAttachment:attachment didFailInMessage:message error:error];
failureHandler(error);
OWSAssertDebug(backgroundTask);
@ -141,12 +345,8 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
};
void (^markAndHandleSuccess)(TSAttachmentStream *attachmentStream) = ^(TSAttachmentStream *attachmentStream) {
// Ensure enclosing transaction is complete.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
successHandler(attachmentStream);
if (message) {
[message touch];
}
OWSAssertDebug(backgroundTask);
backgroundTask = nil;
@ -263,7 +463,8 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
}
if (!plaintext) {
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
NSError *error = OWSErrorWithCodeDescription(
OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
failureHandler(error);
return;
}
@ -278,7 +479,6 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
return;
}
[stream save];
successHandler(stream);
}
@ -299,7 +499,7 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
failure:(void (^)(NSURLSessionTask *_Nullable task, NSError *error))failureHandlerParam
{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
[manager.requestSerializer setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
@ -451,31 +651,6 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
}];
}
- (void)setAttachment:(TSAttachmentPointer *)pointer
isDownloadingInMessage:(nullable TSMessage *)message
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
pointer.state = TSAttachmentPointerStateDownloading;
[pointer saveWithTransaction:transaction];
if (message) {
[message touchWithTransaction:transaction];
}
}
- (void)setAttachment:(TSAttachmentPointer *)pointer
didFailInMessage:(nullable TSMessage *)message
error:(NSError *)error
{
pointer.mostRecentFailureLocalizedText = error.localizedDescription;
pointer.state = TSAttachmentPointerStateFailed;
[pointer save];
if (message) {
[message touch];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -107,6 +107,9 @@ NS_ASSUME_NONNULL_BEGIN
(NSArray<SSKProtoAttachmentPointer *> *)attachmentProtos
albumMessage:(TSMessage *)albumMessage
{
OWSAssertDebug(attachmentProtos);
OWSAssertDebug(albumMessage);
NSMutableArray *attachmentPointers = [NSMutableArray new];
for (SSKProtoAttachmentPointer *attachmentProto in attachmentProtos) {
TSAttachmentPointer *_Nullable attachmentPointer =

View File

@ -9,7 +9,7 @@
#import "MimeTypeUtil.h"
#import "NSNotificationCenter+OWS.h"
#import "NotificationsProtocol.h"
#import "OWSAttachmentsProcessor.h"
#import "OWSAttachmentDownloads.h"
#import "OWSBlockingManager.h"
#import "OWSCallMessageHandler.h"
#import "OWSContact.h"
@ -164,6 +164,11 @@ NS_ASSUME_NONNULL_BEGIN
return SSKEnvironment.shared.typingIndicators;
}
- (OWSAttachmentDownloads *)attachmentDownloads
{
return SSKEnvironment.shared.attachmentDownloads;
}
#pragma mark -
- (void)startObserving
@ -729,13 +734,14 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
TSAttachmentPointer *avatarPointer =
TSAttachmentPointer *_Nullable avatarPointer =
[TSAttachmentPointer attachmentPointerFromProto:dataMessage.group.avatar albumMessage:nil];
OWSAttachmentsProcessor *attachmentsProcessor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:@[ avatarPointer ]];
[attachmentsProcessor fetchAttachmentsForMessage:nil
transaction:transaction
if (!avatarPointer) {
OWSLogWarn(@"received unsupported group avatar envelope");
return;
}
[self.attachmentDownloads downloadAttachmentPointer:avatarPointer
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
@ -771,35 +777,26 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
TSIncomingMessage *_Nullable createdMessage = [self handleReceivedEnvelope:envelope
withDataMessage:dataMessage
transaction:transaction];
if (!createdMessage) {
TSIncomingMessage *_Nullable message =
[self handleReceivedEnvelope:envelope withDataMessage:dataMessage transaction:transaction];
if (!message) {
return;
}
NSArray<TSAttachmentPointer *> *attachmentPointers =
[TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments albumMessage:createdMessage];
for (TSAttachmentPointer *pointer in attachmentPointers) {
[pointer saveWithTransaction:transaction];
[createdMessage.attachmentIds addObject:pointer.uniqueId];
}
[createdMessage saveWithTransaction:transaction];
[message saveWithTransaction:transaction];
OWSLogDebug(@"incoming attachment message: %@", createdMessage.debugDescription);
OWSLogDebug(@"incoming attachment message: %@", message.debugDescription);
OWSAttachmentsProcessor *attachmentsProcessor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:attachmentPointers];
[attachmentsProcessor fetchAttachmentsForMessage:createdMessage
[self.attachmentDownloads downloadAttachmentsForMessage:message
transaction:transaction
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSLogDebug(@"successfully fetched attachments: %lu for message: %@",
(unsigned long)attachmentStreams.count,
createdMessage);
message);
}
failure:^(NSError *error) {
OWSLogError(@"failed to fetch attachments for message: %@ with error: %@", createdMessage, error);
OWSLogError(@"failed to fetch attachments for message: %@ with error: %@", message, error);
}];
}
@ -1264,6 +1261,22 @@ NS_ASSUME_NONNULL_BEGIN
serverTimestamp:serverTimestamp
wasReceivedByUD:wasReceivedByUD];
NSArray<TSAttachmentPointer *> *attachmentPointers =
[TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments
albumMessage:incomingMessage];
for (TSAttachmentPointer *pointer in attachmentPointers) {
[pointer saveWithTransaction:transaction];
[incomingMessage.attachmentIds addObject:pointer.uniqueId];
}
if (body.length == 0 && attachmentPointers.count < 1 && !contact) {
OWSLogWarn(@"ignoring empty incoming message from: %@ for group: %@ with timestamp: %lu",
envelopeAddress(envelope),
groupId,
(unsigned long)timestamp);
return nil;
}
[self finalizeIncomingMessage:incomingMessage
thread:oldGroupThread
envelope:envelope
@ -1298,6 +1311,20 @@ NS_ASSUME_NONNULL_BEGIN
serverTimestamp:serverTimestamp
wasReceivedByUD:wasReceivedByUD];
NSArray<TSAttachmentPointer *> *attachmentPointers =
[TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments albumMessage:incomingMessage];
for (TSAttachmentPointer *pointer in attachmentPointers) {
[pointer saveWithTransaction:transaction];
[incomingMessage.attachmentIds addObject:pointer.uniqueId];
}
if (body.length == 0 && attachmentPointers.count < 1 && !contact) {
OWSLogWarn(@"ignoring empty incoming message from: %@ with timestamp: %lu",
envelopeAddress(envelope),
(unsigned long)timestamp);
return nil;
}
[self finalizeIncomingMessage:incomingMessage
thread:thread
envelope:envelope
@ -1344,16 +1371,12 @@ NS_ASSUME_NONNULL_BEGIN
transaction:transaction];
if ([attachmentPointer isKindOfClass:[TSAttachmentPointer class]]) {
OWSAttachmentsProcessor *attachmentProcessor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:@[ attachmentPointer ]];
OWSLogDebug(@"downloading thumbnail for message: %lu", (unsigned long)incomingMessage.timestamp);
[attachmentProcessor fetchAttachmentsForMessage:incomingMessage
transaction:transaction
[self.attachmentDownloads downloadAttachmentPointer:attachmentPointer
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
OWSAssertDebug(attachmentStreams.count == 1);
TSAttachmentStream *attachmentStream = attachmentStreams.firstObject;
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[incomingMessage setQuotedMessageThumbnailAttachmentStream:attachmentStream];
[incomingMessage saveWithTransaction:transaction];
}];
@ -1374,14 +1397,11 @@ NS_ASSUME_NONNULL_BEGIN
if (![attachmentPointer isKindOfClass:[TSAttachmentPointer class]]) {
OWSFailDebug(@"avatar attachmentPointer was unexpectedly nil");
} else {
OWSAttachmentsProcessor *attachmentProcessor =
[[OWSAttachmentsProcessor alloc] initWithAttachmentPointers:@[ attachmentPointer ]];
OWSLogDebug(@"downloading contact avatar for message: %lu", (unsigned long)incomingMessage.timestamp);
[attachmentProcessor fetchAttachmentsForMessage:incomingMessage
transaction:transaction
[self.attachmentDownloads downloadAttachmentPointer:attachmentPointer
success:^(NSArray<TSAttachmentStream *> *attachmentStreams) {
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[incomingMessage touchWithTransaction:transaction];
}];
}

View File

@ -9,6 +9,7 @@ NS_ASSUME_NONNULL_BEGIN
@class ContactDiscoveryService;
@class ContactsUpdater;
@class OWS2FAManager;
@class OWSAttachmentDownloads;
@class OWSBatchMessageProcessor;
@class OWSBlockingManager;
@class OWSDisappearingMessagesJob;
@ -60,7 +61,8 @@ NS_ASSUME_NONNULL_BEGIN
outgoingReceiptManager:(OWSOutgoingReceiptManager *)outgoingReceiptManager
reachabilityManager:(id<SSKReachabilityManager>)reachabilityManager
syncManager:(id<OWSSyncManagerProtocol>)syncManager
typingIndicators:(id<OWSTypingIndicators>)typingIndicators NS_DESIGNATED_INITIALIZER;
typingIndicators:(id<OWSTypingIndicators>)typingIndicators
attachmentDownloads:(OWSAttachmentDownloads *)attachmentDownloads NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@ -97,6 +99,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) id<OWSSyncManagerProtocol> syncManager;
@property (nonatomic, readonly) id<SSKReachabilityManager> reachabilityManager;
@property (nonatomic, readonly) id<OWSTypingIndicators> typingIndicators;
@property (nonatomic, readonly) OWSAttachmentDownloads *attachmentDownloads;
// This property is configured after Environment is created.
@property (atomic, nullable) id<OWSCallMessageHandler> callMessageHandler;

View File

@ -35,6 +35,7 @@ static SSKEnvironment *sharedSSKEnvironment;
@property (nonatomic) id<OWSSyncManagerProtocol> syncManager;
@property (nonatomic) id<SSKReachabilityManager> reachabilityManager;
@property (nonatomic) id<OWSTypingIndicators> typingIndicators;
@property (nonatomic) OWSAttachmentDownloads *attachmentDownloads;
@end
@ -73,6 +74,7 @@ static SSKEnvironment *sharedSSKEnvironment;
reachabilityManager:(id<SSKReachabilityManager>)reachabilityManager
syncManager:(id<OWSSyncManagerProtocol>)syncManager
typingIndicators:(id<OWSTypingIndicators>)typingIndicators
attachmentDownloads:(OWSAttachmentDownloads *)attachmentDownloads
{
self = [super init];
if (!self) {
@ -103,6 +105,7 @@ static SSKEnvironment *sharedSSKEnvironment;
OWSAssertDebug(syncManager);
OWSAssertDebug(reachabilityManager);
OWSAssertDebug(typingIndicators);
OWSAssertDebug(attachmentDownloads);
_contactsManager = contactsManager;
_messageSender = messageSender;
@ -128,6 +131,7 @@ static SSKEnvironment *sharedSSKEnvironment;
_syncManager = syncManager;
_reachabilityManager = reachabilityManager;
_typingIndicators = typingIndicators;
_attachmentDownloads = attachmentDownloads;
return self;
}

View File

@ -5,6 +5,7 @@
#import "MockSSKEnvironment.h"
#import "ContactDiscoveryService.h"
#import "OWS2FAManager.h"
#import "OWSAttachmentDownloads.h"
#import "OWSBatchMessageProcessor.h"
#import "OWSBlockingManager.h"
#import "OWSDisappearingMessagesJob.h"
@ -76,6 +77,7 @@ NS_ASSUME_NONNULL_BEGIN
id<SSKReachabilityManager> reachabilityManager = [SSKReachabilityManagerImpl new];
id<OWSSyncManagerProtocol> syncManager = [[OWSMockSyncManager alloc] init];
id<OWSTypingIndicators> typingIndicators = [[OWSTypingIndicatorsImpl alloc] init];
OWSAttachmentDownloads *attachmentDownloads = [[OWSAttachmentDownloads alloc] init];
self = [super initWithContactsManager:contactsManager
messageSender:messageSender
@ -100,7 +102,8 @@ NS_ASSUME_NONNULL_BEGIN
outgoingReceiptManager:outgoingReceiptManager
reachabilityManager:reachabilityManager
syncManager:syncManager
typingIndicators:typingIndicators];
typingIndicators:typingIndicators
attachmentDownloads:attachmentDownloads];
if (!self) {
return nil;