1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-02 09:27:55 +01:00
Telegram/Telegraph/TGLiveUploadActor.m
2015-10-01 19:19:52 +03:00

515 lines
15 KiB
Objective-C

/*
* This is the source code of Telegram for iOS v. 1.1
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Peter Iakovlev, 2013.
*/
#import "TGLiveUploadActor.h"
#import "ActionStage.h"
#import "TGTelegramNetworking.h"
#import "TGNetworkWorker.h"
#import <MtProtoKit/MTRequest.h>
#import "TL/TLMetaScheme.h"
#import <CommonCrypto/CommonDigest.h>
#import <MTProtoKit/MTEncryption.h>
#import "TGDataItem.h"
@interface TGLiveUploadActorData () <ASWatcher>
@property (nonatomic, strong) ASHandle *actionHandle;
@end
@implementation TGLiveUploadActorData
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_actionHandle = [[ASHandle alloc] initWithDelegate:self];
}
return self;
}
- (void)dealloc
{
[ActionStageInstance() removeWatcher:self];
}
@end
@interface TGLiveUploadPart ()
@property (nonatomic, strong) id cancelToken;
@end
@implementation TGLiveUploadPart
@end
@interface TGLiveUploadActor ()
{
TGDataItem *_fileItem;
bool _encryptFile;
int64_t _fileId;
NSUInteger _availableSize;
id _workerToken;
TGNetworkWorkerGuard *_worker;
NSMutableArray *_doneParts;
NSMutableArray *_uploadingParts;
bool _lateHeaderMode;
NSData *(^_dataProvider)(NSUInteger offset, NSUInteger length);
NSData *_unfinishedHeaderData;
bool _liveMode;
bool _canComplete;
NSData *_aesKey;
NSData *_aesIv;
NSMutableData *_aesRunningIv;
int32_t _keyFingerprint;
}
@end
@implementation TGLiveUploadActor
+ (void)load
{
[ASActor registerActorClass:self];
}
- (void)dealloc
{
}
+ (NSString *)genericPath
{
return @"/tg/liveUpload/@";
}
- (instancetype)initWithPath:(NSString *)path
{
self = [super initWithPath:path];
if (self != nil)
{
_doneParts = [[NSMutableArray alloc] init];
_uploadingParts = [[NSMutableArray alloc] init];
}
return self;
}
- (void)_unlinkFileIfNeeded
{
}
- (void)_complete:(id)result
{
[self _unlinkFileIfNeeded];
[ActionStageInstance() actionCompleted:self.path result:result];
}
- (void)_fail
{
[self _unlinkFileIfNeeded];
[ActionStageInstance() actionFailed:self.path reason:-1];
}
- (void)execute:(NSDictionary *)options
{
arc4random_buf(&_fileId, 8);
_fileItem = options[@"fileItem"];
if (_fileItem == nil && options[@"filePath"] != nil)
_fileItem = [[TGDataItem alloc] initWithFilePath:options[@"filePath"]];
_encryptFile = [options[@"encryptFile"] boolValue];
_lateHeaderMode = [options[@"lateHeader"] boolValue];
_dataProvider = [options[@"dataProvider"] copy];
_liveMode = true;
if (_fileItem == nil)
[self _fail];
else
{
if (_encryptFile)
{
uint8_t rawKey[32];
SecRandomCopyBytes(kSecRandomDefault, 32, rawKey);
_aesKey = [[NSData alloc] initWithBytes:rawKey length:32];
uint8_t rawIv[32];
SecRandomCopyBytes(kSecRandomDefault, 32, rawIv);
_aesIv = [[NSData alloc] initWithBytes:rawIv length:32];
_aesRunningIv = [[NSMutableData alloc] initWithData:_aesIv];
uint8_t keyPlusIv[32 + 32];
[_aesKey getBytes:keyPlusIv range:NSMakeRange(0, 32)];
[_aesIv getBytes:keyPlusIv + 32 range:NSMakeRange(0, 32)];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5(keyPlusIv, 32 + 32, digest);
int32_t digestHigh = 0;
int32_t digestLow = 0;
memcpy(&digestHigh, digest, 4);
memcpy(&digestLow, digest + 4, 4);
_keyFingerprint = digestHigh ^ digestLow;
}
__weak TGLiveUploadActor *weakSelf = self;
_workerToken = [[TGTelegramNetworking instance] requestDownloadWorkerForDatacenterId:[TGTelegramNetworking instance].masterDatacenterId completion:^(TGNetworkWorkerGuard *worker)
{
[ActionStageInstance() dispatchOnStageQueue:^
{
__strong TGLiveUploadActor *strongSelf = weakSelf;
[strongSelf _beginWithWorker:worker];
}];
}];
}
}
- (void)_beginWithWorker:(TGNetworkWorkerGuard *)worker
{
_worker = worker;
[_worker.strongWorker ensureConnection];
[self _dequeuePartIfAny];
}
- (void)updateSize:(NSUInteger)availableSize
{
[ActionStageInstance() dispatchOnStageQueue:^
{
if (availableSize > _availableSize)
_availableSize = availableSize;
[self _dequeuePartIfAny];
}];
}
- (NSUInteger)doneSize
{
NSUInteger value = 0;
for (TGLiveUploadPart *part in _doneParts)
{
value += part.length;
}
return value;
}
- (void)_dequeuePartIfAny
{
if (!_liveMode && [self doneSize] >= _availableSize)
{
if (_canComplete)
{
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
result[@"fileId"] = @(_fileId);
result[@"partCount"] = @(_doneParts.count);
result[@"fileSize"] = @(_availableSize);
if (_encryptFile && _aesKey != nil &&_aesIv != nil)
{
result[@"aesKey"] = _aesKey;
result[@"aesIv"] = _aesIv;
result[@"aesKeyFingerprint"] = @(_keyFingerprint);
}
if (_fileItem != nil)
{
CC_MD5_CTX ctx;
CC_MD5_Init(&ctx);
NSUInteger bufferSize = 4 * 1024;
for (NSUInteger i = 0; i < _availableSize; i += bufferSize)
{
NSUInteger currentBufferSize = MIN(bufferSize, _availableSize - i);
NSData *data = [_fileItem readDataAtOffset:i length:currentBufferSize];
CC_MD5_Update(&ctx, data.bytes, (CC_LONG)data.length);
}
unsigned char md5Buffer[16];
CC_MD5_Final(md5Buffer, &ctx);
NSString *hash = [[NSString alloc] initWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", md5Buffer[0], md5Buffer[1], md5Buffer[2], md5Buffer[3], md5Buffer[4], md5Buffer[5], md5Buffer[6], md5Buffer[7], md5Buffer[8], md5Buffer[9], md5Buffer[10], md5Buffer[11], md5Buffer[12], md5Buffer[13], md5Buffer[14], md5Buffer[15]];
result[@"md5"] = hash;
}
[self _complete:result];
}
}
else
{
NSInteger availableUploadSlots = MAX(0, (_liveMode ? (_lateHeaderMode ? 8 : 2) : 8) - (NSInteger)_uploadingParts.count);
for (NSInteger slotIndex = 0; slotIndex < availableUploadSlots; slotIndex++)
{
NSUInteger processedSize = 0;
for (TGLiveUploadPart *part in _uploadingParts)
{
processedSize = MAX(processedSize, part.offset + part.length);
}
for (TGLiveUploadPart *part in _doneParts)
{
processedSize = MAX(processedSize, part.offset + part.length);
}
NSUInteger nextPartSize = 16 * 1024;
NSInteger minPartSize = 1024;
bool takePart = false;
if (_liveMode)
{
if (processedSize + minPartSize <= _availableSize)
{
NSInteger calculatedPartSize = _availableSize - processedSize;
if (_encryptFile)
{
while (calculatedPartSize % 16 != 0)
{
calculatedPartSize--;
}
}
if (calculatedPartSize > 0)
takePart = true;
}
}
else
{
if (processedSize < _availableSize || _unfinishedHeaderData != nil)
takePart = true;
}
if (takePart)
{
NSInteger currentPartSize = 0;
NSData *partData = nil;
bool isHeader = false;
if (_unfinishedHeaderData != nil)
{
partData = _unfinishedHeaderData;
_unfinishedHeaderData = nil;
currentPartSize = partData.length;
isHeader = true;
}
else
{
currentPartSize = MIN((NSInteger)nextPartSize, MAX(0, (NSInteger)_availableSize - (NSInteger)processedSize));
if (_encryptFile)
{
if (_liveMode || processedSize + currentPartSize < _availableSize)
{
while (currentPartSize % 16 != 0)
{
currentPartSize--;
}
}
}
if (_dataProvider != nil)
partData = _dataProvider(processedSize, currentPartSize);
else
partData = [_fileItem readDataAtOffset:processedSize length:currentPartSize];
}
if (partData.length == (NSUInteger)currentPartSize)
{
NSData *alignedPartData = partData;
if (_encryptFile)
{
NSMutableData *tempData = [[NSMutableData alloc] initWithData:partData];
while (tempData.length == 0 || (NSInteger)tempData.length % 16 != 0)
{
uint8_t zero = 0;
[tempData appendBytes:&zero length:1];
}
MTAesEncryptInplaceAndModifyIv(tempData, _aesKey, _aesRunningIv);
alignedPartData = tempData;
}
MTRequest *request = [[MTRequest alloc] init];
TLRPCupload_saveFilePart$upload_saveFilePart *saveFilePart = [[TLRPCupload_saveFilePart$upload_saveFilePart alloc] init];
saveFilePart.file_id = _fileId;
saveFilePart.file_part = (int32_t)(isHeader ? 0 : ((_lateHeaderMode ? 1 : 0) + _doneParts.count + _uploadingParts.count));
saveFilePart.bytes = alignedPartData;
request.body = saveFilePart;
int32_t partIndex = saveFilePart.file_part;
TGLiveUploadPart *part = [[TGLiveUploadPart alloc] init];
part.partIndex = partIndex;
part.offset = isHeader ? 0 : processedSize;
part.length = currentPartSize;
part.cancelToken = request.internalId;
[_uploadingParts addObject:part];
TGLog(@"[TGLiveUploadActor#%p uploading part at %d (%d bytes)]", self, partIndex, (int)currentPartSize);
__weak TGLiveUploadActor *weakSelf = self;
[request setCompleted:^(__unused id result, __unused NSTimeInterval timestamp, id error)
{
[ActionStageInstance() dispatchOnStageQueue:^
{
__strong TGLiveUploadActor *strongSelf = weakSelf;
if (error == nil)
[strongSelf partUploadSuccess:partIndex];
else
[strongSelf partUploadFailed];
}];
}];
[_worker.strongWorker addRequest:request];
}
else
{
[self _fail];
return;
}
}
}
}
}
- (void)partUploadSuccess:(int32_t)partIndex
{
TGLog(@"[TGLiveUploadActor#%p finished part at %d]", self, (int)partIndex);
NSInteger index = -1;
for (TGLiveUploadPart *part in _uploadingParts)
{
index++;
if (part.partIndex == partIndex)
{
[_doneParts addObject:part];
[_uploadingParts removeObjectAtIndex:(NSUInteger)index];
break;
}
}
if (!_liveMode)
[self notifyAboutProgress];
[self _dequeuePartIfAny];
}
- (void)notifyAboutProgress
{
[ActionStageInstance() dispatchMessageToWatchers:self.path messageType:@"progress" message:@([self progress])];
}
- (void)partUploadFailed
{
[self _fail];
}
- (TGLiveUploadActorData *)finishRestOfFile:(NSUInteger)finalSize
{
NSUInteger alignedFinalSize = finalSize;
if (_encryptFile)
{
while (alignedFinalSize % 16 != 0)
{
alignedFinalSize++;
}
}
TGLiveUploadActorData *data = [[TGLiveUploadActorData alloc] init];
[ActionStageInstance() requestActor:self.path options:nil flags:-1 watcher:data];
data.path = self.path;
_liveMode = false;
_availableSize = finalSize;
[self _dequeuePartIfAny];
return data;
}
- (TGLiveUploadActorData *)finishRestOfFileWithHeader:(NSData *)header finalSize:(NSUInteger)finalSize
{
TGLiveUploadActorData *data = [[TGLiveUploadActorData alloc] init];
[ActionStageInstance() requestActor:self.path options:nil flags:-1 watcher:data];
data.path = self.path;
_liveMode = false;
_lateHeaderMode = false;
_availableSize = finalSize;
_unfinishedHeaderData = header;
for (TGLiveUploadPart *part in _uploadingParts)
{
part.offset += header.length;
}
for (TGLiveUploadPart *part in _doneParts)
{
part.offset += header.length;
}
_dataProvider = nil;
[self _dequeuePartIfAny];
return data;
}
- (void)completeWhenReady
{
_canComplete = true;
[self _dequeuePartIfAny];
}
- (float)progress
{
if (_availableSize > 0)
return MAX(0.0f, MIN(1.0f, [self doneSize] / (float)_availableSize));
return 0.0f;
}
- (void)cancel
{
for (TGLiveUploadPart *part in _uploadingParts)
{
[_worker.strongWorker cancelRequestByIdSoft:part.cancelToken];
}
[_uploadingParts removeAllObjects];
[super cancel];
}
@end