Initial commit

This commit is contained in:
Tha_14
2024-02-22 21:43:11 +02:00
commit 1b96a031d2
1108 changed files with 157706 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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