mirror of
https://github.com/danog/Telegram.git
synced 2024-12-02 09:27:55 +01:00
628 lines
21 KiB
Objective-C
628 lines
21 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 "TGMultipartFileDownloadActor.h"
|
|
|
|
#import "TL/TLMetaScheme.h"
|
|
|
|
#import "ActionStage.h"
|
|
|
|
#import "TGTelegraph.h"
|
|
|
|
#import "TGTelegramNetworking.h"
|
|
#import "TGNetworkWorker.h"
|
|
|
|
#import <MTProtoKit/MTEncryption.h>
|
|
#import <MTProtoKit/MTRequest.h>
|
|
|
|
@interface TGFilePartInfo : NSObject
|
|
|
|
@property (nonatomic, strong) id token;
|
|
@property (nonatomic) NSTimeInterval startTime;
|
|
@property (nonatomic) int partLength;
|
|
@property (nonatomic) int downloadedLength;
|
|
@property (nonatomic) NSData *downloadedData;
|
|
|
|
@end
|
|
|
|
@implementation TGFilePartInfo
|
|
|
|
@end
|
|
|
|
@interface TGMultipartFileDownloadActor ()
|
|
{
|
|
TLInputFileLocation *_fileLocation;
|
|
int _datacenterId;
|
|
int _size;
|
|
NSMutableArray *_storeFilePaths;
|
|
NSString *_tempStoreFilePath;
|
|
|
|
NSString *_contextStoreFilePath;
|
|
|
|
NSMutableDictionary *_downloadingParts;
|
|
NSMutableArray *_explicitDownloadParts;
|
|
NSOutputStream *_fileStream;
|
|
NSOutputStream *_encryptionContextStream;
|
|
int _currentEncryptionContextStreamCount;
|
|
|
|
NSData *_encryptionKey;
|
|
NSData *_encryptionIv;
|
|
NSMutableData *_runningEncryptionIv;
|
|
int _decryptedSize;
|
|
|
|
int _downloadedFileSize;
|
|
|
|
bool _reportedProgress;
|
|
int _lastReportedProgress;
|
|
|
|
NSTimeInterval _averageDownloadSpeed;
|
|
int _takenTimeSamples;
|
|
|
|
float _progress;
|
|
|
|
id _worker1Token;
|
|
id _worker2Token;
|
|
TGNetworkWorkerGuard *_worker1;
|
|
TGNetworkWorkerGuard *_worker2;
|
|
|
|
int _nextWorker;
|
|
|
|
bool _completeWithData;
|
|
}
|
|
|
|
@end
|
|
|
|
@interface TGFilePartRequestInfo : NSObject
|
|
|
|
@property (nonatomic) int offset;
|
|
@property (nonatomic) int length;
|
|
|
|
@end
|
|
|
|
@implementation TGFilePartRequestInfo
|
|
|
|
@end
|
|
|
|
@implementation TGMultipartFileDownloadActor
|
|
|
|
+ (NSString *)genericPath
|
|
{
|
|
return @"/tg/multipart-file/@";
|
|
}
|
|
|
|
- (instancetype)initWithPath:(NSString *)path
|
|
{
|
|
self = [super initWithPath:path];
|
|
if (self != nil)
|
|
{
|
|
_downloadingParts = [[NSMutableDictionary alloc] init];
|
|
_explicitDownloadParts = [[NSMutableArray alloc] init];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if (_worker1Token != nil)
|
|
{
|
|
[[TGTelegramNetworking instance] cancelDownloadWorkerRequestByToken:_worker1Token];
|
|
_worker1Token = nil;
|
|
}
|
|
|
|
if (_worker2Token != nil)
|
|
{
|
|
[[TGTelegramNetworking instance] cancelDownloadWorkerRequestByToken:_worker2Token];
|
|
_worker2Token = nil;
|
|
}
|
|
|
|
[_worker1 releaseWorker];
|
|
[_worker2 releaseWorker];
|
|
|
|
if (_fileStream != nil)
|
|
{
|
|
[_fileStream close];
|
|
_fileStream = nil;
|
|
}
|
|
|
|
if (_encryptionContextStream != nil)
|
|
{
|
|
[_encryptionContextStream close];
|
|
_encryptionContextStream = nil;
|
|
}
|
|
}
|
|
|
|
- (void)storeCurrentIv
|
|
{
|
|
if (_contextStoreFilePath != nil)
|
|
{
|
|
[_runningEncryptionIv writeToFile:_contextStoreFilePath atomically:false];
|
|
}
|
|
}
|
|
|
|
- (void)execute:(NSDictionary *)options
|
|
{
|
|
_storeFilePaths = [[NSMutableArray alloc] init];
|
|
|
|
_fileLocation = options[@"fileLocation"];
|
|
_size = [options[@"encryptedSize"] intValue];
|
|
_decryptedSize = [options[@"decryptedSize"] intValue];
|
|
if (options[@"storeFilePath"] != nil)
|
|
[_storeFilePaths addObject:options[@"storeFilePath"]];
|
|
_datacenterId = [options[@"datacenterId"] intValue];
|
|
_completeWithData = [options[@"completeWithData"] boolValue];
|
|
|
|
if (options[@"encryptionArgs"][@"key"] != nil)
|
|
{
|
|
_encryptionKey = options[@"encryptionArgs"][@"key"];
|
|
_encryptionIv = options[@"encryptionArgs"][@"iv"];
|
|
_runningEncryptionIv = _encryptionIv.length == 0 ? nil : [[NSMutableData alloc] initWithData:_encryptionIv];
|
|
}
|
|
|
|
if (_fileLocation != nil && _storeFilePaths.count != 0)
|
|
{
|
|
_tempStoreFilePath = options[@"tempStoreFilePath"];
|
|
if (_tempStoreFilePath == nil)
|
|
_tempStoreFilePath = [_storeFilePaths[0] stringByAppendingString:@".parts"];
|
|
|
|
NSFileManager *fileManager = [ActionStageInstance() globalFileManager];
|
|
|
|
NSError *error = nil;
|
|
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:_tempStoreFilePath error:&error];
|
|
int fileSize = [[fileAttributes objectForKey:NSFileSize] intValue];
|
|
|
|
if (fileAttributes == nil || (fileSize % 1024 != 0 && fileSize != _decryptedSize && _decryptedSize != 0))
|
|
{
|
|
if (fileAttributes != nil)
|
|
{
|
|
TGLog(@"Invalid partial file length %d, restarting", fileSize);
|
|
fileSize = 0;
|
|
[self cancelRequests];
|
|
}
|
|
[fileManager createFileAtPath:_tempStoreFilePath contents:[NSData data] attributes:nil];
|
|
}
|
|
|
|
_downloadedFileSize = fileSize;
|
|
|
|
if (_runningEncryptionIv != nil)
|
|
{
|
|
_contextStoreFilePath = [_tempStoreFilePath stringByAppendingString:@".info"];
|
|
NSData *previousData = [[NSData alloc] initWithContentsOfFile:_contextStoreFilePath];
|
|
if (previousData != nil && previousData.length >= _runningEncryptionIv.length)
|
|
_runningEncryptionIv = [[NSMutableData alloc] initWithData:[previousData subdataWithRange:NSMakeRange(previousData.length - _runningEncryptionIv.length, _runningEncryptionIv.length)]];
|
|
|
|
_currentEncryptionContextStreamCount = (int)(previousData.length / _runningEncryptionIv.length);
|
|
_encryptionContextStream = [NSOutputStream outputStreamToFileAtPath:_contextStoreFilePath append:true];
|
|
[_encryptionContextStream open];
|
|
}
|
|
|
|
_fileStream = [[NSOutputStream alloc] initToFileAtPath:_tempStoreFilePath append:true];
|
|
[_fileStream open];
|
|
|
|
#if TGUseModernNetworking
|
|
__weak TGMultipartFileDownloadActor *weakSelf = self;
|
|
_worker1Token = [[TGTelegramNetworking instance] requestDownloadWorkerForDatacenterId:_datacenterId completion:^(TGNetworkWorkerGuard *worker)
|
|
{
|
|
__strong TGMultipartFileDownloadActor *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
{
|
|
[strongSelf downloadFilePartsWithWorker:worker];
|
|
}
|
|
}];
|
|
|
|
_worker2Token = [[TGTelegramNetworking instance] requestDownloadWorkerForDatacenterId:_datacenterId completion:^(TGNetworkWorkerGuard *worker)
|
|
{
|
|
__strong TGMultipartFileDownloadActor *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
{
|
|
[strongSelf assignAdditionalWorker:worker];
|
|
}
|
|
}];
|
|
#else
|
|
[self downloadFilePartsWithWorker:nil];
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
[ActionStageInstance() actionFailed:self.path reason:-1];
|
|
}
|
|
}
|
|
|
|
- (void)watcherJoined:(ASHandle *)watcherHandle options:(NSDictionary *)options waitingInActorQueue:(bool)waitingInActorQueue
|
|
{
|
|
[super watcherJoined:watcherHandle options:options waitingInActorQueue:waitingInActorQueue];
|
|
|
|
if (options[@"storeFilePath"] != nil && ![_storeFilePaths containsObject:options[@"storeFilePath"]])
|
|
[_storeFilePaths addObject:options[@"storeFilePath"]];
|
|
}
|
|
|
|
- (int)preferredPartSize
|
|
{
|
|
return 128 * 1024;
|
|
}
|
|
|
|
- (void)recordTimeSample:(int)length startTime:(NSTimeInterval)startTime
|
|
{
|
|
NSTimeInterval downloadTime = CFAbsoluteTimeGetCurrent() - startTime;
|
|
float downloadSpeed = ((float)length) / ((float)downloadTime);
|
|
|
|
_takenTimeSamples++;
|
|
_averageDownloadSpeed = (_averageDownloadSpeed + downloadSpeed) / 2.0f;
|
|
|
|
#ifdef DEBUG
|
|
TGLog(@"Average speed: %f kbytes/s", _averageDownloadSpeed / 1024.0f);
|
|
#endif
|
|
}
|
|
|
|
- (void)downloadFilePartsWithWorker:(TGNetworkWorkerGuard *)worker
|
|
{
|
|
[ActionStageInstance() dispatchOnStageQueue:^
|
|
{
|
|
_worker1 = worker;
|
|
|
|
[self downloadFileParts];
|
|
}];
|
|
}
|
|
|
|
- (void)assignAdditionalWorker:(TGNetworkWorkerGuard *)worker
|
|
{
|
|
[ActionStageInstance() dispatchOnStageQueue:^
|
|
{
|
|
_worker2 = worker;
|
|
|
|
[self downloadFileParts];
|
|
}];
|
|
}
|
|
|
|
- (void)downloadFileParts
|
|
{
|
|
if (_size != 0 && _downloadedFileSize >= _size)
|
|
{
|
|
if (_fileStream != nil)
|
|
{
|
|
[_fileStream close];
|
|
_fileStream = nil;
|
|
}
|
|
|
|
if (_encryptionContextStream != nil)
|
|
{
|
|
[_encryptionContextStream close];
|
|
_encryptionContextStream = nil;
|
|
}
|
|
|
|
if (_contextStoreFilePath != nil)
|
|
[[NSFileManager defaultManager] removeItemAtPath:_contextStoreFilePath error:nil];
|
|
|
|
for (NSString *storeFilePath in _storeFilePaths)
|
|
{
|
|
[[ActionStageInstance() globalFileManager] copyItemAtPath:_tempStoreFilePath toPath:storeFilePath error:NULL];
|
|
}
|
|
[[ActionStageInstance() globalFileManager] removeItemAtPath:_tempStoreFilePath error:NULL];
|
|
|
|
[ActionStageInstance() actionCompleted:self.path result:_completeWithData ? [[NSData alloc] initWithContentsOfFile:_storeFilePaths[0]] : nil];
|
|
}
|
|
else
|
|
{
|
|
if (!_reportedProgress)
|
|
{
|
|
_reportedProgress = true;
|
|
|
|
[self reportProgress:true];
|
|
}
|
|
|
|
NSMutableArray *requestList = [[NSMutableArray alloc] init];
|
|
|
|
__block int activeDownloadingParts = 0;
|
|
|
|
[_downloadingParts enumerateKeysAndObjectsUsingBlock:^(__unused NSNumber *offset, TGFilePartInfo *partInfo, __unused BOOL *stop)
|
|
{
|
|
if (partInfo.downloadedData == nil)
|
|
activeDownloadingParts++;
|
|
}];
|
|
|
|
int partSize = [self preferredPartSize];
|
|
|
|
while (activeDownloadingParts < (_size != 0 ? 4 : 1))
|
|
{
|
|
if (_explicitDownloadParts.count == 0)
|
|
{
|
|
__block int nextDownloadOffset = _downloadedFileSize;
|
|
|
|
[_downloadingParts enumerateKeysAndObjectsUsingBlock:^(NSNumber *offset, TGFilePartInfo *partInfo, __unused BOOL *stop)
|
|
{
|
|
if ([offset intValue] + partInfo.partLength > nextDownloadOffset)
|
|
nextDownloadOffset = [offset intValue] + partInfo.partLength;
|
|
}];
|
|
|
|
if (_size != 0 && nextDownloadOffset >= _size)
|
|
break;
|
|
|
|
int partLength = 0;
|
|
if (_size == 0)
|
|
partLength = partSize;
|
|
else
|
|
partLength = MIN(partSize, _size - _downloadedFileSize);
|
|
|
|
TGFilePartRequestInfo *requestInfo = [[TGFilePartRequestInfo alloc] init];
|
|
requestInfo.offset = nextDownloadOffset;
|
|
requestInfo.length = partLength;
|
|
[requestList addObject:requestInfo];
|
|
|
|
TGFilePartInfo *partInfo = [[TGFilePartInfo alloc] init];
|
|
partInfo.partLength = partLength;
|
|
_downloadingParts[@(nextDownloadOffset)] = partInfo;
|
|
|
|
activeDownloadingParts++;
|
|
}
|
|
else
|
|
{
|
|
TGFilePartRequestInfo *explicitPartRequest = [_explicitDownloadParts firstObject];
|
|
[requestList addObject:explicitPartRequest];
|
|
|
|
TGFilePartInfo *partInfo = [[TGFilePartInfo alloc] init];
|
|
partInfo.partLength = explicitPartRequest.length;
|
|
_downloadingParts[@(explicitPartRequest.offset)] = partInfo;
|
|
activeDownloadingParts++;
|
|
|
|
[_explicitDownloadParts removeObjectAtIndex:0];
|
|
}
|
|
}
|
|
|
|
for (TGFilePartRequestInfo *requestInfo in requestList)
|
|
{
|
|
#if TGUseModernNetworking
|
|
MTRequest *request = [[MTRequest alloc] init];
|
|
|
|
TLRPCupload_getFile$upload_getFile *getFile = [[TLRPCupload_getFile$upload_getFile alloc] init];
|
|
|
|
TLInputFileLocation *location = _fileLocation;
|
|
getFile.location = location;
|
|
getFile.offset = requestInfo.offset;
|
|
getFile.limit = requestInfo.length;
|
|
request.body = getFile;
|
|
|
|
__weak TGMultipartFileDownloadActor *weakSelf = self;
|
|
[request setCompleted:^(TLupload_File *result, __unused NSTimeInterval timestamp, id error)
|
|
{
|
|
[ActionStageInstance() dispatchOnStageQueue:^
|
|
{
|
|
__strong TGMultipartFileDownloadActor *strongSelf = weakSelf;
|
|
|
|
if (error == nil)
|
|
[strongSelf filePartDownloadSuccess:location offset:requestInfo.offset length:requestInfo.length data:result.bytes];
|
|
else
|
|
[strongSelf filePartDownloadFailed:location offset:requestInfo.offset length:requestInfo.length];
|
|
}];
|
|
}];
|
|
|
|
[request setProgressUpdated:^(float progress, NSUInteger packetLength)
|
|
{
|
|
[ActionStageInstance() dispatchOnStageQueue:^
|
|
{
|
|
__strong TGMultipartFileDownloadActor *strongSelf = weakSelf;
|
|
|
|
[strongSelf filePartDownloadProgress:location offset:requestInfo.offset length:requestInfo.length packetLength:(int)packetLength progress:progress];
|
|
}];
|
|
}];
|
|
|
|
TGNetworkWorkerGuard *worker = _worker1;
|
|
if (_worker2 != nil)
|
|
{
|
|
_nextWorker++;
|
|
if (_nextWorker % 2 == 0)
|
|
worker = _worker2;
|
|
}
|
|
|
|
id token = request.internalId;
|
|
[worker.strongWorker addRequest:request];
|
|
#else
|
|
id token = [TGTelegraphInstance doDownloadFilePart:_datacenterId location:_fileLocation offset:requestInfo.offset length:requestInfo.length actor:(id<TGFileDownloadActor>)self];
|
|
#endif
|
|
((TGFilePartInfo *)_downloadingParts[@(requestInfo.offset)]).token = token;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)filePartDownloadProgress:(TLInputFileLocation *)__unused location offset:(int)offset length:(int)__unused length packetLength:(int)packetLength progress:(float)progress
|
|
{
|
|
TGFilePartInfo *partInfo = _downloadingParts[@(offset)];
|
|
if (partInfo != nil)
|
|
{
|
|
partInfo.downloadedLength = (int)(progress * packetLength);
|
|
}
|
|
|
|
[self reportProgress:false];
|
|
}
|
|
|
|
- (void)reportProgress:(bool)force
|
|
{
|
|
if (_size == 0)
|
|
return;
|
|
|
|
__block int readyFileSize = _downloadedFileSize;
|
|
|
|
[_downloadingParts enumerateKeysAndObjectsUsingBlock:^(__unused NSNumber *nOffset, TGFilePartInfo *partInfo, __unused BOOL *stop)
|
|
{
|
|
readyFileSize += partInfo.downloadedLength;
|
|
}];
|
|
|
|
if (force || (_lastReportedProgress < readyFileSize && (_lastReportedProgress / 16 * 1024 != readyFileSize / 16 * 1024 || readyFileSize == _size)))
|
|
{
|
|
_lastReportedProgress = readyFileSize;
|
|
|
|
_progress = MIN(1.0f, MAX(0.001f, ((float)readyFileSize) / ((float)_size)));
|
|
|
|
[ActionStageInstance() dispatchMessageToWatchers:self.path messageType:@"progress" message:[[NSNumber alloc] initWithFloat:_progress]];
|
|
}
|
|
}
|
|
|
|
- (void)filePartDownloadSuccess:(TLInputFileLocation *)__unused location offset:(int)offset length:(int)length data:(NSData *)data
|
|
{
|
|
if (self.cancelled)
|
|
return;
|
|
|
|
if (_size == 0 && (int)data.length < length)
|
|
_size = _downloadedFileSize + (int)data.length;
|
|
|
|
TGFilePartInfo *partInfo = _downloadingParts[@(offset)];
|
|
if (partInfo != nil)
|
|
{
|
|
partInfo.token = nil;
|
|
partInfo.downloadedLength = length;
|
|
|
|
[self recordTimeSample:length startTime:partInfo.startTime];
|
|
}
|
|
|
|
int fileSize = _downloadedFileSize;
|
|
|
|
if (partInfo != nil)
|
|
{
|
|
partInfo.downloadedData = data;
|
|
|
|
int nextWriteOffset = fileSize;
|
|
|
|
NSMutableArray *sortedDownloadingParts = [[NSMutableArray alloc] init];
|
|
for (NSNumber *nOffset in _downloadingParts)
|
|
{
|
|
[sortedDownloadingParts addObject:nOffset];
|
|
}
|
|
|
|
[sortedDownloadingParts sortUsingComparator:^NSComparisonResult(NSNumber *number1, NSNumber *number2)
|
|
{
|
|
return [number1 compare:number2];
|
|
}];
|
|
|
|
while (true)
|
|
{
|
|
bool hadCommit = false;
|
|
|
|
for (NSNumber *nOffset in sortedDownloadingParts)
|
|
{
|
|
if ([nOffset intValue] == nextWriteOffset)
|
|
{
|
|
TGFilePartInfo *listPartInfo = _downloadingParts[nOffset];
|
|
if (listPartInfo.downloadedData == nil)
|
|
break;
|
|
|
|
hadCommit = true;
|
|
|
|
NSData *dataToWrite = listPartInfo.downloadedData;
|
|
|
|
if (_encryptionKey != nil && _runningEncryptionIv != nil)
|
|
{
|
|
NSMutableData *decryptedData = [[NSMutableData alloc] initWithData:dataToWrite];
|
|
if (decryptedData.length % 16 == 0)
|
|
MTAesDecryptInplaceAndModifyIv(decryptedData, _encryptionKey, _runningEncryptionIv);
|
|
else
|
|
TGLog(@"**** Error: encrypted data length % 16 != 0");
|
|
dataToWrite = decryptedData;
|
|
|
|
if (_decryptedSize != 0 && _decryptedSize >= _downloadedFileSize && _downloadedFileSize + (int)decryptedData.length > _decryptedSize)
|
|
{
|
|
[decryptedData setLength:_decryptedSize - _downloadedFileSize];
|
|
}
|
|
|
|
if (_currentEncryptionContextStreamCount >= 32)
|
|
{
|
|
_currentEncryptionContextStreamCount = 0;
|
|
[_encryptionContextStream close];
|
|
|
|
_encryptionContextStream = [NSOutputStream outputStreamToFileAtPath:_contextStoreFilePath append:true];
|
|
[_encryptionContextStream open];
|
|
}
|
|
else
|
|
_currentEncryptionContextStreamCount++;
|
|
|
|
[_encryptionContextStream writeData:_runningEncryptionIv];
|
|
}
|
|
|
|
[_fileStream writeData:dataToWrite];
|
|
|
|
_downloadedFileSize += listPartInfo.downloadedData.length;
|
|
nextWriteOffset += listPartInfo.downloadedData.length;
|
|
|
|
if (_size != 0 && (int)listPartInfo.downloadedData.length < listPartInfo.partLength && _downloadedFileSize + (int)listPartInfo.downloadedData.length < _size)
|
|
{
|
|
int dataGapLength = listPartInfo.partLength - (int)listPartInfo.downloadedData.length;
|
|
TGLog(@"Data gap found: %d bytes", dataGapLength);
|
|
|
|
TGFilePartRequestInfo *gapInfo = [[TGFilePartRequestInfo alloc] init];
|
|
gapInfo.offset = nextWriteOffset;
|
|
gapInfo.length = dataGapLength;
|
|
[_explicitDownloadParts addObject:gapInfo];
|
|
}
|
|
|
|
[_downloadingParts removeObjectForKey:nOffset];
|
|
}
|
|
}
|
|
|
|
if (!hadCommit)
|
|
break;
|
|
}
|
|
|
|
[self reportProgress:false];
|
|
|
|
if (!self.cancelled)
|
|
[self downloadFileParts];
|
|
}
|
|
else
|
|
{
|
|
[ActionStageInstance() actionFailed:self.path reason:-1];
|
|
}
|
|
}
|
|
|
|
- (void)filePartDownloadFailed:(TLInputFileLocation *)__unused location offset:(int)offset length:(int)__unused length
|
|
{
|
|
TGFilePartInfo *partInfo = _downloadingParts[@(offset)];
|
|
if (partInfo != nil)
|
|
[_downloadingParts removeObjectForKey:@(offset)];
|
|
|
|
self.cancelled = true;
|
|
|
|
[self cancelRequests];
|
|
[ActionStageInstance() actionFailed:self.path reason:-1];
|
|
}
|
|
|
|
- (void)cancelRequests
|
|
{
|
|
if (_worker1Token != nil)
|
|
{
|
|
[[TGTelegramNetworking instance] cancelDownloadWorkerRequestByToken:_worker1Token];
|
|
_worker1Token = nil;
|
|
}
|
|
|
|
if (_worker2Token != nil)
|
|
{
|
|
[[TGTelegramNetworking instance] cancelDownloadWorkerRequestByToken:_worker2Token];
|
|
_worker2Token = nil;
|
|
}
|
|
|
|
[_downloadingParts enumerateKeysAndObjectsUsingBlock:^(__unused NSNumber *nOffset, TGFilePartInfo *partInfo, __unused BOOL *stop)
|
|
{
|
|
if (partInfo.token != nil)
|
|
{
|
|
[TGTelegraphInstance cancelRequestByToken:partInfo.token];
|
|
[_worker1.strongWorker cancelRequestById:partInfo.token];
|
|
[_worker2.strongWorker cancelRequestById:partInfo.token];
|
|
}
|
|
}];
|
|
|
|
[_worker1 releaseWorker];
|
|
[_worker2 releaseWorker];
|
|
|
|
[_downloadingParts removeAllObjects];
|
|
}
|
|
|
|
- (void)cancel
|
|
{
|
|
[self cancelRequests];
|
|
|
|
[super cancel];
|
|
}
|
|
|
|
@end
|