#if TARGET_IPHONE#import #endif#import #import typedef enum {SCRFTPRequestOperationDownload,SCRFTPRequestOperationUpload,SCRFTPRequestOperationCreateDirectory} SCRFTPRequestOperation;typedef enum { SCRFTPConnectionFailureErrorType = 1, SCRFTPRequestTimedOutErrorType
= 2, SCRFTPAuthenticationErrorType = 3, SCRFTPRequestCancelledErrorType = 4,SCRFTPUnableToCreateRequestErrorType = 5,SCRFTPInternalErrorWhileBuildingRequestType = 6, SCRFTPInternalErrorWhileApplyingCredentialsType = 7} SCRFTPRequestErrorType;typedef enum {SCRFTPRequestStatusNone,SCRFTPRequestStatusOpenNetworkConnection,SCRFTPRequestStatusReadingFromStream,SCRFTPRequestStatusWritingToStream,SCRFTPRequestStatusClosedNetworkConnection,SCRFTPRequestStatusError}
SCRFTPRequestStatus;/* When using file streams, the 32KB buffer is probably not enough. * A good way to establish a buffer size is to increase it over time. * If every read consumes the entire buffer, start increasing the buffer * size, and at some point you
would then cap it. 32KB is fine for network * sockets, although using the technique described above is still a good idea. */#define kSCRFTPRequestBufferSize 32768/* The error domain that all errors generated by SCRFTPRequest use. */extern NSString *const SCRFTPRequestErrorDomain;@class
SCRFTPRequest;@protocol SCRFTPRequestDelegate /** Called on the delegate when the request completes successfully. */- (void)ftpRequestDidFinish:(SCRFTPRequest *)request;/** Called on the delegate when the request fails. */- (void)ftpRequest:(SCRFTPRequest
*)request didFailWithError:(NSError *)error;@optional/** Called on the delegate when the transfer is about to start. */- (void)ftpRequestWillStart:(SCRFTPRequest *)request;/** Called on the delegate when the status of the request instance changed. */- (void)ftpRequest:(SCRFTPRequest
*)request didChangeStatus:(SCRFTPRequestStatus)status;/** Called on the delegate when some amount of bytes were transferred. */- (void)ftpRequest:(SCRFTPRequest *)request didWriteBytes:(NSUInteger)bytesWritten;@end@interface SCRFTPRequest : NSOperation@property
(nonatomic, weak) id delegate;/** If 0 the size cannot be determined. fileSize is determined when delegate receives a notification via willStartSelector. */@property (nonatomic, readonly) UInt64 fileSize;/** The amount of bytes currently uploaded or downloaded.
Delegate can listen to the changes of this property via bytesWrittenSelector. */@property (nonatomic, readonly) SInt64 bytesWritten;/** Current instance status. Delegate can listen to the changes of this property via didChangeStatusSelector. */@property (nonatomic,
readonly) SCRFTPRequestStatus status;/** Populated when error occurs. */@property (nonatomic, strong) NSError *error;/** Specifies the operation for the request to invoke. */@property (nonatomic) SCRFTPRequestOperation operation;/** In this dictionary you
can pass any state info you need. */@property (nonatomic, strong) NSDictionary *userInfo;/** Username for authentication. */@property (nonatomic, copy) NSString *username;/** Password for authentication. */@property (nonatomic, copy) NSString *password;/**
The url for this operation. */@property (nonatomic, strong) NSURL *ftpURL;/** A custom upload file name */@property (nonatomic, copy) NSString *customUploadFileName;/** Specifies the file to upload or to write the downloaded data to. */@property (nonatomic,
copy) NSString *filePath;/** Denotes the directory to create. Specified when operation is SCRFTPRequestOperationCreateDirectory. */@property (nonatomic, copy) NSString *directoryName;@property (assign) NSTimeInterval timeOutSeconds;#pragma mark init / dealloc-
(id)initWithURL:(NSURL *)ftpURL toDownloadFile:(NSString *)filePath;- (id)initWithURL:(NSURL *)ftpURL toUploadFile:(NSString *)filePath;- (id)initWithURL:(NSURL *)ftpURL toCreateDirectory:(NSString *)directoryName;+ (id)requestWithURL:(NSURL *)ftpURL toDownloadFile:(NSString
*)filePath;+ (id)requestWithURL:(NSURL *)ftpURL toUploadFile:(NSString *)filePath;+ (id)requestWithURL:(NSURL *)ftpURL toCreateDirectory:(NSString *)directoryName;#pragma mark Request logic- (void)cancelRequest;- (void)startRequest;- (void)startAsynchronous;+
(NSOperationQueue *)sharedRequestQueue;@end#import "SCRFTPRequest.h"NSString *const SCRFTPRequestErrorDomain = @"SCRFTPRequestErrorDomain";static NSError *SCRFTPRequestTimedOutError;static NSError *SCRFTPAuthenticationError;static NSError *SCRFTPRequestCancelledError;static
NSError *SCRFTPUnableToCreateRequestError;static NSOperationQueue *sharedRequestQueue = nil;@interface SCRFTPRequest (/* Private */){BOOL _complete;/* State */UInt8 _buffer[kSCRFTPRequestBufferSize];UInt32 _bufferOffset;UInt32 _bufferLimit;}@property (nonatomic,
strong) NSOutputStream *writeStream;@property (nonatomic, strong) NSInputStream *readStream;@property (nonatomic, strong) NSDate *timeOutDate;@property (nonatomic, strong) NSRecursiveLock *cancelledLock;@end@implementation SCRFTPRequeststatic inline void performOnMainThread(void
(^block)()) { [[NSOperationQueue mainQueue] addOperations:@[[NSBlockOperation blockOperationWithBlock:block]] waitUntilFinished:![NSThread isMainThread]];}- (void)setStatus:(SCRFTPRequestStatus)status {if (_status != status) {_status = status;if ([self.delegate
respondsToSelector:@selector(ftpRequest:didChangeStatus:)]) { performOnMainThread(^{ [self.delegate ftpRequest:self didChangeStatus:_status]; });}}}#pragma mark init / dealloc+ (void)initialize {if (self == [SCRFTPRequest class]) {SCRFTPRequestTimedOutError
= [NSError errorWithDomain:SCRFTPRequestErrorDomain code:SCRFTPRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"The request timed out.", @""), NSLocalizedDescriptionKey, nil]];SCRFTPAuthenticationError = [NSError
errorWithDomain:SCRFTPRequestErrorDomaincode:SCRFTPAuthenticationErrorTypeuserInfo:[NSDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Authentication needed.", @""), NSLocalizedDescriptionKey, nil]];SCRFTPRequestCancelledError = [NSError errorWithDomain:SCRFTPRequestErrorDomain
code:SCRFTPRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"The request was cancelled.", @""),NSLocalizedDescriptionKey, nil]];SCRFTPUnableToCreateRequestError = [NSError errorWithDomain:SCRFTPRequestErrorDomain
code:SCRFTPUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Unable to create request (bad url?)", @""), NSLocalizedDescriptionKey,nil]];}[super initialize];}- (id)init {if (self = [super init]) {[self
initializeComponentWithURL:nil operation:SCRFTPRequestOperationDownload];}return self;}- (id)initWithURL:(NSURL *)ftpURL toDownloadFile:(NSString *)filePath {if (self = [super init]) {[self initializeComponentWithURL:ftpURL operation:SCRFTPRequestOperationDownload];self.filePath
= filePath;}return self;}- (id)initWithURL:(NSURL *)ftpURL toUploadFile:(NSString *)filePath {if (self = [super init]) {[self initializeComponentWithURL:ftpURL operation:SCRFTPRequestOperationUpload];self.filePath = filePath;}return self;}- (id)initWithURL:(NSURL
*)ftpURL toCreateDirectory:(NSString *)directoryName {if (self = [super init]) {[self initializeComponentWithURL:ftpURL operation:SCRFTPRequestOperationCreateDirectory];self.directoryName = directoryName;}return self;}- (void)initializeComponentWithURL:(NSURL
*)ftpURL operation:(SCRFTPRequestOperation)operation {self.ftpURL = ftpURL;self.operation = operation;self.timeOutSeconds = 10;self.cancelledLock = [[NSRecursiveLock alloc] init];}+ (id)requestWithURL:(NSURL *)ftpURL toDownloadFile:(NSString *)filePath {return
[[self alloc] initWithURL:ftpURL toDownloadFile:filePath];}+ (id)requestWithURL:(NSURL *)ftpURL toUploadFile:(NSString *)filePath {return [[self alloc] initWithURL:ftpURL toUploadFile:filePath];}+ (id)requestWithURL:(NSURL *)ftpURL toCreateDirectory:(NSString
*)directoryName {return [[self alloc] initWithURL:ftpURL toCreateDirectory:directoryName];}#pragma mark Request logic- (void)applyCredentials {if (self.username) {if (![self.writeStream setProperty:self.username forKey:(id)kCFStreamPropertyFTPUserName]) {[self
failWithError: [self constructErrorWithCode:SCRFTPInternalErrorWhileApplyingCredentialsType message:[NSString stringWithFormat: NSLocalizedString(@"Cannot apply the username \"%@\" to the FTP stream.", @""), self.username]]];return;}if (![self.writeStream
setProperty:self.password forKey:(id)kCFStreamPropertyFTPPassword]) {[self failWithError: [self constructErrorWithCode:SCRFTPInternalErrorWhileApplyingCredentialsType message:[NSString stringWithFormat: NSLocalizedString(@"Cannot apply the password \"%@\"
to the FTP stream.", @""), self.password]]];return;}}}- (void)cancel {[[self cancelledLock] lock];/* Request may already be complete. */if ([self isComplete] || [self isCancelled]) {return;}[self cancelRequest];[[self cancelledLock] unlock];/* Must tell the
operation to cancel after we unlock, as this request might be dealloced and then NSLock will log an error. */[super cancel];}- (void)main {@autoreleasepool { [[self cancelledLock] lock]; [self startRequest]; [self resetTimeout]; [[self cancelledLock] unlock];
/* Main loop */ while (![self isCancelled] && ![self isComplete]) { [[self cancelledLock] lock]; /* Do we need to timeout? */ if ([[self timeOutDate] timeIntervalSinceNow] < 0) { [self failWithError:SCRFTPRequestTimedOutError]; break; } [[self cancelledLock]
unlock]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[self timeOutDate]]; } }}- (void)resetTimeout{[self setTimeOutDate:[NSDate dateWithTimeIntervalSinceNow:[self timeOutSeconds]]];}- (void)cancelRequest {[self failWithError:SCRFTPRequestCancelledError];}-
(void)startRequest {_complete = NO;_fileSize = 0;_bytesWritten = 0;_status = SCRFTPRequestStatusNone;switch (self.operation) {case SCRFTPRequestOperationUpload:[self startUploadRequest];break;case SCRFTPRequestOperationCreateDirectory:[self startCreateDirectoryRequest];break;
case SCRFTPRequestOperationDownload: break;}}- (void)startAsynchronous{[[SCRFTPRequest sharedRequestQueue] addOperation:self];}- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {//[[self cancelledLock] lock]; NSAssert(stream == self.writeStream,
@"Stream should be equal to write stream.");[self resetTimeout];switch (self.operation) {case SCRFTPRequestOperationUpload:[self handleUploadEvent:eventCode];break;case SCRFTPRequestOperationCreateDirectory:[self handleCreateDirectoryEvent:eventCode];break;
case SCRFTPRequestOperationDownload: break;}//[[self cancelledLock] unlock];}#pragma mark Upload logic- (void)startUploadRequest {if (!self.ftpURL || !self.filePath) {[self failWithError:SCRFTPUnableToCreateRequestError];return;}CFStringRef fileName = CFBridgingRetain(self.customUploadFileName
? self.customUploadFileName : [self.filePath lastPathComponent]); if (!fileName) {[self failWithError: [self constructErrorWithCode:SCRFTPInternalErrorWhileBuildingRequestType message:[NSString stringWithFormat: NSLocalizedString(@"Unable to retrieve file
name from file located at %@", @""), self.filePath]]];return;}CFURLRef uploadUrl = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, (__bridge CFURLRef)self.ftpURL, fileName, false); CFRelease(fileName);if (!uploadUrl) {[self failWithError:[self constructErrorWithCode:SCRFTPInternalErrorWhileBuildingRequestType
message:NSLocalizedString(@"Unable to build URL to upload.", @"")]];return;}NSError *attributesError = nil;NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&attributesError];if (attributesError) {[self
failWithError:attributesError]; CFRelease(uploadUrl); //added this line to fix analyze warning saying that uploadURL wasn't being release...return;} else {_fileSize = [fileAttributes fileSize];if ([self.delegate respondsToSelector:@selector(ftpRequestWillStart:)])
{ performOnMainThread(^{ [self.delegate ftpRequestWillStart:self]; });}}self.readStream = [NSInputStream inputStreamWithFileAtPath:self.filePath];if (!self.readStream) {[self failWithError: [self constructErrorWithCode:SCRFTPUnableToCreateRequestErrorType
message:[NSString stringWithFormat: NSLocalizedString(@"Cannot start reading the file located at %@ (bad path?).", @""), self.filePath]]]; CFRelease(uploadUrl); //added this line to fix analyze warning saying that uploadURL wasn't being release...return;}[self.readStream
open];CFWriteStreamRef uploadStream = CFWriteStreamCreateWithFTPURL(NULL, uploadUrl);if (!uploadStream) {[self failWithError: [self constructErrorWithCode:SCRFTPUnableToCreateRequestErrorType message:[NSString stringWithFormat: NSLocalizedString(@"Cannot open
FTP connection to %@", @""), CFBridgingRelease(uploadUrl)]]];return;}CFRelease(uploadUrl);self.writeStream = CFBridgingRelease(uploadStream);[self applyCredentials];self.writeStream.delegate = self;[self.writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];[self.writeStream open];}- (void)handleUploadEvent:(NSStreamEvent)eventCode {switch (eventCode) { case NSStreamEventOpenCompleted: {[self setStatus:SCRFTPRequestStatusOpenNetworkConnection]; } break; case NSStreamEventHasSpaceAvailable:
{ /* If we don't have any data buffered, go read the next chunk of data. */ if (_bufferOffset == _bufferLimit) {[self setStatus:SCRFTPRequestStatusReadingFromStream]; NSInteger bytesRead = [self.readStream read:_buffer maxLength:kSCRFTPRequestBufferSize];
if (bytesRead == -1) {[self failWithError: [self constructErrorWithCode:SCRFTPConnectionFailureErrorType message:[NSString stringWithFormat: NSLocalizedString(@"Cannot continue reading the file at %@", @""), self.filePath]]];return;} else if (bytesRead ==
0) {[self requestFinished];return; } else { _bufferOffset = 0; _bufferLimit = (UInt32)bytesRead; } } /* If we're not out of data completely, send the next chunk. */ if (_bufferOffset != _bufferLimit) { _bytesWritten = [self.writeStream write:&_buffer[_bufferOffset]
maxLength:_bufferLimit - _bufferOffset]; assert(_bytesWritten != 0); if (_bytesWritten == -1) {[self failWithError: [self constructErrorWithCode:SCRFTPConnectionFailureErrorType message:NSLocalizedString(@"Cannot continue writing file to the specified URL
at the FTP server.", @"")]];return; } else {[self setStatus:SCRFTPRequestStatusWritingToStream];if ([self.delegate respondsToSelector:@selector(ftpRequest:didWriteBytes:)]) { performOnMainThread(^{ [self.delegate ftpRequest:self didWriteBytes:_bytesWritten];
});} _bufferOffset += _bytesWritten; } } } break; case NSStreamEventErrorOccurred: {[self failWithError:[self constructErrorWithCode:SCRFTPConnectionFailureErrorType message:NSLocalizedString(@"Cannot open FTP connection.", @"")]]; } break; case NSStreamEventEndEncountered:
{/* Ignore */ } break; default: { NSAssert(NO, @"Default should never happen."); } break; }}- (void)startCreateDirectoryRequest {if (!self.ftpURL || !self.directoryName) {[self failWithError:SCRFTPUnableToCreateRequestError];return;}CFURLRef createUrl = CFURLCreateCopyAppendingPathComponent(NULL,
(CFURLRef)self.ftpURL, (CFStringRef)self.directoryName, true);if (!createUrl) {[self failWithError:[self constructErrorWithCode:SCRFTPInternalErrorWhileBuildingRequestType message:NSLocalizedString(@"Unable to build URL to create directory.", @"")]];return;}CFWriteStreamRef
createStream = CFWriteStreamCreateWithFTPURL(NULL, createUrl);if (!createStream) {[self failWithError: [self constructErrorWithCode:SCRFTPUnableToCreateRequestErrorType message:[NSString stringWithFormat: NSLocalizedString(@"Cannot open FTP connection to %@",
@""), (__bridge_transfer NSURL *)createUrl]]];// CFRelease(createUrl);return;}CFRelease(createUrl);self.writeStream = (__bridge_transfer NSOutputStream *)createStream;[self applyCredentials];self.writeStream.delegate = self;[self.writeStream scheduleInRunLoop:[NSRunLoop
currentRunLoop] forMode:NSDefaultRunLoopMode];[self.writeStream open];// CFRelease(createStream);}- (void)handleCreateDirectoryEvent:(NSStreamEvent)eventCode {switch (eventCode) { case NSStreamEventOpenCompleted: {[self setStatus:SCRFTPRequestStatusOpenNetworkConnection];
/* Despite what it says in the documentation , * you should wait for the NSStreamEventEndEncountered event to see * if the directory was created successfully. If you shut the stream * down now, you miss any errors coming back from the server in response *
to the MKD command. */ } break; case NSStreamEventHasBytesAvailable: { NSAssert(NO, @"Should never happen for the output stream."); /* Should never happen for the output stream. */ } break; case NSStreamEventHasSpaceAvailable: { NSAssert(NO, @"Should never
happen for the output stream."); } break; case NSStreamEventErrorOccurred: { /* -streamError does not return a useful error domain value, so we * get the old school CFStreamError and check it. */CFStreamError err = CFWriteStreamGetError((CFWriteStreamRef)self.writeStream);
if (err.domain == kCFStreamErrorDomainFTP) { [self failWithError: [self constructErrorWithCode:SCRFTPConnectionFailureErrorType message:[NSString stringWithFormat:NSLocalizedString(@"FTP error %d", @""), (int)err.error]]]; } else {[self failWithError: [self
constructErrorWithCode:SCRFTPConnectionFailureErrorType message:NSLocalizedString(@"Cannot open FTP connection.", @"")]]; } } break; case NSStreamEventEndEncountered: {[self requestFinished]; } break; default: { NSAssert(NO, @"Default should never happen for
the output stream."); } break; }}#pragma mark Complete / Failure- (NSError *)constructErrorWithCode:(NSInteger)code message:(NSString *)message {return [NSError errorWithDomain:SCRFTPRequestErrorDomain code:code userInfo:[NSDictionary dictionaryWithObjectsAndKeys:message,
NSLocalizedDescriptionKey, nil]];}- (BOOL)isComplete {return _complete;}- (BOOL)isFinished {return [self isComplete];}- (void)requestFinished {_complete = YES;[self cleanUp];[self setStatus:SCRFTPRequestStatusClosedNetworkConnection]; performOnMainThread(^{
[self.delegate ftpRequestDidFinish:self]; });}- (void)failWithError:(NSError *)error {_complete = YES;if (self.error != nil || [self isCancelled]) {return;}self.error = error;[self cleanUp];[self setStatus:SCRFTPRequestStatusError]; performOnMainThread(^{
[self.delegate ftpRequest:self didFailWithError:self.error]; });}- (void)cleanUp {if (self.writeStream != nil) { [self.writeStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; self.writeStream.delegate = nil; [self.writeStream
close]; self.writeStream = nil; } if (self.readStream != nil) { [self.readStream close]; self.readStream = nil; }}+ (NSOperationQueue *)sharedRequestQueue{if (!sharedRequestQueue) {sharedRequestQueue = [[NSOperationQueue alloc] init];[sharedRequestQueue setMaxConcurrentOperationCount:4];}return
sharedRequestQueue;}@end
iOS的FTP文件上传
最新推荐文章于 2021-12-01 19:03:57 发布
1682

被折叠的 条评论
为什么被折叠?



