diff --git a/Classes/ASIInputStream.h b/Classes/ASIInputStream.h index 7b9f93ed..81832006 100644 --- a/Classes/ASIInputStream.h +++ b/Classes/ASIInputStream.h @@ -14,13 +14,12 @@ // Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead. // It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading -@interface ASIInputStream : NSObject { - NSInputStream *stream; - ASIHTTPRequest *request; -} +@interface ASIInputStream : NSObject + + (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request; + (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request; +- (id)initWithInputStream:(NSInputStream *)stream; -@property (retain, nonatomic) NSInputStream *stream; @property (assign, nonatomic) ASIHTTPRequest *request; + @end diff --git a/Classes/ASIInputStream.m b/Classes/ASIInputStream.m index d2b84288..ccbcbcb4 100644 --- a/Classes/ASIInputStream.m +++ b/Classes/ASIInputStream.m @@ -8,68 +8,67 @@ #import "ASIInputStream.h" #import "ASIHTTPRequest.h" +#import // Used to ensure only one request can read data at once static NSLock *readLock = nil; @implementation ASIInputStream +{ + NSInputStream *stream; + id delegate; + + CFReadStreamClientCallBack copiedCallback; + CFStreamClientContext copiedContext; + CFOptionFlags requestedEvents; + ASIHTTPRequest *request; +} + (void)initialize { - if (self == [ASIInputStream class]) { - readLock = [[NSLock alloc] init]; - } + if (self == [ASIInputStream class]) { + readLock = [[NSLock alloc] init]; + } } + (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)theRequest { - ASIInputStream *theStream = [[[self alloc] init] autorelease]; - [theStream setRequest:theRequest]; - [theStream setStream:[NSInputStream inputStreamWithFileAtPath:path]]; - return theStream; + ASIInputStream *theStream = [[[ASIInputStream alloc] initWithInputStream:[NSInputStream inputStreamWithFileAtPath:path]] autorelease]; + [theStream setRequest:theRequest]; + return theStream; } + (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)theRequest { - ASIInputStream *theStream = [[[self alloc] init] autorelease]; - [theStream setRequest:theRequest]; - [theStream setStream:[NSInputStream inputStreamWithData:data]]; - return theStream; + ASIInputStream *theStream = [[[ASIInputStream alloc] initWithInputStream:[NSInputStream inputStreamWithData:data]] autorelease]; + [theStream setRequest:theRequest]; + return theStream; } -- (void)dealloc +#pragma mark - Object lifecycle + +- (id)initWithInputStream:(NSInputStream *)aStream { - [stream release]; - [super dealloc]; + self = [super init]; + if (self) { + // Initialization code here. + stream = [aStream retain]; + [stream setDelegate:self]; + + [self setDelegate:self]; + } + + return self; } -// Called when CFNetwork wants to read more of our request body -// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read -- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len +- (void)dealloc { - [readLock lock]; - unsigned long toRead = len; - if ([ASIHTTPRequest isBandwidthThrottled]) { - toRead = [ASIHTTPRequest maxUploadReadLength]; - if (toRead > len) { - toRead = len; - } else if (toRead == 0) { - toRead = 1; - } - [request performThrottling]; - } - [readLock unlock]; - NSInteger rv = [stream read:buffer maxLength:toRead]; - if (rv > 0) - [ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv]; - return rv; -} - -/* - * Implement NSInputStream mandatory methods to make sure they are implemented - * (necessary for MacRuby for example) and avoid the overhead of method - * forwarding for these common methods. - */ + [stream release]; + [super dealloc]; +} + +#pragma mark - NSStream subclass methods + - (void)open { [stream open]; @@ -80,14 +79,19 @@ - (void)close [stream close]; } -- (id)delegate +- (id )delegate { - return [stream delegate]; + return delegate; } -- (void)setDelegate:(id)delegate +- (void)setDelegate:(id)aDelegate { - [stream setDelegate:delegate]; + if (aDelegate == nil) { + delegate = self; + } + else { + delegate = aDelegate; + } } - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode @@ -120,19 +124,161 @@ - (NSError *)streamError return [stream streamError]; } +#pragma mark - NSInputStream subclass methods + +// Called when CFNetwork wants to read more of our request body +// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len +{ + [readLock lock]; + unsigned long toRead = len; + if ([ASIHTTPRequest isBandwidthThrottled]) { + toRead = [ASIHTTPRequest maxUploadReadLength]; + if (toRead > len) { + toRead = len; + } else if (toRead == 0) { + toRead = 1; + } + [request performThrottling]; + } + [readLock unlock]; + NSInteger rv = [stream read:buffer maxLength:toRead]; + if (rv > 0) + [ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv]; + return rv; +} + + +- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len +{ + // We cannot implement our character-counting in O(1) time, + // so we return NO as indicated in the NSInputStream + // documentation. + return NO; +} + +- (BOOL)hasBytesAvailable +{ + return [stream hasBytesAvailable]; +} + +#pragma mark - Undocumented CFReadStream bridged methods + ++ (BOOL)resolveInstanceMethod:(SEL) selector +{ + NSString *name = NSStringFromSelector(selector); + + if ([name hasPrefix:@"_"]){ + name = [name substringFromIndex:1]; + SEL aSelector = NSSelectorFromString(name); + Method method = class_getInstanceMethod(self, aSelector); + + if (method) + { + class_addMethod(self, + selector, + method_getImplementation(method), + method_getTypeEncoding(method)); + return YES; + } + } + return [super resolveInstanceMethod:selector]; +} + +- (void)scheduleInCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode +{ + CFReadStreamScheduleWithRunLoop((CFReadStreamRef)stream, aRunLoop, aMode); +} + +- (BOOL)setCFClientFlags:(CFOptionFlags)inFlags callback:(CFReadStreamClientCallBack)inCallback context:(CFStreamClientContext *)inContext +{ + if (inCallback != NULL) { + requestedEvents = inFlags; + copiedCallback = inCallback; + memcpy(&copiedContext, inContext, sizeof(CFStreamClientContext)); + + if (copiedContext.info && copiedContext.retain) { + copiedContext.retain(copiedContext.info); + } + } + else { + requestedEvents = kCFStreamEventNone; + copiedCallback = NULL; + if (copiedContext.info && copiedContext.release) { + copiedContext.release(copiedContext.info); + } + + memset(&copiedContext, 0, sizeof(CFStreamClientContext)); + } + + return YES; +} + +- (void)unscheduleFromCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode +{ + CFReadStreamUnscheduleFromRunLoop((CFReadStreamRef)stream, aRunLoop, aMode); +} + +#pragma mark - NSStreamDelegate methods + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode +{ + assert(aStream == stream); + + switch (eventCode) { + case NSStreamEventOpenCompleted: + if (requestedEvents & kCFStreamEventOpenCompleted) { + copiedCallback((CFReadStreamRef)self, + kCFStreamEventOpenCompleted, + copiedContext.info); + } + break; + + case NSStreamEventHasBytesAvailable: + if (requestedEvents & kCFStreamEventHasBytesAvailable) { + copiedCallback((CFReadStreamRef)self, + kCFStreamEventHasBytesAvailable, + copiedContext.info); + } + break; + + case NSStreamEventErrorOccurred: + if (requestedEvents & kCFStreamEventErrorOccurred) { + copiedCallback((CFReadStreamRef)self, + kCFStreamEventErrorOccurred, + copiedContext.info); + } + break; + + case NSStreamEventEndEncountered: + if (requestedEvents & kCFStreamEventEndEncountered) { + copiedCallback((CFReadStreamRef)self, + kCFStreamEventEndEncountered, + copiedContext.info); + } + break; + + case NSStreamEventHasSpaceAvailable: + // This doesn't make sense for a read stream + break; + + default: + break; + } +} + // If we get asked to perform a method we don't have (probably internal ones), // we'll just forward the message to our stream - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { - return [stream methodSignatureForSelector:aSelector]; + return [stream methodSignatureForSelector:aSelector]; } - + - (void)forwardInvocation:(NSInvocation *)anInvocation { - [anInvocation invokeWithTarget:stream]; + [anInvocation invokeWithTarget:stream]; } -@synthesize stream; @synthesize request; -@end +@end \ No newline at end of file