mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
7.3 KiB
Objective-C
181 lines
7.3 KiB
Objective-C
//
|
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
#import "OWSUploadingService.h"
|
|
#import "Cryptography.h"
|
|
#import "MIMETypeUtil.h"
|
|
#import "NSNotificationCenter+OWS.h"
|
|
#import "OWSError.h"
|
|
#import "OWSMessageSender.h"
|
|
#import "TSAttachmentStream.h"
|
|
#import "TSNetworkManager.h"
|
|
#import "TSOutgoingMessage.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
NSString *const kAttachmentUploadProgressNotification = @"kAttachmentUploadProgressNotification";
|
|
NSString *const kAttachmentUploadProgressKey = @"kAttachmentUploadProgressKey";
|
|
NSString *const kAttachmentUploadAttachmentIDKey = @"kAttachmentUploadAttachmentIDKey";
|
|
|
|
// Use a slightly non-zero value to ensure that the progress
|
|
// indicator shows up as quickly as possible.
|
|
static const CGFloat kAttachmentUploadProgressTheta = 0.001f;
|
|
|
|
@interface OWSUploadingService ()
|
|
|
|
@property (nonatomic, readonly) TSNetworkManager *networkManager;
|
|
|
|
@end
|
|
|
|
@implementation OWSUploadingService
|
|
|
|
- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager
|
|
{
|
|
self = [super init];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
_networkManager = networkManager;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)uploadAttachmentStream:(TSAttachmentStream *)attachmentStream
|
|
message:(TSOutgoingMessage *)outgoingMessage
|
|
success:(void (^)(void))successHandler
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
{
|
|
void (^successHandlerWrapper)(void) = ^{
|
|
[self fireProgressNotification:1 attachmentId:attachmentStream.uniqueId];
|
|
|
|
successHandler();
|
|
};
|
|
|
|
RetryableFailureHandler failureHandlerWrapper = ^(NSError *_Nonnull error) {
|
|
[self fireProgressNotification:0 attachmentId:attachmentStream.uniqueId];
|
|
|
|
failureHandler(error);
|
|
};
|
|
|
|
if (attachmentStream.serverId) {
|
|
DDLogDebug(@"%@ Attachment previously uploaded.", self.logTag);
|
|
successHandlerWrapper();
|
|
return;
|
|
}
|
|
|
|
[self fireProgressNotification:kAttachmentUploadProgressTheta attachmentId:attachmentStream.uniqueId];
|
|
|
|
TSRequest *allocateAttachment = [[TSAllocAttachmentRequest alloc] init];
|
|
[self.networkManager makeRequest:allocateAttachment
|
|
success:^(NSURLSessionDataTask *task, id responseObject) {
|
|
dispatch_async([OWSDispatch attachmentsQueue], ^{ // TODO can we move this queue specification up a level?
|
|
if (![responseObject isKindOfClass:[NSDictionary class]]) {
|
|
DDLogError(@"%@ unexpected response from server: %@", self.logTag, responseObject);
|
|
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
|
[error setIsRetryable:YES];
|
|
return failureHandlerWrapper(error);
|
|
}
|
|
|
|
NSDictionary *responseDict = (NSDictionary *)responseObject;
|
|
UInt64 serverId = ((NSDecimalNumber *)[responseDict objectForKey:@"id"]).unsignedLongLongValue;
|
|
NSString *location = [responseDict objectForKey:@"location"];
|
|
|
|
NSError *error;
|
|
NSData *attachmentData = [attachmentStream readDataFromFileWithError:&error];
|
|
if (error) {
|
|
DDLogError(@"%@ Failed to read attachment data with error:%@", self.logTag, error);
|
|
[error setIsRetryable:YES];
|
|
return failureHandlerWrapper(error);
|
|
}
|
|
|
|
NSData *encryptionKey;
|
|
NSData *digest;
|
|
NSData *encryptedAttachmentData =
|
|
[Cryptography encryptAttachmentData:attachmentData outKey:&encryptionKey outDigest:&digest];
|
|
|
|
attachmentStream.encryptionKey = encryptionKey;
|
|
attachmentStream.digest = digest;
|
|
|
|
[self uploadDataWithProgress:encryptedAttachmentData
|
|
location:location
|
|
attachmentId:attachmentStream.uniqueId
|
|
success:^{
|
|
OWSAssert([NSThread isMainThread]);
|
|
|
|
DDLogInfo(@"%@ Uploaded attachment: %p.", self.logTag, attachmentStream);
|
|
attachmentStream.serverId = serverId;
|
|
attachmentStream.isUploaded = YES;
|
|
[attachmentStream saveAsyncWithCompletionBlock:successHandlerWrapper];
|
|
}
|
|
failure:failureHandlerWrapper];
|
|
|
|
});
|
|
}
|
|
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
|
DDLogError(@"%@ Failed to allocate attachment with error: %@", self.logTag, error);
|
|
[error setIsRetryable:YES];
|
|
failureHandlerWrapper(error);
|
|
}];
|
|
}
|
|
|
|
|
|
- (void)uploadDataWithProgress:(NSData *)cipherText
|
|
location:(NSString *)location
|
|
attachmentId:(NSString *)attachmentId
|
|
success:(void (^)(void))successHandler
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
{
|
|
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:location]];
|
|
request.HTTPMethod = @"PUT";
|
|
request.HTTPBody = cipherText;
|
|
[request setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"];
|
|
|
|
AFURLSessionManager *manager = [[AFURLSessionManager alloc]
|
|
initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
|
|
|
|
NSURLSessionUploadTask *uploadTask;
|
|
uploadTask = [manager uploadTaskWithRequest:request
|
|
fromData:cipherText
|
|
progress:^(NSProgress *_Nonnull uploadProgress) {
|
|
[self fireProgressNotification:MAX(kAttachmentUploadProgressTheta, uploadProgress.fractionCompleted)
|
|
attachmentId:attachmentId];
|
|
}
|
|
completionHandler:^(NSURLResponse *_Nonnull response, id _Nullable responseObject, NSError *_Nullable error) {
|
|
OWSAssert([NSThread isMainThread]);
|
|
if (error) {
|
|
[error setIsRetryable:YES];
|
|
return failureHandler(error);
|
|
}
|
|
|
|
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
|
|
BOOL isValidResponse = (statusCode >= 200) && (statusCode < 400);
|
|
if (!isValidResponse) {
|
|
DDLogError(@"%@ Unexpected server response: %d", self.logTag, (int)statusCode);
|
|
NSError *invalidResponseError = OWSErrorMakeUnableToProcessServerResponseError();
|
|
[invalidResponseError setIsRetryable:YES];
|
|
return failureHandler(invalidResponseError);
|
|
}
|
|
|
|
successHandler();
|
|
}];
|
|
|
|
[uploadTask resume];
|
|
}
|
|
|
|
- (void)fireProgressNotification:(CGFloat)progress attachmentId:(NSString *)attachmentId
|
|
{
|
|
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
|
[notificationCenter postNotificationNameAsync:kAttachmentUploadProgressNotification
|
|
object:nil
|
|
userInfo:@{
|
|
kAttachmentUploadProgressKey : @(progress),
|
|
kAttachmentUploadAttachmentIDKey : attachmentId
|
|
}];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|