Server/FileSendOperation.m
/* |
File: FileSendOperation.m |
Abstract: An NSOperation that sends a file over a TCP connection. |
Version: 2.2 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2013 Apple Inc. All Rights Reserved. |
*/ |
#import "FileSendOperation.h" |
#include <zlib.h> |
enum { |
kFileSendOperationStateStart, |
kFileSendOperationStateHeader, |
kFileSendOperationStateBody, |
kFileSendOperationStateTrailer |
}; |
enum { |
kFileSendOperationBufferSize = 32768 |
}; |
@interface FileSendOperation () <NSStreamDelegate> |
// internal properties |
@property (atomic, assign, readwrite) NSInteger sendState; |
@property (atomic, strong, readwrite) NSInputStream * fileStream; |
@property (atomic, strong, readwrite) NSMutableData * buffer; |
@property (atomic, assign, readwrite) NSUInteger bufferOffset; |
@property (atomic, assign, readwrite) off_t fileLength; |
@property (atomic, assign, readwrite) off_t fileOffset; |
@property (atomic, assign, readwrite) uLong crc; |
@end |
@implementation FileSendOperation |
- (id)initWithFilePath:(NSString *)filePath outputStream:(NSOutputStream *)outputStream |
{ |
assert(filePath != nil); |
assert(outputStream != nil); |
self = [super init]; |
if (self != nil) { |
self->_filePath = [filePath copy]; |
self->_outputStream = outputStream; |
} |
return self; |
} |
- (void)dealloc |
{ |
assert(self->_buffer == nil); |
assert(self->_fileStream == nil); |
} |
#pragma mark * Start and stop |
- (void)operationDidStart |
// Our superclass calls this on the actual run loop thread to give us an opportunity |
// to install our run loop sources (and do various other bits of initialisation). |
{ |
NSDictionary * fileAttributes; |
NSError * error; |
assert(self.isActualRunLoopThread); |
assert(self.state == kQRunLoopOperationStateExecuting); |
assert(self.fileStream == nil); |
// First get the file attributes, to allows us to send the header which contains the file size. |
// This has the added benefit of flush out errors early. |
fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&error]; |
if (fileAttributes == nil) { |
[self finishWithError:error]; |
} else { |
// Now open the file stream. |
self.fileStream = [NSInputStream inputStreamWithFileAtPath:self.filePath]; |
if (self.fileStream == nil) { |
[self finishWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSFileNoSuchFileError userInfo:nil]]; |
} else { |
// Determine the file length. |
assert( [fileAttributes[NSFileSize] isKindOfClass:[NSNumber class]] ); |
self.fileLength = [fileAttributes[NSFileSize] longLongValue]; |
assert(self.fileLength >= 0); |
// Allocate a transfer buffer. |
self.buffer = [NSMutableData dataWithCapacity:kFileSendOperationBufferSize]; |
assert(self.buffer != nil); |
assert(self.sendState == kFileSendOperationStateStart); |
assert(self.bufferOffset == 0); |
assert(self.fileOffset == 0); |
assert(self.crc == 0); |
// Open up our streams. |
[self.fileStream open]; |
[self.outputStream setDelegate:self]; |
for (NSString * mode in self.actualRunLoopModes) { |
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:mode]; |
} |
[self.outputStream open]; |
} |
} |
} |
- (void)operationWillFinish |
// Our superclass calls this on the actual run loop thread to give us an opportunity |
// to remove our run loop sources (and do various other bits of clean up). |
{ |
assert(self.isActualRunLoopThread); |
if (self.fileStream != nil) { |
[self.fileStream close]; |
self.fileStream = nil; |
} |
if (self.outputStream != nil) { |
// We want to hold on to our reference to outputStream until -dealloc, but |
// we don't want to do this teardown twice, so we conditionalise it based on |
// whether the delegate is still set. |
if ([self.outputStream delegate] != nil) { |
[self.outputStream setDelegate:nil]; |
for (NSString * mode in self.actualRunLoopModes) { |
[self.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:mode]; |
} |
[self.outputStream close]; |
} |
} |
self.buffer = nil; // might as well free up the memory now |
} |
#pragma mark * Stream delegate callbacks |
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode |
// An NSStream delegate callback that's called when events happen on our TCP stream. |
{ |
// CFSocketStream does not retain its delegate. It's possible that actions early |
// in this method can affect the state of the program (typically by setting isFinished |
// to YES so that all our clients release their references to the operation) such that |
// all references to this object are released, resulting in code later in the method |
// accessing a self that's been deallocated <rdar://problem/12682482>. To avoid this, |
// we manually retain self across the lifetime of this callback. |
CFRetain( (__bridge CFTypeRef) self ); |
assert([NSThread isMainThread]); |
assert(aStream == self.outputStream); |
#pragma unused(aStream) |
switch (eventCode) { |
case NSStreamEventOpenCompleted: { |
// do nothing |
} break; |
case NSStreamEventHasBytesAvailable: { |
assert(NO); |
} break; |
case NSStreamEventHasSpaceAvailable: { |
NSInteger bytesWritten; |
#if ! defined(NDEBUG) |
if (self.debugStallSend) { |
return; |
} |
#endif |
// If the buffer has no more data to send, refill it. |
if (self.bufferOffset == [self.buffer length]) { |
self.bufferOffset = 0; |
switch (self.sendState) { |
case kFileSendOperationStateStart: { |
uint64_t header; |
// Set up the buffer to send the header. |
header = OSSwapHostToBigInt64(self.fileLength); |
[self.buffer setLength:0]; |
[self.buffer appendBytes:&header length:sizeof(header)]; |
self.sendState = kFileSendOperationStateHeader; |
} break; |
case kFileSendOperationStateHeader: |
self.sendState = kFileSendOperationStateBody; |
// fall through |
case kFileSendOperationStateBody: { |
if (self.fileOffset < self.fileLength) { |
NSUInteger bytesToRead; |
NSInteger bytesRead; |
// Set up the buffer to send the next chunk of body data. |
if ( (self.fileLength - self.fileOffset) < (off_t) kFileSendOperationBufferSize ) { |
bytesToRead = (NSUInteger) (self.fileLength - self.fileOffset); |
} else { |
bytesToRead = kFileSendOperationBufferSize; |
} |
[self.buffer setLength:bytesToRead]; |
bytesRead = [self.fileStream read:[self.buffer mutableBytes] maxLength:bytesToRead]; |
if (bytesRead < 0) { |
[self finishWithError:[self.fileStream streamError]]; |
} else if (bytesRead == 0) { |
// The file must have shrunk while we were reading it! |
[self finishWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:EPIPE userInfo:nil]]; |
} else { |
[self.buffer setLength:(NSUInteger) bytesRead]; |
self.crc = crc32(self.crc, [self.buffer bytes], (uInt) [self.buffer length]); |
self.fileOffset += bytesRead; |
} |
} else { |
uint32_t trailer; |
// Set up the buffer to send the trailer. |
trailer = OSSwapHostToBigInt32( (uint32_t) self.crc ); |
#if ! defined(NDEBUG) |
if (self.debugSendBadChecksum) { |
trailer ^= 1; |
} |
#endif |
[self.buffer setLength:0]; |
[self.buffer appendBytes:&trailer length:sizeof(trailer)]; |
self.sendState = kFileSendOperationStateTrailer; |
} |
} break; |
case kFileSendOperationStateTrailer: { |
[self finishWithError:nil]; |
} break; |
} |
} |
// Try to send the remaining bytes in the buffer. |
if ( ! [self isFinished] ) { |
assert(self.bufferOffset < [self.buffer length]); |
bytesWritten = [self.outputStream write:((const uint8_t *) [self.buffer bytes]) + self.bufferOffset maxLength:[self.buffer length] - self.bufferOffset]; |
if (bytesWritten < 0) { |
[self finishWithError:[self.outputStream streamError]]; |
} else { |
self.bufferOffset += bytesWritten; |
} |
} |
} break; |
case NSStreamEventErrorOccurred: { |
assert([self.outputStream streamError] != nil); |
[self finishWithError:[self.outputStream streamError]]; |
} break; |
case NSStreamEventEndEncountered: { |
assert(NO); |
} break; |
default: { |
assert(NO); |
} break; |
} |
CFRelease( (__bridge CFTypeRef) self ); |
} |
@end |
Copyright © 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-08-15