Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions Classes/ASIInputStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<NSStreamDelegate>

+ (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
244 changes: 195 additions & 49 deletions Classes/ASIInputStream.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,67 @@

#import "ASIInputStream.h"
#import "ASIHTTPRequest.h"
#import <objc/runtime.h>

// Used to ensure only one request can read data at once
static NSLock *readLock = nil;

@implementation ASIInputStream
{
NSInputStream *stream;
id<NSStreamDelegate> 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];
Expand All @@ -80,14 +79,19 @@ - (void)close
[stream close];
}

- (id)delegate
- (id <NSStreamDelegate> )delegate
{
return [stream delegate];
return delegate;
}

- (void)setDelegate:(id)delegate
- (void)setDelegate:(id<NSStreamDelegate>)aDelegate
{
[stream setDelegate:delegate];
if (aDelegate == nil) {
delegate = self;
}
else {
delegate = aDelegate;
}
}

- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
Expand Down Expand Up @@ -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