Initial commit
This commit is contained in:
@ -0,0 +1,31 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OCTToxConstants.h"
|
||||
|
||||
@class OCTMessageAbstract;
|
||||
|
||||
@interface NSError (OCTFile)
|
||||
|
||||
+ (NSError *)sendFileErrorInternalError;
|
||||
+ (NSError *)sendFileErrorCannotReadFile;
|
||||
+ (NSError *)sendFileErrorCannotSaveFileToUploads;
|
||||
+ (NSError *)sendFileErrorFriendNotFound;
|
||||
+ (NSError *)sendFileErrorFriendNotConnected;
|
||||
+ (NSError *)sendFileErrorNameTooLong;
|
||||
+ (NSError *)sendFileErrorTooMany;
|
||||
+ (NSError *)sendFileErrorFromToxFileSendError:(OCTToxErrorFileSend)code;
|
||||
|
||||
+ (NSError *)acceptFileErrorInternalError;
|
||||
+ (NSError *)acceptFileErrorCannotWriteToFile;
|
||||
+ (NSError *)acceptFileErrorFriendNotFound;
|
||||
+ (NSError *)acceptFileErrorFriendNotConnected;
|
||||
+ (NSError *)acceptFileErrorWrongMessage:(OCTMessageAbstract *)message;
|
||||
+ (NSError *)acceptFileErrorFromToxFileSendChunkError:(OCTToxErrorFileSendChunk)code;
|
||||
+ (NSError *)acceptFileErrorFromToxFileControl:(OCTToxErrorFileControl)code;
|
||||
|
||||
+ (NSError *)fileTransferErrorWrongMessage:(OCTMessageAbstract *)message;
|
||||
|
||||
@end
|
@ -0,0 +1,189 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "NSError+OCTFile.h"
|
||||
#import "OCTManagerConstants.h"
|
||||
|
||||
@implementation NSError (OCTFile)
|
||||
|
||||
+ (NSError *)sendFileErrorInternalError
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSendFileErrorInternalError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Send file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Internal error",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)sendFileErrorCannotReadFile
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSendFileErrorCannotReadFile
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Send file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Cannot read file",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)sendFileErrorCannotSaveFileToUploads
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSendFileErrorCannotSaveFileToUploads
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Send file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Cannot save send file to uploads folder.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)sendFileErrorFriendNotFound
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSendFileErrorFriendNotFound
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Send file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Friend to send file to was not found.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)sendFileErrorFriendNotConnected
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSendFileErrorFriendNotConnected
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Send file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Friend is not connected at the moment.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)sendFileErrorNameTooLong
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSendFileErrorFriendNotConnected
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Send file",
|
||||
NSLocalizedFailureReasonErrorKey : @"File name is too long.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)sendFileErrorTooMany
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTSendFileErrorFriendNotConnected
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Send file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Too many active file transfers.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)sendFileErrorFromToxFileSendError:(OCTToxErrorFileSend)code
|
||||
{
|
||||
switch (code) {
|
||||
case OCTToxErrorFileSendUnknown:
|
||||
return [self sendFileErrorInternalError];
|
||||
case OCTToxErrorFileSendFriendNotFound:
|
||||
return [self sendFileErrorFriendNotFound];
|
||||
case OCTToxErrorFileSendFriendNotConnected:
|
||||
return [self sendFileErrorFriendNotConnected];
|
||||
case OCTToxErrorFileSendNameTooLong:
|
||||
return [self sendFileErrorNameTooLong];
|
||||
case OCTToxErrorFileSendTooMany:
|
||||
return [self sendFileErrorTooMany];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSError *)acceptFileErrorInternalError
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTAcceptFileErrorInternalError
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Download file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Internal error",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)acceptFileErrorCannotWriteToFile
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTAcceptFileErrorCannotWriteToFile
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Download file",
|
||||
NSLocalizedFailureReasonErrorKey : @"File is not available for writing.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)acceptFileErrorFriendNotFound
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTAcceptFileErrorFriendNotFound
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Download file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Friend to send file to was not found.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)acceptFileErrorFriendNotConnected
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTAcceptFileErrorFriendNotConnected
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Download file",
|
||||
NSLocalizedFailureReasonErrorKey : @"Friend is not connected at the moment.",
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)acceptFileErrorWrongMessage:(OCTMessageAbstract *)message
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTAcceptFileErrorWrongMessage
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Download file",
|
||||
NSLocalizedFailureReasonErrorKey : [NSString stringWithFormat:@"Specified wrong message %@", message],
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSError *)acceptFileErrorFromToxFileSendChunkError:(OCTToxErrorFileSendChunk)code
|
||||
{
|
||||
switch (code) {
|
||||
case OCTToxErrorFileSendChunkFriendNotFound:
|
||||
return [self acceptFileErrorFriendNotFound];
|
||||
case OCTToxErrorFileSendChunkFriendNotConnected:
|
||||
return [self acceptFileErrorFriendNotConnected];
|
||||
case OCTToxErrorFileSendChunkUnknown:
|
||||
case OCTToxErrorFileSendChunkNotFound:
|
||||
case OCTToxErrorFileSendChunkNotTransferring:
|
||||
case OCTToxErrorFileSendChunkInvalidLength:
|
||||
case OCTToxErrorFileSendChunkSendq:
|
||||
case OCTToxErrorFileSendChunkWrongPosition:
|
||||
return [self acceptFileErrorInternalError];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSError *)acceptFileErrorFromToxFileControl:(OCTToxErrorFileControl)code
|
||||
{
|
||||
switch (code) {
|
||||
case OCTToxErrorFileControlFriendNotFound:
|
||||
return [self acceptFileErrorFriendNotFound];
|
||||
case OCTToxErrorFileControlFriendNotConnected:
|
||||
return [self acceptFileErrorFriendNotConnected];
|
||||
case OCTToxErrorFileControlNotFound:
|
||||
case OCTToxErrorFileControlNotPaused:
|
||||
case OCTToxErrorFileControlDenied:
|
||||
case OCTToxErrorFileControlAlreadyPaused:
|
||||
case OCTToxErrorFileControlSendq:
|
||||
return [self acceptFileErrorInternalError];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSError *)fileTransferErrorWrongMessage:(OCTMessageAbstract *)message
|
||||
{
|
||||
return [NSError errorWithDomain:kOCTManagerErrorDomain
|
||||
code:OCTFileTransferErrorWrongMessage
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Error",
|
||||
NSLocalizedFailureReasonErrorKey : [NSString stringWithFormat:@"Specified wrong message %@", message],
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,42 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileBaseOperation.h"
|
||||
|
||||
@interface OCTFileBaseOperation (Private)
|
||||
|
||||
@property (weak, nonatomic, readonly, nullable) OCTTox *tox;
|
||||
|
||||
@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber;
|
||||
@property (assign, nonatomic, readonly) OCTToxFileNumber fileNumber;
|
||||
@property (assign, nonatomic, readonly) OCTToxFileSize fileSize;
|
||||
|
||||
/**
|
||||
* Override this method to start custom actions. Call finish when operation is done.
|
||||
*/
|
||||
- (void)operationStarted NS_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
* Override this method to do clean up on operation cancellation.
|
||||
*/
|
||||
- (void)operationWasCanceled NS_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
* Call this method to change bytes done value.
|
||||
*/
|
||||
- (void)updateBytesDone:(OCTToxFileSize)bytesDone;
|
||||
|
||||
/**
|
||||
* Call this method in case if operation was finished.
|
||||
*/
|
||||
- (void)finishWithSuccess;
|
||||
|
||||
/**
|
||||
* Call this method in case if operation was finished or cancelled with error.
|
||||
*
|
||||
* @param error Pass error if occured, nil on success.
|
||||
*/
|
||||
- (void)finishWithError:(nonnull NSError *)error;
|
||||
|
||||
@end
|
@ -0,0 +1,85 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OCTTox.h"
|
||||
#import "OCTToxConstants.h"
|
||||
|
||||
@class OCTFileBaseOperation;
|
||||
|
||||
/**
|
||||
* Block to notify about operation progress.
|
||||
*
|
||||
* @param operation Operation that is running.
|
||||
* @param progress Progress of operation. From 0.0 to 1.0.
|
||||
* @param bytesPerSecond Speed of loading.
|
||||
* @param eta Estimated time of finish of loading.
|
||||
*/
|
||||
typedef void (^OCTFileBaseOperationProgressBlock)(OCTFileBaseOperation *__nonnull operation);
|
||||
|
||||
/**
|
||||
* Block to notify about operation success.
|
||||
*
|
||||
* @param operation Operation that is running.
|
||||
* @param filePath Path of file being loaded
|
||||
*/
|
||||
typedef void (^OCTFileBaseOperationSuccessBlock)(OCTFileBaseOperation *__nonnull operation);
|
||||
|
||||
/**
|
||||
* Block to notify about operation failure.
|
||||
*
|
||||
* @param operation Operation that is running.
|
||||
*/
|
||||
typedef void (^OCTFileBaseOperationFailureBlock)(OCTFileBaseOperation *__nonnull operation, NSError *__nonnull error);
|
||||
|
||||
|
||||
@interface OCTFileBaseOperation : NSOperation
|
||||
|
||||
/**
|
||||
* Identifier of operation, unique for all active file operations.
|
||||
*/
|
||||
@property (strong, nonatomic, readonly, nonnull) NSString *operationId;
|
||||
|
||||
/**
|
||||
* Progress properties.
|
||||
*/
|
||||
@property (assign, nonatomic, readonly) OCTToxFileSize bytesDone;
|
||||
@property (assign, nonatomic, readonly) float progress;
|
||||
@property (assign, nonatomic, readonly) OCTToxFileSize bytesPerSecond;
|
||||
@property (assign, nonatomic, readonly) CFTimeInterval eta;
|
||||
|
||||
@property (strong, nonatomic, readonly, nullable) NSDictionary *userInfo;
|
||||
|
||||
/**
|
||||
* Creates operation id from file and friend number.
|
||||
*/
|
||||
+ (nonnull NSString *)operationIdFromFileNumber:(OCTToxFileNumber)fileNumber friendNumber:(OCTToxFriendNumber)friendNumber;
|
||||
|
||||
/**
|
||||
* Create operation.
|
||||
*
|
||||
* @param tox Tox object to load from.
|
||||
* @param friendNumber Number of friend.
|
||||
* @param fileNumber Number of file to load.
|
||||
* @param fileSize Size of file in bytes.
|
||||
* @param userInfo Any object that will be stored by operation.
|
||||
* @param progressBlock Block called to notify about loading progress. Block will be called on main thread.
|
||||
* @param etaUpdateBlock Block called to notify about loading eta update. Block will be called on main thread.
|
||||
* @param successBlock Block called on operation success. Block will be called on main thread.
|
||||
* @param failureBlock Block called on loading error. Block will be called on main thread.
|
||||
*/
|
||||
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
fileNumber:(OCTToxFileNumber)fileNumber
|
||||
fileSize:(OCTToxFileSize)fileSize
|
||||
userInfo:(nullable NSDictionary *)userInfo
|
||||
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
|
||||
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
|
||||
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
|
||||
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock;
|
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE;
|
||||
+ (nullable instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
@ -0,0 +1,286 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileBaseOperation.h"
|
||||
#import "OCTFileBaseOperation+Private.h"
|
||||
#import "OCTLogging.h"
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
static const CFTimeInterval kMinUpdateProgressInterval = 0.1;
|
||||
static const CFTimeInterval kMinUpdateEtaInterval = 1.0;
|
||||
|
||||
@interface OCTEtaObject : NSObject
|
||||
@property (assign, nonatomic) CFTimeInterval deltaTime;
|
||||
@property (assign, nonatomic) OCTToxFileSize deltaBytes;
|
||||
@end
|
||||
|
||||
@implementation OCTEtaObject
|
||||
@end
|
||||
|
||||
@interface OCTFileBaseOperation ()
|
||||
|
||||
@property (assign, atomic) BOOL privateExecuting;
|
||||
@property (assign, atomic) BOOL privateFinished;
|
||||
|
||||
@property (weak, nonatomic, readonly, nullable) OCTTox *tox;
|
||||
|
||||
@property (assign, nonatomic, readonly) OCTToxFriendNumber friendNumber;
|
||||
@property (assign, nonatomic, readonly) OCTToxFileNumber fileNumber;
|
||||
@property (assign, nonatomic, readonly) OCTToxFileSize fileSize;
|
||||
|
||||
@property (assign, nonatomic, readwrite) OCTToxFileSize bytesDone;
|
||||
@property (assign, nonatomic, readwrite) float progress;
|
||||
@property (assign, nonatomic, readwrite) OCTToxFileSize bytesPerSecond;
|
||||
@property (assign, nonatomic, readwrite) CFTimeInterval eta;
|
||||
|
||||
@property (copy, nonatomic) OCTFileBaseOperationProgressBlock progressBlock;
|
||||
@property (copy, nonatomic) OCTFileBaseOperationProgressBlock etaUpdateBlock;
|
||||
@property (copy, nonatomic) OCTFileBaseOperationSuccessBlock successBlock;
|
||||
@property (copy, nonatomic) OCTFileBaseOperationFailureBlock failureBlock;
|
||||
|
||||
@property (assign, nonatomic) CFTimeInterval lastUpdateProgressTime;
|
||||
@property (assign, nonatomic) OCTToxFileSize lastUpdateBytesDone;
|
||||
@property (assign, nonatomic) CFTimeInterval lastUpdateEtaProgressTime;
|
||||
@property (assign, nonatomic) OCTToxFileSize lastUpdateEtaBytesDone;
|
||||
|
||||
@property (strong, nonatomic) NSMutableArray *last10EtaObjects;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTFileBaseOperation
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
+ (NSString *)operationIdFromFileNumber:(OCTToxFileNumber)fileNumber friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
{
|
||||
return [NSString stringWithFormat:@"%d-%d", fileNumber, friendNumber];
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
fileNumber:(OCTToxFileNumber)fileNumber
|
||||
fileSize:(OCTToxFileSize)fileSize
|
||||
userInfo:(NSDictionary *)userInfo
|
||||
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
|
||||
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
|
||||
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
|
||||
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock
|
||||
{
|
||||
NSParameterAssert(tox);
|
||||
NSParameterAssert(fileSize > 0);
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_operationId = [[self class] operationIdFromFileNumber:fileNumber friendNumber:friendNumber];
|
||||
|
||||
_tox = tox;
|
||||
|
||||
_friendNumber = friendNumber;
|
||||
_fileNumber = fileNumber;
|
||||
_fileSize = fileSize;
|
||||
|
||||
_progress = 0.0;
|
||||
_bytesPerSecond = 0;
|
||||
_eta = 0;
|
||||
|
||||
_userInfo = userInfo;
|
||||
|
||||
_progressBlock = [progressBlock copy];
|
||||
_etaUpdateBlock = [etaUpdateBlock copy];
|
||||
_successBlock = [successBlock copy];
|
||||
_failureBlock = [failureBlock copy];
|
||||
|
||||
_bytesDone = 0;
|
||||
_lastUpdateProgressTime = 0;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setExecuting:(BOOL)executing
|
||||
{
|
||||
[self willChangeValueForKey:@"isExecuting"];
|
||||
self.privateExecuting = executing;
|
||||
[self didChangeValueForKey:@"isExecuting"];
|
||||
}
|
||||
|
||||
- (BOOL)isExecuting
|
||||
{
|
||||
return self.privateExecuting;
|
||||
}
|
||||
|
||||
- (void)setFinished:(BOOL)finished
|
||||
{
|
||||
[self willChangeValueForKey:@"isFinished"];
|
||||
self.privateFinished = finished;
|
||||
[self didChangeValueForKey:@"isFinished"];
|
||||
}
|
||||
|
||||
- (BOOL)isFinished
|
||||
{
|
||||
return self.privateFinished;
|
||||
}
|
||||
|
||||
#pragma mark - Private category
|
||||
|
||||
- (void)updateBytesDone:(OCTToxFileSize)bytesDone
|
||||
{
|
||||
self.bytesDone = bytesDone;
|
||||
|
||||
[self updateProgressIfNeeded:bytesDone];
|
||||
[self updateEtaIfNeeded:bytesDone];
|
||||
}
|
||||
|
||||
- (void)operationStarted
|
||||
{
|
||||
OCTLogInfo(@"start loading file with identifier %@", self.operationId);
|
||||
}
|
||||
|
||||
- (void)operationWasCanceled
|
||||
{
|
||||
OCTLogInfo(@"was cancelled");
|
||||
}
|
||||
|
||||
- (void)finishWithSuccess
|
||||
{
|
||||
OCTLogInfo(@"finished with success");
|
||||
|
||||
self.executing = NO;
|
||||
self.finished = YES;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.successBlock) {
|
||||
self.successBlock(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)finishWithError:(nonnull NSError *)error
|
||||
{
|
||||
NSParameterAssert(error);
|
||||
|
||||
OCTLogInfo(@"finished with error %@", error);
|
||||
|
||||
self.executing = NO;
|
||||
self.finished = YES;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.failureBlock) {
|
||||
self.failureBlock(self, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Override
|
||||
|
||||
- (void)start
|
||||
{
|
||||
if (self.cancelled) {
|
||||
self.finished = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
self.executing = YES;
|
||||
|
||||
self.lastUpdateProgressTime = CACurrentMediaTime();
|
||||
self.lastUpdateBytesDone = 0;
|
||||
self.lastUpdateEtaProgressTime = CACurrentMediaTime();
|
||||
self.lastUpdateEtaBytesDone = 0;
|
||||
self.last10EtaObjects = [NSMutableArray new];
|
||||
|
||||
[self operationStarted];
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
[super cancel];
|
||||
|
||||
[self operationWasCanceled];
|
||||
|
||||
self.executing = NO;
|
||||
self.finished = YES;
|
||||
}
|
||||
|
||||
- (BOOL)asynchronous
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateProgressIfNeeded:(OCTToxFileSize)bytesDone
|
||||
{
|
||||
CFTimeInterval time = CACurrentMediaTime();
|
||||
|
||||
CFTimeInterval deltaTime = time - self.lastUpdateProgressTime;
|
||||
|
||||
if (deltaTime <= kMinUpdateProgressInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.lastUpdateProgressTime = time;
|
||||
self.lastUpdateBytesDone = bytesDone;
|
||||
|
||||
self.progress = (float)bytesDone / self.fileSize;
|
||||
|
||||
OCTLogInfo(@"progress %.2f, bytes per second %lld, eta %.0f seconds", self.progress, self.bytesPerSecond, self.eta);
|
||||
|
||||
if (self.progressBlock) {
|
||||
self.progressBlock(self);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateEtaIfNeeded:(OCTToxFileSize)bytesDone
|
||||
{
|
||||
CFTimeInterval time = CACurrentMediaTime();
|
||||
|
||||
CFTimeInterval deltaTime = time - self.lastUpdateEtaProgressTime;
|
||||
|
||||
if (deltaTime <= kMinUpdateEtaInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
OCTToxFileSize deltaBytes = bytesDone - self.lastUpdateEtaBytesDone;
|
||||
OCTToxFileSize bytesLeft = self.fileSize - bytesDone;
|
||||
|
||||
self.lastUpdateEtaProgressTime = time;
|
||||
self.lastUpdateEtaBytesDone = bytesDone;
|
||||
|
||||
OCTEtaObject *etaObject = [OCTEtaObject new];
|
||||
etaObject.deltaTime = deltaTime;
|
||||
etaObject.deltaBytes = deltaBytes;
|
||||
|
||||
[self.last10EtaObjects addObject:etaObject];
|
||||
if (self.last10EtaObjects.count > 10) {
|
||||
[self.last10EtaObjects removeObjectAtIndex:0];
|
||||
}
|
||||
|
||||
CFTimeInterval totalDeltaTime = 0.0;
|
||||
OCTToxFileSize totalDeltaBytes = 0;
|
||||
|
||||
for (OCTEtaObject *object in self.last10EtaObjects) {
|
||||
totalDeltaTime += object.deltaTime;
|
||||
totalDeltaBytes += object.deltaBytes;
|
||||
}
|
||||
|
||||
self.bytesPerSecond = totalDeltaBytes / totalDeltaTime;
|
||||
|
||||
if (totalDeltaBytes) {
|
||||
self.eta = totalDeltaTime * bytesLeft / totalDeltaBytes;
|
||||
}
|
||||
|
||||
if (self.etaUpdateBlock) {
|
||||
self.etaUpdateBlock(self);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,15 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OCTFileInputProtocol.h"
|
||||
|
||||
@interface OCTFileDataInput : NSObject <OCTFileInputProtocol>
|
||||
|
||||
- (nullable instancetype)initWithData:(nonnull NSData *)data;
|
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE;
|
||||
+ (nullable instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
@ -0,0 +1,50 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileDataInput.h"
|
||||
#import "OCTLogging.h"
|
||||
|
||||
@interface OCTFileDataInput ()
|
||||
|
||||
@property (strong, nonatomic, readonly) NSData *data;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTFileDataInput
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (nullable instancetype)initWithData:(nonnull NSData *)data
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_data = data;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - OCTFileInputProtocol
|
||||
|
||||
- (BOOL)prepareToRead
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (nonnull NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length
|
||||
{
|
||||
@try {
|
||||
return [self.data subdataWithRange:NSMakeRange((NSUInteger)position, length)];
|
||||
}
|
||||
@catch (NSException *ex) {
|
||||
OCTLogWarn(@"catched exception %@", ex);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,15 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OCTFileOutputProtocol.h"
|
||||
|
||||
@interface OCTFileDataOutput : NSObject <OCTFileOutputProtocol>
|
||||
|
||||
/**
|
||||
* Result data. This property will contain data only after download finishes.
|
||||
*/
|
||||
@property (strong, nonatomic, readonly, nullable) NSData *resultData;
|
||||
|
||||
@end
|
@ -0,0 +1,42 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileDataOutput.h"
|
||||
|
||||
@interface OCTFileDataOutput ()
|
||||
|
||||
@property (strong, nonatomic) NSMutableData *tempData;
|
||||
@property (strong, nonatomic) NSData *resultData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTFileDataOutput
|
||||
|
||||
#pragma mark - OCTFileOutputProtocol
|
||||
|
||||
- (BOOL)prepareToWrite
|
||||
{
|
||||
self.tempData = [NSMutableData new];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)writeData:(nonnull NSData *)data
|
||||
{
|
||||
[self.tempData appendData:data];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)finishWriting
|
||||
{
|
||||
self.resultData = [self.tempData copy];
|
||||
self.tempData = nil;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
self.tempData = nil;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,45 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileBaseOperation.h"
|
||||
|
||||
@class OCTTox;
|
||||
@protocol OCTFileOutputProtocol;
|
||||
|
||||
/**
|
||||
* File operation for downloading file.
|
||||
*
|
||||
* When started will automatically send resume control to friend.
|
||||
*/
|
||||
@interface OCTFileDownloadOperation : OCTFileBaseOperation
|
||||
|
||||
@property (strong, nonatomic, readonly, nonnull) id<OCTFileOutputProtocol> output;
|
||||
|
||||
/**
|
||||
* Create operation.
|
||||
*
|
||||
* @param fileOutput Output to use as a destination for file transfer.
|
||||
*
|
||||
* For other parameters description see OCTFileBaseOperation.
|
||||
*/
|
||||
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
|
||||
fileOutput:(nonnull id<OCTFileOutputProtocol>)fileOutput
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
fileNumber:(OCTToxFileNumber)fileNumber
|
||||
fileSize:(OCTToxFileSize)fileSize
|
||||
userInfo:(nullable NSDictionary *)userInfo
|
||||
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
|
||||
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
|
||||
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
|
||||
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock;
|
||||
|
||||
/**
|
||||
* Call this method to get next chunk to operation.
|
||||
*
|
||||
* @param chunk Next chunk of data to append to file.
|
||||
* @param position Position in file to append chunk.
|
||||
*/
|
||||
- (void)receiveChunk:(nullable NSData *)chunk position:(OCTToxFileSize)position;
|
||||
|
||||
@end
|
@ -0,0 +1,111 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileDownloadOperation.h"
|
||||
#import "OCTFileBaseOperation+Private.h"
|
||||
#import "OCTFileOutputProtocol.h"
|
||||
#import "OCTLogging.h"
|
||||
#import "NSError+OCTFile.h"
|
||||
|
||||
@interface OCTFileDownloadOperation ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTFileDownloadOperation
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
|
||||
fileOutput:(nonnull id<OCTFileOutputProtocol>)fileOutput
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
fileNumber:(OCTToxFileNumber)fileNumber
|
||||
fileSize:(OCTToxFileSize)fileSize
|
||||
userInfo:(NSDictionary *)userInfo
|
||||
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
|
||||
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
|
||||
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
|
||||
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock
|
||||
{
|
||||
NSParameterAssert(fileOutput);
|
||||
|
||||
self = [super initWithTox:tox
|
||||
friendNumber:friendNumber
|
||||
fileNumber:fileNumber
|
||||
fileSize:fileSize
|
||||
userInfo:userInfo
|
||||
progressBlock:progressBlock
|
||||
etaUpdateBlock:etaUpdateBlock
|
||||
successBlock:successBlock
|
||||
failureBlock:failureBlock];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_output = fileOutput;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)receiveChunk:(NSData *)chunk position:(OCTToxFileSize)position
|
||||
{
|
||||
if (! chunk) {
|
||||
if ([self.output finishWriting]) {
|
||||
[self finishWithSuccess];
|
||||
}
|
||||
else {
|
||||
[self finishWithError:[NSError acceptFileErrorCannotWriteToFile]];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.bytesDone != position) {
|
||||
OCTLogWarn(@"bytesDone doesn't match position");
|
||||
[self.tox fileSendControlForFileNumber:self.fileNumber
|
||||
friendNumber:self.friendNumber
|
||||
control:OCTToxFileControlCancel
|
||||
error:nil];
|
||||
[self finishWithError:[NSError acceptFileErrorInternalError]];
|
||||
return;
|
||||
}
|
||||
|
||||
if (! [self.output writeData:chunk]) {
|
||||
[self finishWithError:[NSError acceptFileErrorCannotWriteToFile]];
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateBytesDone:self.bytesDone + chunk.length];
|
||||
}
|
||||
|
||||
#pragma mark - Override
|
||||
|
||||
- (void)operationStarted
|
||||
{
|
||||
[super operationStarted];
|
||||
|
||||
if (! [self.output prepareToWrite]) {
|
||||
[self finishWithError:[NSError acceptFileErrorCannotWriteToFile]];
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
if (! [self.tox fileSendControlForFileNumber:self.fileNumber
|
||||
friendNumber:self.friendNumber
|
||||
control:OCTToxFileControlResume
|
||||
error:&error]) {
|
||||
OCTLogWarn(@"cannot send control %@", error);
|
||||
[self finishWithError:[NSError acceptFileErrorFromToxFileControl:error.code]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)operationWasCanceled
|
||||
{
|
||||
[super operationWasCanceled];
|
||||
|
||||
[self.output cancel];
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,27 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OCTToxConstants.h"
|
||||
|
||||
@protocol OCTFileInputProtocol <NSObject>
|
||||
|
||||
/**
|
||||
* Prepare input to read. This method will be called before first call to bytesWithPosition:length:.
|
||||
*
|
||||
* @return YES on success, NO on failure.
|
||||
*/
|
||||
- (BOOL)prepareToRead;
|
||||
|
||||
/**
|
||||
* Provide bytes.
|
||||
*
|
||||
* @param position Start position to start reading from.
|
||||
* @param length Length of bytes to read.
|
||||
*
|
||||
* @return NSData on success, nil on failure
|
||||
*/
|
||||
- (nonnull NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length;
|
||||
|
||||
@end
|
@ -0,0 +1,37 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol OCTFileOutputProtocol <NSObject>
|
||||
|
||||
/**
|
||||
* Prepare input to write. This method will be called before first call to writeData:.
|
||||
*
|
||||
* @return YES on success, NO on failure.
|
||||
*/
|
||||
- (BOOL)prepareToWrite;
|
||||
|
||||
/**
|
||||
* Write data to output.
|
||||
*
|
||||
* @param data Data to write.
|
||||
*
|
||||
* @return YES on success, NO on failure.
|
||||
*/
|
||||
- (BOOL)writeData:(nonnull NSData *)data;
|
||||
|
||||
/**
|
||||
* This method is called after last writeData: method.
|
||||
*
|
||||
* @return YES on success, NO on failure.
|
||||
*/
|
||||
- (BOOL)finishWriting;
|
||||
|
||||
/**
|
||||
* This method is called if all progress was canceled. Do needed cleanup.
|
||||
*/
|
||||
- (void)cancel;
|
||||
|
||||
@end
|
@ -0,0 +1,15 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OCTFileInputProtocol.h"
|
||||
|
||||
@interface OCTFilePathInput : NSObject <OCTFileInputProtocol>
|
||||
|
||||
- (nullable instancetype)initWithFilePath:(nonnull NSString *)filePath;
|
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE;
|
||||
+ (nullable instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
@ -0,0 +1,61 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFilePathInput.h"
|
||||
#import "OCTLogging.h"
|
||||
|
||||
@interface OCTFilePathInput ()
|
||||
|
||||
@property (strong, nonatomic, readonly) NSString *filePath;
|
||||
@property (strong, nonatomic) NSFileHandle *handle;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTFilePathInput
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (nullable instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_filePath = filePath;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - OCTFileInputProtocol
|
||||
|
||||
- (BOOL)prepareToRead
|
||||
{
|
||||
self.handle = [NSFileHandle fileHandleForReadingAtPath:self.filePath];
|
||||
|
||||
if (! self.handle) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSData *)bytesWithPosition:(OCTToxFileSize)position length:(size_t)length
|
||||
{
|
||||
@try {
|
||||
if (self.handle.offsetInFile != position) {
|
||||
[self.handle seekToFileOffset:position];
|
||||
}
|
||||
|
||||
return [self.handle readDataOfLength:length];
|
||||
}
|
||||
@catch (NSException *ex) {
|
||||
OCTLogWarn(@"catched exception %@", ex);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,19 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "OCTFileOutputProtocol.h"
|
||||
|
||||
@interface OCTFilePathOutput : NSObject <OCTFileOutputProtocol>
|
||||
|
||||
@property (copy, nonatomic, readonly, nonnull) NSString *resultFilePath;
|
||||
|
||||
- (nullable instancetype)initWithTempFolder:(nonnull NSString *)tempFolder
|
||||
resultFolder:(nonnull NSString *)resultFolder
|
||||
fileName:(nonnull NSString *)fileName;
|
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE;
|
||||
+ (nullable instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
@ -0,0 +1,100 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFilePathOutput.h"
|
||||
#import "OCTLogging.h"
|
||||
#import "OCTFileTools.h"
|
||||
|
||||
@interface OCTFilePathOutput ()
|
||||
|
||||
@property (copy, nonatomic, readonly, nonnull) NSString *tempFilePath;
|
||||
|
||||
@property (strong, nonatomic) NSFileHandle *handle;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTFilePathOutput
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (nullable instancetype)initWithTempFolder:(nonnull NSString *)tempFolder
|
||||
resultFolder:(nonnull NSString *)resultFolder
|
||||
fileName:(nonnull NSString *)fileName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_tempFilePath = [OCTFileTools createNewFilePathInDirectory:tempFolder fileName:fileName];
|
||||
_resultFilePath = [OCTFileTools createNewFilePathInDirectory:resultFolder fileName:fileName];
|
||||
|
||||
// Create dummy file to reserve fileName.
|
||||
[[NSFileManager defaultManager] createFileAtPath:_resultFilePath contents:[NSData data] attributes:nil];
|
||||
|
||||
OCTLogInfo(@"temp path %@", _tempFilePath);
|
||||
OCTLogInfo(@"result path %@", _resultFilePath);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - OCTFileOutputProtocol
|
||||
|
||||
- (BOOL)prepareToWrite
|
||||
{
|
||||
if (! [[NSFileManager defaultManager] createFileAtPath:self.tempFilePath contents:nil attributes:nil]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.handle = [NSFileHandle fileHandleForWritingAtPath:self.tempFilePath];
|
||||
|
||||
if (! self.handle) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)writeData:(nonnull NSData *)data
|
||||
{
|
||||
@try {
|
||||
[self.handle writeData:data];
|
||||
return YES;
|
||||
}
|
||||
@catch (NSException *ex) {
|
||||
OCTLogWarn(@"catched exception %@", ex);
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)finishWriting
|
||||
{
|
||||
@try {
|
||||
[self.handle synchronizeFile];
|
||||
}
|
||||
@catch (NSException *ex) {
|
||||
OCTLogWarn(@"catched exception %@", ex);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
// Remove dummy file.
|
||||
if (! [fileManager removeItemAtPath:self.resultFilePath error:nil]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [[NSFileManager defaultManager] moveItemAtPath:self.tempFilePath toPath:self.resultFilePath error:nil];
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
self.handle = nil;
|
||||
|
||||
[[NSFileManager defaultManager] removeItemAtPath:self.tempFilePath error:nil];
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,20 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface OCTFileTools : NSObject
|
||||
|
||||
/**
|
||||
* Creates filePath in directory for given fileName. In case if file already exists appends " N" suffix,
|
||||
* e.g. "file 2.txt", "file 3.txt".
|
||||
*
|
||||
* @param directory Directory part of filePath.
|
||||
* @param fileName Name of the file to use in path.
|
||||
*
|
||||
* @return File path to file that does not exist.
|
||||
*/
|
||||
+ (nonnull NSString *)createNewFilePathInDirectory:(nonnull NSString *)directory fileName:(nonnull NSString *)fileName;
|
||||
|
||||
@end
|
@ -0,0 +1,77 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileTools.h"
|
||||
|
||||
@implementation OCTFileTools
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
+ (nonnull NSString *)createNewFilePathInDirectory:(nonnull NSString *)directory fileName:(nonnull NSString *)fileName
|
||||
{
|
||||
NSParameterAssert(directory);
|
||||
NSParameterAssert(fileName);
|
||||
|
||||
NSString *path = [directory stringByAppendingPathComponent:fileName];
|
||||
|
||||
if (! [self fileExistsAtPath:path]) {
|
||||
return path;
|
||||
}
|
||||
|
||||
NSString *base;
|
||||
NSString *pathExtension = [fileName pathExtension];
|
||||
NSInteger suffix;
|
||||
|
||||
[self getBaseString:&base andSuffix:&suffix fromString:[fileName stringByDeletingPathExtension]];
|
||||
|
||||
while (YES) {
|
||||
NSString *resultName = [base stringByAppendingFormat:@" %ld", (long)suffix];
|
||||
|
||||
if (pathExtension.length > 0) {
|
||||
resultName = [resultName stringByAppendingPathExtension:pathExtension];
|
||||
}
|
||||
|
||||
NSString *path = [directory stringByAppendingPathComponent:resultName];
|
||||
|
||||
if (! [self fileExistsAtPath:path]) {
|
||||
return path;
|
||||
}
|
||||
|
||||
suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
+ (BOOL)fileExistsAtPath:(NSString *)filePath
|
||||
{
|
||||
return [[NSFileManager defaultManager] fileExistsAtPath:filePath];
|
||||
}
|
||||
|
||||
+ (void)getBaseString:(NSString **)baseString andSuffix:(NSInteger *)suffix fromString:(NSString *)original
|
||||
{
|
||||
NSString *tempBase = original;
|
||||
NSInteger tempSuffix = 1;
|
||||
|
||||
NSArray *components = [tempBase componentsSeparatedByString:@" "];
|
||||
|
||||
if (components.count > 1) {
|
||||
NSString *lastComponent = components.lastObject;
|
||||
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:lastComponent];
|
||||
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] isSupersetOfSet:set]) {
|
||||
tempSuffix = [lastComponent integerValue];
|
||||
|
||||
// -1 for space.
|
||||
NSInteger index = tempBase.length - lastComponent.length - 1;
|
||||
tempBase = [tempBase substringToIndex:index];
|
||||
}
|
||||
}
|
||||
tempSuffix++;
|
||||
|
||||
*baseString = tempBase;
|
||||
*suffix = tempSuffix;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,39 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileBaseOperation.h"
|
||||
|
||||
@protocol OCTFileInputProtocol;
|
||||
|
||||
@interface OCTFileUploadOperation : OCTFileBaseOperation
|
||||
|
||||
@property (strong, nonatomic, readonly, nonnull) id<OCTFileInputProtocol> input;
|
||||
|
||||
/**
|
||||
* Create operation.
|
||||
*
|
||||
* @param fileInput Input to use as a source for file transfer.
|
||||
*
|
||||
* For other parameters description see OCTFileBaseOperation.
|
||||
*/
|
||||
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
|
||||
fileInput:(nonnull id<OCTFileInputProtocol>)fileInput
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
fileNumber:(OCTToxFileNumber)fileNumber
|
||||
fileSize:(OCTToxFileSize)fileSize
|
||||
userInfo:(nullable NSDictionary *)userInfo
|
||||
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
|
||||
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
|
||||
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
|
||||
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock;
|
||||
|
||||
/**
|
||||
* Call this method to request next chunk.
|
||||
*
|
||||
* @param position The file or stream position from which to continue reading.
|
||||
* @param length The number of bytes requested for the current chunk.
|
||||
*/
|
||||
- (void)chunkRequestWithPosition:(OCTToxFileSize)position length:(size_t)length;
|
||||
|
||||
@end
|
@ -0,0 +1,109 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#import "OCTFileUploadOperation.h"
|
||||
#import "OCTFileBaseOperation+Private.h"
|
||||
#import "OCTFileInputProtocol.h"
|
||||
#import "OCTLogging.h"
|
||||
#import "NSError+OCTFile.h"
|
||||
|
||||
@interface OCTFileUploadOperation ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation OCTFileUploadOperation
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (nullable instancetype)initWithTox:(nonnull OCTTox *)tox
|
||||
fileInput:(nonnull id<OCTFileInputProtocol>)fileInput
|
||||
friendNumber:(OCTToxFriendNumber)friendNumber
|
||||
fileNumber:(OCTToxFileNumber)fileNumber
|
||||
fileSize:(OCTToxFileSize)fileSize
|
||||
userInfo:(nullable NSDictionary *)userInfo
|
||||
progressBlock:(nullable OCTFileBaseOperationProgressBlock)progressBlock
|
||||
etaUpdateBlock:(nullable OCTFileBaseOperationProgressBlock)etaUpdateBlock
|
||||
successBlock:(nullable OCTFileBaseOperationSuccessBlock)successBlock
|
||||
failureBlock:(nullable OCTFileBaseOperationFailureBlock)failureBlock
|
||||
{
|
||||
NSParameterAssert(fileInput);
|
||||
|
||||
self = [super initWithTox:tox
|
||||
friendNumber:friendNumber
|
||||
fileNumber:fileNumber
|
||||
fileSize:fileSize
|
||||
userInfo:userInfo
|
||||
progressBlock:progressBlock
|
||||
etaUpdateBlock:etaUpdateBlock
|
||||
successBlock:successBlock
|
||||
failureBlock:failureBlock];
|
||||
|
||||
if (! self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_input = fileInput;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)chunkRequestWithPosition:(OCTToxFileSize)position length:(size_t)length
|
||||
{
|
||||
if (length == 0) {
|
||||
[self finishWithSuccess];
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *data = [self.input bytesWithPosition:position length:length];
|
||||
|
||||
if (! data) {
|
||||
[self finishWithError:[NSError sendFileErrorCannotReadFile]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
|
||||
BOOL result;
|
||||
do {
|
||||
result = [self.tox fileSendChunkForFileNumber:self.fileNumber
|
||||
friendNumber:self.friendNumber
|
||||
position:position
|
||||
data:data
|
||||
error:&error];
|
||||
|
||||
if (! result) {
|
||||
[NSThread sleepForTimeInterval:0.01];
|
||||
}
|
||||
}
|
||||
while (! result && error.code == OCTToxErrorFileSendChunkSendq);
|
||||
|
||||
if (! result) {
|
||||
OCTLogWarn(@"upload error %@", error);
|
||||
|
||||
[self.tox fileSendControlForFileNumber:self.fileNumber
|
||||
friendNumber:self.friendNumber
|
||||
control:OCTToxFileControlCancel
|
||||
error:nil];
|
||||
|
||||
[self finishWithError:[NSError acceptFileErrorFromToxFileSendChunkError:error.code]];
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateBytesDone:position + length];
|
||||
}
|
||||
|
||||
#pragma mark - Override
|
||||
|
||||
- (void)operationStarted
|
||||
{
|
||||
[super operationStarted];
|
||||
|
||||
if (! [self.input prepareToRead]) {
|
||||
[self finishWithError:[NSError sendFileErrorCannotReadFile]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
Reference in New Issue
Block a user