Created
September 25, 2022 19:47
-
-
Save TheDreamsWind/1fc5645f70bb04777f0e1db56b7ea639 to your computer and use it in GitHub Desktop.
[SO-a/73847407/5690248] `TDWFTPUploader` a helper class to upload files to ftp servers in iOS/macOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TDWFTPUploader.h | |
// iOSPlayground | |
// | |
// Created by Aleksandr Medvedev on 25.09.2022. | |
// | |
@import Foundation; | |
NS_ASSUME_NONNULL_BEGIN | |
typedef void(^TDWFTPUploaderCallback)(NSError *_Nullable); | |
@interface TDWFTPUploader : NSObject | |
@property (strong, readonly, nonatomic) NSURL *fileURL; | |
@property (strong, readonly, nonatomic) NSURL *uploadURL; | |
- (instancetype)initWithFileURL:(NSURL *)fileURL | |
uploadURL:(NSURL *)uploadURL | |
userLogin:(nullable NSString *)login | |
userPassword:(nullable NSString *)password; | |
- (void)resumeWithCallback:(nullable TDWFTPUploaderCallback)callback; | |
@end | |
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TDWFTPUploader.m | |
// iOSPlayground | |
// | |
// Created by Aleksandr Medvedev on 25.09.2022. | |
// | |
#import "TDWFTPUploader.h" | |
typedef NS_ENUM(int8_t, TDWFTPUploaderState) { | |
TDWFTPUploaderStateSuspended, | |
TDWFTPUploaderStateRunning, | |
TDWFTPUploaderStateCancelled, | |
TDWFTPUploaderStateFinished, | |
}; | |
@interface TDWFTPUploader () <NSStreamDelegate> | |
@property (strong, readonly, nonatomic) NSInputStream *inputStream; | |
@property (strong, readonly, nonatomic) NSOutputStream *outputStream; | |
@property (assign, nonatomic) TDWFTPUploaderState state; | |
@property (strong, readonly, nonatomic) dispatch_queue_t stateAccessQueue; | |
@property (assign, readonly, nonatomic) uint8_t *dataBuffer; | |
@property (assign, nonatomic) size_t dataBufferOffset; | |
@property (assign, nonatomic) size_t dataBufferLimit; | |
@property (strong, nonatomic) NSError *error; | |
@property (copy, nonatomic) TDWFTPUploaderCallback callback; | |
@end | |
static const size_t kDataBufferSize = 1 << 15; | |
@implementation TDWFTPUploader | |
@synthesize state = _state; | |
#pragma mark Lifecycle | |
- (instancetype)initWithFileURL:(NSURL *)fileURL | |
uploadURL:(NSURL *)uploadURL | |
userLogin:(NSString *)login | |
userPassword:(NSString *)password { | |
if (self = [super init]) { | |
NSInputStream *readStream = [NSInputStream inputStreamWithURL:fileURL]; | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |
CFWriteStreamRef writeStream = CFWriteStreamCreateWithFTPURL(kCFAllocatorDefault, (__bridge CFURLRef)uploadURL); | |
if (!readStream || !writeStream) { | |
return nil; | |
} | |
_outputStream = (__bridge_transfer NSOutputStream *)writeStream; | |
if (login) { | |
[_outputStream setProperty:login forKey:(__bridge NSString *)kCFStreamPropertyFTPUserName]; | |
} | |
if (password) { | |
[_outputStream setProperty:password forKey:(__bridge NSString *)kCFStreamPropertyFTPPassword]; | |
} | |
#pragma GCC diagnostic pop | |
_fileURL = fileURL; | |
_uploadURL = uploadURL; | |
_inputStream = readStream; | |
_outputStream.delegate = self; | |
_state = TDWFTPUploaderStateSuspended; | |
_stateAccessQueue = dispatch_queue_create("the.dreams.wind.queues.access.TDWFTPUploader.state", DISPATCH_QUEUE_CONCURRENT); | |
_dataBuffer = malloc(kDataBufferSize); | |
_callback = nil; | |
} | |
return self; | |
} | |
- (void)resumeWithCallback:(nullable TDWFTPUploaderCallback)callback { | |
if (self.state != TDWFTPUploaderStateSuspended) { | |
return; | |
} | |
self.callback = callback; | |
self.state = TDWFTPUploaderStateRunning; | |
[self performSelectorInBackground:@selector(p_uploadDataInBackground) withObject:nil]; | |
} | |
- (void)dealloc { | |
if (_outputStream) { | |
[_outputStream close]; | |
} | |
if (_inputStream) { | |
[_inputStream close]; | |
} | |
free(_dataBuffer); | |
} | |
#pragma mark Properties | |
- (TDWFTPUploaderState)state { | |
__block TDWFTPUploaderState tempState; | |
dispatch_sync(_stateAccessQueue, ^{ | |
tempState = _state; | |
}); | |
return tempState; | |
} | |
- (void)setState:(TDWFTPUploaderState)state { | |
typeof(self) __weak weakSelf = self; | |
dispatch_barrier_async(_stateAccessQueue, ^{ | |
if (!weakSelf) { | |
return; | |
} | |
typeof(weakSelf) __strong strongSelf = weakSelf; | |
strongSelf->_state = state; | |
}); | |
} | |
#pragma mark NSStreamDelegate | |
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { | |
if (self.state != TDWFTPUploaderStateRunning) { | |
[aStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode]; | |
return; | |
} | |
switch (eventCode) { | |
case NSStreamEventOpenCompleted: | |
[_inputStream open]; | |
return; | |
case NSStreamEventHasSpaceAvailable: | |
if (_dataBufferOffset == _dataBufferLimit) { | |
NSInteger bytesRead = [_inputStream read:_dataBuffer maxLength:kDataBufferSize]; | |
switch (bytesRead) { | |
case -1: | |
[self p_cancelWithError:_inputStream.streamError]; | |
return; | |
case 0: | |
[aStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode]; | |
self.state = TDWFTPUploaderStateFinished; | |
return; | |
default: | |
_dataBufferOffset = 0; | |
_dataBufferLimit = bytesRead; | |
} | |
} | |
if (_dataBufferOffset != _dataBufferLimit) { | |
NSInteger bytesWritten = [_outputStream write:&_dataBuffer[_dataBufferOffset] | |
maxLength:_dataBufferLimit - _dataBufferOffset]; | |
if (bytesWritten == -1) { | |
[self p_cancelWithError:_outputStream.streamError]; | |
return; | |
} else { | |
self.dataBufferOffset += bytesWritten; | |
} | |
} | |
return; | |
case NSStreamEventErrorOccurred: | |
[self p_cancelWithError:_outputStream.streamError]; | |
return; | |
default: | |
break; | |
} | |
} | |
#pragma mark Private | |
- (void)p_cancelWithError:(NSError *)error { | |
self.state = TDWFTPUploaderStateCancelled; | |
self.error = error; | |
[_outputStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode]; | |
} | |
- (void)p_uploadDataInBackground { | |
@autoreleasepool { | |
NSRunLoop *loop = NSRunLoop.currentRunLoop; | |
[_outputStream scheduleInRunLoop:loop forMode:NSDefaultRunLoopMode]; | |
[_outputStream open]; | |
while ( | |
self.state == TDWFTPUploaderStateRunning && | |
[loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]] | |
) {} | |
if (_callback) { | |
_callback(_error); | |
} | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment