diff options
94 files changed, 6279 insertions, 1457 deletions
diff --git a/AppKit/GTMDelegatingTableColumn.h b/AppKit/GTMDelegatingTableColumn.h index a90b298..63abf20 100644 --- a/AppKit/GTMDelegatingTableColumn.h +++ b/AppKit/GTMDelegatingTableColumn.h @@ -17,16 +17,19 @@ // #import <Cocoa/Cocoa.h> +#import "GTMDefines.h" -@interface GTMDelegatingTableColumn : NSTableColumn { - @private - IBOutlet id delegate_; -} -- (void)setDelegate:(id)delegate; -- (id)delegate; -- (id)dataCellForRow:(int)row; +// NOTE: If you're using the 10.5 SDK, just use the new delegate method: +// tableView:dataCellForTableColumn:row: + +@interface GTMDelegatingTableColumn : NSTableColumn +// no instance state or new method, it will just invoke the tableview's delegate +// w/ the method below. @end +// the method delegated to @interface NSObject (GTMDelegatingTableColumnDelegate) -- (id)tableColumn:(NSTableColumn*)column dataCellForRow:(int)row; +- (id)gtm_tableView:(NSTableView *)tableView + dataCellForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row; @end diff --git a/AppKit/GTMDelegatingTableColumn.m b/AppKit/GTMDelegatingTableColumn.m index d84e2e7..0bd2371 100644 --- a/AppKit/GTMDelegatingTableColumn.m +++ b/AppKit/GTMDelegatingTableColumn.m @@ -19,19 +19,22 @@ #import "GTMDelegatingTableColumn.h" @implementation GTMDelegatingTableColumn -- (void)setDelegate:(id)delegate { - delegate_ = delegate; -} - -- (id)delegate { - return delegate_; -} - -- (id)dataCellForRow:(int)row { +- (id)dataCellForRow:(NSInteger)row { id dataCell = nil; - if (delegate_ && [delegate_ respondsToSelector:@selector(tableColumn:dataCellForRow:)]) { - dataCell = [delegate_ tableColumn:self dataCellForRow:row]; - } else { + id delegate = [[self tableView] delegate]; + BOOL sendSuper = YES; + if (delegate) { + if ([delegate respondsToSelector:@selector(gtm_tableView:dataCellForTableColumn:row:)]) { + + dataCell = [delegate gtm_tableView:[self tableView] + dataCellForTableColumn:self + row:row]; + sendSuper = NO; + } else { + _GTMDevLog(@"tableView delegate didn't implement gtm_tableView:dataCellForTableColumn:row:"); + } + } + if (sendSuper) { dataCell = [super dataCellForRow:row]; } return dataCell; diff --git a/AppKit/GTMLinearRGBShading.h b/AppKit/GTMLinearRGBShading.h index f6b6405..d85a4eb 100644 --- a/AppKit/GTMLinearRGBShading.h +++ b/AppKit/GTMLinearRGBShading.h @@ -34,7 +34,7 @@ CGFunctionRef function_; // function used to calculated shading (STRONG) CGColorSpaceRef colorSpace_; // colorspace used for shading (STRONG) BOOL isCalibrated_; // are we using calibrated or device RGB. - float colorValue[4]; // the RGBA color values + CGFloat colorValue_[4]; // the RGBA color values } /// Generate a shading with color |begin| at position 0.0 and color |end| at 1.0. @@ -64,8 +64,8 @@ // a GTMLinearRGBShading + (id)shadingWithColors:(NSColor **)colors fromSpaceNamed:(NSString*)colorSpaceName - atPositions:(float *)positions - count:(unsigned int)numberOfColors; + atPositions:(CGFloat *)positions + count:(NSUInteger)numberOfColors; /// Designated initializer // Args: diff --git a/AppKit/GTMLinearRGBShading.m b/AppKit/GTMLinearRGBShading.m index 31f22ca..18af0e0 100644 --- a/AppKit/GTMLinearRGBShading.m +++ b/AppKit/GTMLinearRGBShading.m @@ -19,24 +19,24 @@ #import "GTMLinearRGBShading.h" // Carbon callback function required for CoreGraphics -static void cShadeFunction(void *info, const float *inPos, float *outVals); +static void cShadeFunction(void *info, const CGFloat *inPos, CGFloat *outVals); @implementation GTMLinearRGBShading + (id)shadingFromColor:(NSColor *)begin toColor:(NSColor *)end fromSpaceNamed:(NSString*)colorSpaceName { NSColor *theColors[] = { begin, end }; - float thePositions[] = { 0.0f, 1.0f }; + CGFloat thePositions[] = { 0.0, 1.0 }; return [[self class] shadingWithColors:theColors fromSpaceNamed:colorSpaceName atPositions:thePositions - count:(sizeof(thePositions)/sizeof(float))]; + count:(sizeof(thePositions)/sizeof(CGFloat))]; } + (id)shadingWithColors:(NSColor **)colors fromSpaceNamed:(NSString*)colorSpaceName - atPositions:(float *)positions count:(unsigned)count { + atPositions:(CGFloat *)positions count:(NSUInteger)count { GTMLinearRGBShading *theShading = [[[[self class] alloc] initWithColorSpaceName:colorSpaceName] autorelease]; - for (unsigned int i = 0; i < count; ++i) { + for (NSUInteger i = 0; i < count; ++i) { [theShading insertStop:colors[i] atPosition:positions[i]]; } return theShading; @@ -50,14 +50,24 @@ static void cShadeFunction(void *info, const float *inPos, float *outVals); isCalibrated_ = YES; } else { - [self dealloc]; + [self release]; self = nil; } } return self; } -- (void) dealloc { +- (void)finalize { + if (nil != function_) { + CGFunctionRelease(function_); + } + if (nil != colorSpace_) { + CGColorSpaceRelease(colorSpace_); + } + [super finalize]; +} + +- (void)dealloc { if (nil != function_) { CGFunctionRelease(function_); } @@ -68,7 +78,7 @@ static void cShadeFunction(void *info, const float *inPos, float *outVals); } -- (void)insertStop:(id)item atPosition:(float)position { +- (void)insertStop:(id)item atPosition:(CGFloat)position { NSString *colorSpaceName = isCalibrated_ ? NSCalibratedRGBColorSpace : NSDeviceRGBColorSpace; NSColor *tempColor = [item colorUsingColorSpaceName: colorSpaceName]; if (nil != tempColor) { @@ -77,13 +87,13 @@ static void cShadeFunction(void *info, const float *inPos, float *outVals); } // Calculate a linear value based on our stops -- (id)valueAtPosition:(float)position { - unsigned int index = 0; - unsigned int colorCount = [self stopCount]; - float stop1Position = 0.0f; +- (id)valueAtPosition:(CGFloat)position { + NSUInteger index = 0; + NSUInteger colorCount = [self stopCount]; + CGFloat stop1Position = 0.0; NSColor *stop1Color = [self stopAtIndex:index position:&stop1Position]; index += 1; - float stop2Position = 0.0f; + CGFloat stop2Position = 0.0; NSColor *stop2Color = nil; NSColor *theColor = nil; if (colorCount > 1) { @@ -105,30 +115,30 @@ static void cShadeFunction(void *info, const float *inPos, float *outVals); if (position <= stop1Position) { // if we are less than our lowest position, return our first color theColor = stop1Color; - [stop1Color getRed:&colorValue[0] green:&colorValue[1] - blue:&colorValue[2] alpha:&colorValue[3]]; + [stop1Color getRed:&colorValue_[0] green:&colorValue_[1] + blue:&colorValue_[2] alpha:&colorValue_[3]]; } else if (position >= stop2Position) { // likewise if we are greater than our highest position, return the last color - [stop2Color getRed:&colorValue[0] green:&colorValue[1] - blue:&colorValue[2] alpha:&colorValue[3]]; + [stop2Color getRed:&colorValue_[0] green:&colorValue_[1] + blue:&colorValue_[2] alpha:&colorValue_[3]]; } else { // otherwise interpolate between the two position = (position - stop1Position) / (stop2Position - stop1Position); - float red1, red2, green1, green2, blue1, blue2, alpha1, alpha2; + CGFloat red1, red2, green1, green2, blue1, blue2, alpha1, alpha2; [stop1Color getRed:&red1 green:&green1 blue:&blue1 alpha:&alpha1]; [stop2Color getRed:&red2 green:&green2 blue:&blue2 alpha:&alpha2]; - colorValue[0] = (red2 - red1) * position + red1; - colorValue[1] = (green2 - green1) * position + green1; - colorValue[2] = (blue2 - blue1) * position + blue1; - colorValue[3] = (alpha2 - alpha1) * position + alpha1; + colorValue_[0] = (red2 - red1) * position + red1; + colorValue_[1] = (green2 - green1) * position + green1; + colorValue_[2] = (blue2 - blue1) * position + blue1; + colorValue_[3] = (alpha2 - alpha1) * position + alpha1; } - // Yes, I am casting a float[] to an id to pass it by the compiler. This + // Yes, I am casting a CGFloat[] to an id to pass it by the compiler. This // significantly improves performance though as I avoid creating an NSColor // for every scanline which later has to be cleaned up in an autorelease pool // somewhere. Causes guardmalloc to run significantly faster. - return (id)colorValue; + return (id)colorValue_; } // @@ -146,12 +156,12 @@ static void cShadeFunction(void *info, const float *inPos, float *outVals); // a single float value // outVals: where we store our return values. Since we are calculating // an RGBA color, this is a pointer to an array of four float values -// ranging from 0.0f to 1.0f +// ranging from 0.0 to 1.0 // // -static void cShadeFunction(void *info, const float *inPos, float *outVals) { +static void cShadeFunction(void *info, const CGFloat *inPos, CGFloat *outVals) { id object = (id)info; - float *colorValue = (float*)[object valueAtPosition:*inPos]; + CGFloat *colorValue = (CGFloat*)[object valueAtPosition:*inPos]; outVals[0] = colorValue[0]; outVals[1] = colorValue[1]; outVals[2] = colorValue[2]; @@ -165,15 +175,15 @@ static void cShadeFunction(void *info, const float *inPos, float *outVals) { // diposed if necessary in the dealloc call. const CGFunctionCallbacks shadeFunctionCallbacks = { 0, &cShadeFunction, NULL }; - // TODO: this code assumes that we have a range from 0.0f to 1.0f + // TODO: this code assumes that we have a range from 0.0 to 1.0 // which may not be true according to the stops that the user has given us. // In general you have stops at 0.0 and 1.0, so this will do for right now // but may be an issue in the future. - const float inRange[2] = { 0.0f, 1.0f }; - const float outRange[8] = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; - function_ = CGFunctionCreate(self, - sizeof(inRange) / (sizeof(float) * 2), inRange, - sizeof(outRange) / (sizeof(float) * 2), outRange, + const CGFloat inRange[2] = { 0.0, 1.0 }; + const CGFloat outRange[8] = { 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 }; + function_ = CGFunctionCreate(self, + sizeof(inRange) / (sizeof(CGFloat) * 2), inRange, + sizeof(outRange) / (sizeof(CGFloat) * 2), outRange, &shadeFunctionCallbacks); } return function_; diff --git a/AppKit/GTMLinearRGBShadingTest.m b/AppKit/GTMLinearRGBShadingTest.m index e90d84c..119fa79 100644 --- a/AppKit/GTMLinearRGBShadingTest.m +++ b/AppKit/GTMLinearRGBShadingTest.m @@ -34,8 +34,8 @@ toColor:blue fromSpaceNamed:NSCalibratedRGBColorSpace]; STAssertNotNil(theShading,nil); - STAssertEquals([theShading stopCount], 2U, nil); - float *theColor = (float*)[theShading valueAtPosition: 0.5]; + STAssertEquals([theShading stopCount], (NSUInteger)2, nil); + CGFloat *theColor = (CGFloat*)[theShading valueAtPosition: 0.5]; STAssertTrue(theColor[0] == [purple redComponent] && theColor[1] == [purple greenComponent] && theColor[2] == [purple blueComponent] && @@ -44,11 +44,11 @@ - (void)testShadingWith { // Create a shading with kColorCount colors and make sure all the values are there. - const unsigned int kColorCount = 100; + const NSUInteger kColorCount = 100; NSColor *theColors[kColorCount]; - float thePositions[kColorCount]; - const float kColorIncrement = 1.0f / kColorCount; - for (unsigned int i = 0; i < kColorCount; i++) { + CGFloat thePositions[kColorCount]; + const CGFloat kColorIncrement = 1.0 / kColorCount; + for (NSUInteger i = 0; i < kColorCount; i++) { thePositions[i] = kColorIncrement * i; theColors[i] = [NSColor colorWithCalibratedRed:kColorIncrement * i green:kColorIncrement * i @@ -60,8 +60,8 @@ fromSpaceNamed:NSCalibratedRGBColorSpace atPositions:thePositions count:kColorCount]; - for (unsigned int i = 0; i < kColorCount; i++) { - float *theColor = (float*)[theShading valueAtPosition: kColorIncrement * i]; + for (NSUInteger i = 0; i < kColorCount; i++) { + CGFloat *theColor = (CGFloat*)[theShading valueAtPosition: kColorIncrement * i]; STAssertTrue(theColor[0] == kColorIncrement * i && theColor[1] == kColorIncrement * i && theColor[2] == kColorIncrement * i && diff --git a/AppKit/GTMLoginItems.m b/AppKit/GTMLoginItems.m index d3197c0..cc34c11 100644 --- a/AppKit/GTMLoginItems.m +++ b/AppKit/GTMLoginItems.m @@ -18,6 +18,7 @@ // #import "GTMLoginItems.h" +#import "GTMDefines.h" #include <Carbon/Carbon.h> @@ -27,22 +28,22 @@ NSString * const kGTMLoginItemsPathKey = @"Path"; NSString * const kGTMLoginItemsHiddenKey = @"Hide"; @interface GTMLoginItems (PrivateMethods) -+ (int)indexOfLoginItemWithValue:(id)value - forKey:(NSString *)key - loginItems:(NSArray *)items; ++ (NSInteger)indexOfLoginItemWithValue:(id)value + forKey:(NSString *)key + loginItems:(NSArray *)items; + (BOOL)compileAndRunScript:(NSString *)script withError:(NSError **)errorInfo; @end @implementation GTMLoginItems (PrivateMethods) -+ (int)indexOfLoginItemWithValue:(id)value - forKey:(NSString *)key - loginItems:(NSArray *)items { ++ (NSInteger)indexOfLoginItemWithValue:(id)value + forKey:(NSString *)key + loginItems:(NSArray *)items { if (!value || !key || !items) return NSNotFound; NSDictionary *item = nil; NSEnumerator *itemsEnum = [items objectEnumerator]; - int found = -1; + NSInteger found = -1; while ((item = [itemsEnum nextObject])) { ++found; id itemValue = [item objectForKey:key]; @@ -105,8 +106,8 @@ NSString * const kGTMLoginItemsHiddenKey = @"Hide"; } // build our results NSMutableArray *result = [NSMutableArray array]; - int count = [scriptResult numberOfItems]; - for (int i = 0; i < count; ++i) { + NSInteger count = [scriptResult numberOfItems]; + for (NSInteger i = 0; i < count; ++i) { NSAppleEventDescriptor *aeItem = [scriptResult descriptorAtIndex:i+1]; NSAppleEventDescriptor *hidn = [aeItem descriptorForKeyword:kAEHidden]; NSAppleEventDescriptor *nam = [aeItem descriptorForKeyword:pName]; @@ -135,17 +136,17 @@ NSString * const kGTMLoginItemsHiddenKey = @"Hide"; + (BOOL)pathInLoginItems:(NSString *)path { NSArray *loginItems = [self loginItems:nil]; - int index = [self indexOfLoginItemWithValue:path - forKey:kGTMLoginItemsPathKey - loginItems:loginItems]; + NSInteger index = [self indexOfLoginItemWithValue:path + forKey:kGTMLoginItemsPathKey + loginItems:loginItems]; return (index != NSNotFound) ? YES : NO; } + (BOOL)itemWithNameInLoginItems:(NSString *)name { NSArray *loginItems = [self loginItems:nil]; - int index = [self indexOfLoginItemWithValue:name - forKey:kGTMLoginItemsNameKey - loginItems:loginItems]; + NSInteger index = [self indexOfLoginItemWithValue:name + forKey:kGTMLoginItemsNameKey + loginItems:loginItems]; return (index != NSNotFound) ? YES : NO; } diff --git a/AppKit/GTMNSBezierPath+CGPath.m b/AppKit/GTMNSBezierPath+CGPath.m index c805f42..3bc6de9 100644 --- a/AppKit/GTMNSBezierPath+CGPath.m +++ b/AppKit/GTMNSBezierPath+CGPath.m @@ -1,5 +1,5 @@ // -// GTMNSBezierPath+CGPath.h +// GTMNSBezierPath+CGPath.m // // Category for extracting a CGPathRef from a NSBezierPath // @@ -33,13 +33,13 @@ CGMutablePathRef thePath = CGPathCreateMutable(); if (!thePath) return nil; - unsigned int elementCount = [self elementCount]; + NSInteger elementCount = [self elementCount]; // The maximum number of points is 3 for a NSCurveToBezierPathElement. // (controlPoint1, controlPoint2, and endPoint) NSPoint controlPoints[3]; - for (unsigned int i = 0; i < elementCount; i++) { + for (NSInteger i = 0; i < elementCount; i++) { switch ([self elementAtIndex:i associatedPoints:controlPoints]) { case NSMoveToBezierPathElement: CGPathMoveToPoint(thePath, &CGAffineTransformIdentity, diff --git a/AppKit/GTMNSBezierPath+CGPathTest.m b/AppKit/GTMNSBezierPath+CGPathTest.m index 3ca11c3..e22ee07 100644 --- a/AppKit/GTMNSBezierPath+CGPathTest.m +++ b/AppKit/GTMNSBezierPath+CGPathTest.m @@ -36,11 +36,11 @@ // Draws all of our tests so that we can compare this to our stored image file. - (void)gtm_unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{ NSBezierPath *thePath = [NSBezierPath bezierPath]; - NSPoint theStart = NSMakePoint(20.0f, 20.0f); + NSPoint theStart = NSMakePoint(20.0, 20.0); // Test moveto/lineto [thePath moveToPoint: theStart]; - for (unsigned int i = 0; i < 10; ++i) { + for (NSUInteger i = 0; i < 10; ++i) { NSPoint theNewPoint = NSMakePoint(i * 5, i * 10); [thePath lineToPoint: theNewPoint]; theNewPoint = NSMakePoint(i * 2, i * 6); @@ -48,11 +48,11 @@ } // Test moveto/curveto - for (unsigned int i = 0; i < 10; ++i) { - NSPoint startPoint = NSMakePoint(5.0f, 50.0f); - NSPoint endPoint = NSMakePoint(55.0f, 50.0f); - NSPoint controlPoint1 = NSMakePoint(17.5f, 50.0f + 5.0f * i); - NSPoint controlPoint2 = NSMakePoint(42.5f, 50.0f - 5.0f * i); + for (NSUInteger i = 0; i < 10; ++i) { + NSPoint startPoint = NSMakePoint(5.0, 50.0); + NSPoint endPoint = NSMakePoint(55.0, 50.0); + NSPoint controlPoint1 = NSMakePoint(17.5, 50.0 + 5.0 * i); + NSPoint controlPoint2 = NSMakePoint(42.5, 50.0 - 5.0 * i); [thePath moveToPoint:startPoint]; [thePath curveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2]; } diff --git a/AppKit/GTMNSBezierPath+RoundRect.h b/AppKit/GTMNSBezierPath+RoundRect.h index 8e916fe..b0b48de 100644 --- a/AppKit/GTMNSBezierPath+RoundRect.h +++ b/AppKit/GTMNSBezierPath+RoundRect.h @@ -20,6 +20,7 @@ // #import <Cocoa/Cocoa.h> +#import "GTMDefines.h" /// Category for adding utility functions for creating round rectangles. @interface NSBezierPath (GMBezierPathRoundRectAdditions) @@ -33,7 +34,8 @@ // // Returns: // Auto released NSBezierPath -+ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius; ++ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect + cornerRadius:(CGFloat)radius; /// Adds a path which is a round rectangle inscribed inside of rectangle |rect| with a corner radius of |radius| // @@ -41,5 +43,6 @@ // rect: outer rectangle to inscribe into // radius: radius of the corners. |radius| is clamped internally // to be no larger than the smaller of half |rect|'s width or height -- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius; +- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect + cornerRadius:(CGFloat)radius; @end diff --git a/AppKit/GTMNSBezierPath+RoundRect.m b/AppKit/GTMNSBezierPath+RoundRect.m index 4b98dc9..d4e5050 100644 --- a/AppKit/GTMNSBezierPath+RoundRect.m +++ b/AppKit/GTMNSBezierPath+RoundRect.m @@ -24,14 +24,16 @@ @implementation NSBezierPath (GTMBezierPathRoundRectAdditions) -+ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius { ++ (NSBezierPath *)gtm_bezierPathWithRoundRect:(NSRect)rect + cornerRadius:(CGFloat)radius { NSBezierPath *bezier = [NSBezierPath bezierPath]; [bezier gtm_appendBezierPathWithRoundRect:rect cornerRadius:radius]; return bezier; } -- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect cornerRadius:(float)radius { +- (void)gtm_appendBezierPathWithRoundRect:(NSRect)rect + cornerRadius:(CGFloat)radius { if (!NSIsEmptyRect(rect)) { if (radius > 0.0) { // Clamp radius to be no larger than half the rect's width or height. @@ -42,10 +44,18 @@ NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect)); [self moveToPoint:NSMakePoint(NSMidX(rect), NSMaxY(rect))]; - [self appendBezierPathWithArcFromPoint:topLeft toPoint:rect.origin radius:radius]; - [self appendBezierPathWithArcFromPoint:rect.origin toPoint:bottomRight radius:radius]; - [self appendBezierPathWithArcFromPoint:bottomRight toPoint:topRight radius:radius]; - [self appendBezierPathWithArcFromPoint:topRight toPoint:topLeft radius:radius]; + [self appendBezierPathWithArcFromPoint:topLeft + toPoint:rect.origin + radius:radius]; + [self appendBezierPathWithArcFromPoint:rect.origin + toPoint:bottomRight + radius:radius]; + [self appendBezierPathWithArcFromPoint:bottomRight + toPoint:topRight + radius:radius]; + [self appendBezierPathWithArcFromPoint:topRight + toPoint:topLeft + radius:radius]; [self closePath]; } else { // When radius <= 0.0, use plain rectangle. diff --git a/AppKit/GTMNSBezierPath+RoundRectTest.m b/AppKit/GTMNSBezierPath+RoundRectTest.m index 6ff4a18..c67c4b3 100644 --- a/AppKit/GTMNSBezierPath+RoundRectTest.m +++ b/AppKit/GTMNSBezierPath+RoundRectTest.m @@ -35,26 +35,26 @@ // Draws all of our tests so that we can compare this to our stored TIFF file. - (void)gtm_unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo{ NSRect theRects[] = { - NSMakeRect(0.0f, 10.0f, 0.0f, 0.0f), //Empty Rect test - NSMakeRect(50.0f, 10.0f, 30.0f, 30.0f), //Square Test - NSMakeRect(100.0f, 10.0f, 1.0f, 2.0f), //Small Test - NSMakeRect(120.0f, 10.0f, 15.0f, 20.0f), //Medium Test - NSMakeRect(140.0f, 10.0f, 150.0f, 30.0f) //Large Test + NSMakeRect(0.0, 10.0, 0.0, 0.0), //Empty Rect test + NSMakeRect(50.0, 10.0, 30.0, 30.0), //Square Test + NSMakeRect(100.0, 10.0, 1.0, 2.0), //Small Test + NSMakeRect(120.0, 10.0, 15.0, 20.0), //Medium Test + NSMakeRect(140.0, 10.0, 150.0, 30.0) //Large Test }; - const unsigned int theRectCount = sizeof(theRects) / sizeof(NSRect); + const NSUInteger theRectCount = sizeof(theRects) / sizeof(NSRect); // Line Width Tests - float theLineWidths[] = { 0.5f, 50.0f, 2.0f }; - const unsigned int theLineWidthCount = sizeof(theLineWidths) / sizeof(float); - unsigned int i,j; + CGFloat theLineWidths[] = { 0.5, 50.0, 2.0 }; + const NSUInteger theLineWidthCount = sizeof(theLineWidths) / sizeof(CGFloat); + NSUInteger i,j; for (i = 0; i < theLineWidthCount; ++i) { for (j = 0; j < theRectCount; ++j) { NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] - cornerRadius:20.0f]; + cornerRadius:20.0]; [roundRect setLineWidth: theLineWidths[i]]; [roundRect stroke]; - float newWidth = 35.0f; + CGFloat newWidth = 35.0; if (i < theLineWidthCount - 1) { newWidth += theLineWidths[i + 1] + theLineWidths[i]; } @@ -64,32 +64,32 @@ // Fill test NSColor *theColors[] = { - [NSColor colorWithCalibratedRed:1.0f green:0.0f blue:0.0f alpha:1.0f], - [NSColor colorWithCalibratedRed:0.2f green:0.4f blue:0.6f alpha:0.4f] + [NSColor colorWithCalibratedRed:1.0 green:0.0 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:0.2 green:0.4 blue:0.6 alpha:0.4] }; - const unsigned int theColorCount = sizeof(theColors)/sizeof(NSColor); + const NSUInteger theColorCount = sizeof(theColors)/sizeof(NSColor); for (i = 0; i < theColorCount; ++i) { for (j = 0; j < theRectCount; ++j) { NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] - cornerRadius:10.0f]; + cornerRadius:10.0]; [theColors[i] setFill]; [roundRect fill]; - theRects[j].origin.y += 35.0f; + theRects[j].origin.y += 35.0; } } // Flatness test - float theFlatness[] = {0.0f, 0.1f, 1.0f, 10.0f}; - const unsigned int theFlatnessCount = sizeof(theFlatness)/sizeof(float); + CGFloat theFlatness[] = {0.0, 0.1, 1.0, 10.0}; + const NSUInteger theFlatnessCount = sizeof(theFlatness)/sizeof(CGFloat); for (i = 0; i < theFlatnessCount; i++) { for (j = 0; j < theRectCount; ++j) { NSBezierPath *roundRect = [NSBezierPath gtm_bezierPathWithRoundRect:theRects[j] - cornerRadius:6.0f]; + cornerRadius:6.0]; [roundRect setFlatness:theFlatness[i]]; [roundRect stroke]; - theRects[j].origin.y += 35.0f; + theRects[j].origin.y += 35.0; } } } diff --git a/AppKit/GTMNSBezierPath+Shading.h b/AppKit/GTMNSBezierPath+Shading.h index b67043c..5d68728 100644 --- a/AppKit/GTMNSBezierPath+Shading.h +++ b/AppKit/GTMNSBezierPath+Shading.h @@ -19,6 +19,7 @@ // #import <Cocoa/Cocoa.h> +#import "GTMDefines.h" @protocol GTMShading; @@ -66,8 +67,8 @@ // last color in our shading? // shading: the shading to use to take our colors from. // -- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius - to:(NSPoint)toPoint toRadius:(float)toRadius +- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(CGFloat)fromRadius + to:(NSPoint)toPoint toRadius:(CGFloat)toRadius extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd shading:(id<GTMShading>)shading; @@ -113,8 +114,8 @@ // last color in our shading? // shading: the shading to use to take our colors from. // -- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius - to:(NSPoint)toPoint toRadius:(float)toRadius +- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(CGFloat)fromRadius + to:(NSPoint)toPoint toRadius:(CGFloat)toRadius extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd shading:(id<GTMShading>)shading; @end diff --git a/AppKit/GTMNSBezierPath+Shading.m b/AppKit/GTMNSBezierPath+Shading.m index f5935ab..4ab9ee3 100644 --- a/AppKit/GTMNSBezierPath+Shading.m +++ b/AppKit/GTMNSBezierPath+Shading.m @@ -22,6 +22,7 @@ #import "GTMNSBezierPath+CGPath.h" #import "GTMShading.h" #import "GTMGeometryUtils.h" +#import "GTMMethodCheck.h" @interface NSBezierPath (GTMBezierPathShadingAdditionsPrivate) // Fills a CGPathRef either axially or radially with the given shading. @@ -44,8 +45,8 @@ - (void)gtm_fillCGPath:(CGPathRef)path axially:(BOOL)axially asStroke:(BOOL)asStroke - from:(NSPoint)fromPoint fromRadius:(float)fromRadius - to:(NSPoint)toPoint toRadius:(float)toRadius + from:(NSPoint)fromPoint fromRadius:(CGFloat)fromRadius + to:(NSPoint)toPoint toRadius:(CGFloat)toRadius extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd shading:(id<GTMShading>)shading; @@ -62,7 +63,7 @@ // the projected point - (NSPoint)gtm_projectLineFrom:(NSPoint)pointA to:(NSPoint)pointB - by:(float)length; + by:(CGFloat)length; @end @@ -70,8 +71,8 @@ - (void)gtm_fillCGPath:(CGPathRef)path axially:(BOOL)axially asStroke:(BOOL)asStroke - from:(NSPoint)fromPoint fromRadius:(float)fromRadius - to:(NSPoint)toPoint toRadius:(float)toRadius + from:(NSPoint)fromPoint fromRadius:(CGFloat)fromRadius + to:(NSPoint)toPoint toRadius:(CGFloat)toRadius extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd shading:(id<GTMShading>)shading { CGFunctionRef shadingFunction = [shading shadeFunction]; @@ -79,15 +80,15 @@ CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; if (nil != currentContext) { CGContextSaveGState(currentContext); - float lineWidth = [self lineWidth]; - CGContextSetLineWidth(currentContext,lineWidth); + CGFloat lineWidth = [self lineWidth]; + CGContextSetLineWidth(currentContext, lineWidth); if (asStroke) { // if we are using the stroke, we offset the from and to points // by half the stroke width away from the center of the stroke. // Otherwise we tend to end up with fills that only cover half of the // because users set the start and end points based on the center // of the stroke. - float halfWidth = lineWidth * 0.5f; + CGFloat halfWidth = lineWidth * 0.5; fromPoint = [self gtm_projectLineFrom:toPoint to:fromPoint by:halfWidth]; toPoint = [self gtm_projectLineFrom:fromPoint to:toPoint by:-halfWidth]; } @@ -127,18 +128,24 @@ - (NSPoint)gtm_projectLineFrom:(NSPoint)pointA to:(NSPoint)pointB - by:(float)length { + by:(CGFloat)length { NSPoint newPoint = pointB; - float x = (pointB.x - pointA.x); - float y = (pointB.y - pointA.y); - if (x == 0.0f) { + CGFloat x = (pointB.x - pointA.x); + CGFloat y = (pointB.y - pointA.y); + if (x == 0.0) { newPoint.y += length; - } else if (y == 0.0f) { + } else if (y == 0.0) { newPoint.x += length; } else { - float angle = atanf(y / x); +#if CGFLOAT_IS_DOUBLE + CGFloat angle = atan(y / x); + newPoint.x += sin(angle) * length; + newPoint.y += cos(angle) * length; +#else + CGFloat angle = atanf(y / x); newPoint.x += sinf(angle) * length; newPoint.y += cosf(angle) * length; +#endif } return newPoint; } @@ -147,8 +154,7 @@ @implementation NSBezierPath (GTMBezierPathShadingAdditions) - -//METHOD_CHECK(NSBezierPath, gtm_createCGPath); +GTM_METHOD_CHECK(NSBezierPath, gtm_createCGPath); // COV_NF_LINE - (void)gtm_strokeAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd @@ -156,8 +162,8 @@ CGPathRef thePath = [self gtm_createCGPath]; if (nil != thePath) { [self gtm_fillCGPath:thePath axially:YES asStroke:YES - from:fromPoint fromRadius:0.0f - to:toPoint toRadius:0.0f + from:fromPoint fromRadius:(CGFloat)0.0 + to:toPoint toRadius:(CGFloat)0.0 extendingStart:extendingStart extendingEnd:extendingEnd shading:shading]; CGPathRelease(thePath); @@ -165,8 +171,8 @@ } -- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius - to:(NSPoint)toPoint toRadius:(float)toRadius +- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(CGFloat)fromRadius + to:(NSPoint)toPoint toRadius:(CGFloat)toRadius extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd shading:(id<GTMShading>)shading { CGPathRef thePath = [self gtm_createCGPath]; @@ -187,8 +193,8 @@ CGPathRef thePath = [self gtm_createCGPath]; if (nil != thePath) { [self gtm_fillCGPath:thePath axially:YES asStroke:NO - from:fromPoint fromRadius:0.0f - to:toPoint toRadius:0.0f + from:fromPoint fromRadius:(CGFloat)0.0 + to:toPoint toRadius:(CGFloat)0.0 extendingStart:extendingStart extendingEnd:extendingEnd shading:shading]; CGPathRelease(thePath); @@ -196,8 +202,8 @@ } -- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius - to:(NSPoint)toPoint toRadius:(float)toRadius +- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(CGFloat)fromRadius + to:(NSPoint)toPoint toRadius:(CGFloat)toRadius extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd shading:(id<GTMShading>)shading { CGPathRef thePath = [self gtm_createCGPath]; diff --git a/AppKit/GTMNSBezierPath+ShadingTest.m b/AppKit/GTMNSBezierPath+ShadingTest.m index 8b56cfb..9fa7a8f 100644 --- a/AppKit/GTMNSBezierPath+ShadingTest.m +++ b/AppKit/GTMNSBezierPath+ShadingTest.m @@ -40,13 +40,13 @@ [NSColor redColor], [NSColor yellowColor], [NSColor blueColor], [NSColor greenColor], [NSColor redColor] }; - float theFloatArray[] = { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.00f }; + CGFloat theFloatArray[] = { 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 }; GTMLinearRGBShading *shading = [GTMLinearRGBShading shadingWithColors:theColorArray fromSpaceNamed:NSCalibratedRGBColorSpace atPositions:theFloatArray - count:sizeof(theFloatArray)/sizeof(float)]; + count:sizeof(theFloatArray)/sizeof(CGFloat)]; NSBezierPath *shadedPath; // axialStrokeRect test diff --git a/AppKit/GTMNSColor+Theme.m b/AppKit/GTMNSColor+Theme.m deleted file mode 100644 index 67e8459..0000000 --- a/AppKit/GTMNSColor+Theme.m +++ /dev/null @@ -1,62 +0,0 @@ -// -// GTMNSColor+Theme.m -// -// Category for working with Themes and NSColor -// -// Copyright 2006-2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#import "GTMNSColor+Theme.h" -#import "GTMDefines.h" - -@implementation NSColor (GTMColorThemeAdditions) - -/// Create up an NSColor based on a Theme Text Color -/// Colors will be in the CalibratedRGB color space -+ (id)gtm_colorWithThemeTextColor:(ThemeTextColor)textColor { - NSColor *nsTextColor = nil; - RGBColor rgbTextColor; - OSStatus status = GetThemeTextColor(textColor, 32, true, &rgbTextColor); - if (status == noErr) { - float red = rgbTextColor.red / 65535.0f; - float green = rgbTextColor.green / 65535.0f; - float blue = rgbTextColor.blue / 65535.0f; - nsTextColor = [NSColor colorWithCalibratedRed:red - green:green - blue:blue - alpha:1.0f]; - } else { - _GTMDevLog(@"Unable to create color for textcolor %d", textColor); - } - return nsTextColor; -} - -/// Create up an NSColor based on a Theme Brush -/// Colors will be in the CalibratedRGB color space -+ (id)gtm_colorWithThemeBrush:(ThemeBrush)brush { - NSColor *nsBrushColor = nil; - RGBColor rgbBrushColor; - OSStatus status = GetThemeBrushAsColor(brush, 32, true, &rgbBrushColor); - if (status == noErr) { - nsBrushColor = [NSColor colorWithCalibratedRed:rgbBrushColor.red / 65535.0f - green:rgbBrushColor.green / 65535.0f - blue:rgbBrushColor.blue / 65535.0f - alpha:1.0f]; - } else { - _GTMDevLog(@"Unable to create color for brushcolor %d", brush); - } - return nsBrushColor; -} -@end diff --git a/AppKit/GTMNSColor+ThemeTest.m b/AppKit/GTMNSColor+ThemeTest.m deleted file mode 100644 index e7d2a77..0000000 --- a/AppKit/GTMNSColor+ThemeTest.m +++ /dev/null @@ -1,199 +0,0 @@ -// -// NSColor+ThemeTest.m -// -// Copyright 2006-2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#import <SenTestingKit/SenTestingKit.h> -#import "GTMNSColor+Theme.h" -#import "GTMNSWorkspace+Theme.h" -#import "GTMSystemVersion.h" - - -@interface GTMNSColor_ThemeTest : SenTestCase -@end - -@implementation GTMNSColor_ThemeTest - -//METHOD_CHECK(NSWorkspace, themeAppearance); - -- (void)testColorWithThemeTextColor { - float colorValues[][4] = { - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 0.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.449989, 0.449989, 0.449989, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.449989, 0.449989, 0.449989, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.599985, 0.599985, 0.599985, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.599985, 0.599985, 0.599985, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.499992, 0.499992, 0.499992, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 } - }; - - if ([GTMSystemVersion isLeopardOrGreater]) { - // kThemeTextColorRootMenuDisabled changed to white in Leopard. - colorValues[35][0] = 1.0; - colorValues[35][1] = 1.0; - colorValues[35][2] = 1.0; - } - for(int i = kThemeTextColorWhite; i < kThemeTextColorSystemDetail; i++) { - if (i == 0) continue; // There is no brush 0 - NSColor *textColor = [NSColor gtm_colorWithThemeTextColor:i]; - float nsComponents[5]; - [textColor getComponents: nsComponents]; - for(int j = 0; j < 4; j++) { - STAssertEqualsWithAccuracy(nsComponents[j], colorValues[i + 2][j], 0.000001, - @"Theme Text Color %d is wrong", i); - STAssertEqualObjects([textColor colorSpaceName], NSCalibratedRGBColorSpace, - @"Color space must be CalibratedRGB"); - } - } -} - -- (void)testColorWithThemeBrushColor { - float colorValues[][4] = { - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 0.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.980011, 0.990005, 0.990005, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.709789, 0.835294, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.492195, 0.675792, 0.847669, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.000000, 0.000000, 0.000000, 1.000000 }, - { 0.500008, 0.500008, 0.500008, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.600000, 0.600000, 0.600000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.510002, 0.510002, 0.510002, 1.000000 }, - { 0.760006, 0.760006, 0.760006, 1.000000 }, - { 0.940002, 0.940002, 0.940002, 1.000000 }, - { 0.980011, 0.980011, 0.980011, 1.000000 }, - { 0.670008, 0.670008, 0.670008, 1.000000 }, - { 0.860014, 0.860014, 0.860014, 1.000000 }, - { 0.880003, 0.880003, 0.880003, 1.000000 }, - { 0.880003, 0.880003, 0.880003, 1.000000 }, - { 0.990005, 0.990005, 0.990005, 1.000000 }, - { 0.900008, 0.900008, 0.900008, 1.000000 }, - { 0.930007, 0.930007, 0.930007, 1.000000 }, - { 0.930007, 0.930007, 0.930007, 1.000000 }, - { 0.990005, 0.990005, 0.990005, 1.000000 }, - { 0.540002, 0.540002, 0.540002, 1.000000 }, - { 0.590005, 0.590005, 0.590005, 1.000000 }, - { 0.590005, 0.590005, 0.590005, 1.000000 }, - { 0.730007, 0.730007, 0.730007, 1.000000 }, - { 0.640009, 0.640009, 0.640009, 1.000000 }, - { 0.640009, 0.640009, 0.640009, 1.000000 }, - { 0.820005, 0.820005, 0.820005, 1.000000 }, - { 0.820005, 0.820005, 0.820005, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 1.000000, 1.000000, 1.000000, 1.000000 }, - { 0.925490, 0.952895, 0.996094, 1.000000 } - }; - - NSString *theme = [[NSWorkspace sharedWorkspace] gtm_themeAppearance]; - if ([theme isEqualToString:(NSString*)kThemeAppearanceAquaGraphite]) { - // COV_NF_START - // These are the only two brushes that change with an appearance change - // In general we will be testing in blue, so this code won't get run - colorValues[21][0] = 0.605478; - colorValues[21][1] = 0.667979; - colorValues[21][2] = 0.738293; - colorValues[59][0] = 0.941192; - colorValues[59][1] = 0.941192; - colorValues[59][2] = 0.941192; - // COV_NF_END - } - for(int i = kThemeBrushWhite; i < kThemeBrushListViewColumnDivider; i++) { - // Brush "14" is the selection, so it will change depending on the system - // There is no brush 0. - if (i == 0 || i == 14) continue; - NSColor *brushColor = [NSColor gtm_colorWithThemeBrush:i]; - float nsComponents[5]; - [brushColor getComponents: nsComponents]; - for(int j = 0; j < 4; j++) { - STAssertEqualsWithAccuracy(nsComponents[j], colorValues[i + 2][j], 0.000001, - @"Theme Text Brush %d is wrong", i + 2); - STAssertEqualObjects([brushColor colorSpaceName], NSCalibratedRGBColorSpace, - @"Color space must be CalibratedRB"); - } - } -} - -@end diff --git a/AppKit/GTMNSWorkspace+ScreenSaver.m b/AppKit/GTMNSWorkspace+ScreenSaver.m index c86a73c..9e1eb6a 100644 --- a/AppKit/GTMNSWorkspace+ScreenSaver.m +++ b/AppKit/GTMNSWorkspace+ScreenSaver.m @@ -77,8 +77,9 @@ BOOL answer = NO; ScreenSaverController *controller = nil; // We're calling into an "undocumented" framework here, so we are going to - // step rather carefully. + // step rather carefully (and in 10.5.2 it's only 32bit). +#if !__LP64__ Class screenSaverControllerClass = NSClassFromString(@"ScreenSaverController"); _GTMDevAssert(screenSaverControllerClass, @"Are you linked with ScreenSaver.framework?" @@ -96,6 +97,7 @@ } } } +#endif // !__LP64__ if (!controller) { // COV_NF_START diff --git a/AppKit/GTMNSWorkspace+Theme.h b/AppKit/GTMNSWorkspace+Theme.h deleted file mode 100644 index 364d623..0000000 --- a/AppKit/GTMNSWorkspace+Theme.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// GTMNSWorkspace+ScreenSaver.h -// -// Copyright 2007-2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#import <Cocoa/Cocoa.h> -#import <Carbon/Carbon.h> - -enum { - // This means that we had a failure getting the style - kThemeScrollBarArrowsInvalid = -1, - // This is the "missing" scroll bar arrow style that some people use - kThemeScrollBarArrowsDouble = 2 -}; - -/// Category for interacting with the screen saver -@interface NSWorkspace (GTMWorkspaceThemeAddition) - -// returns one of the kThemeAppearance... constants as an autoreleased NSString -// tells you whether we are running under blue or graphite -- (NSString*)gtm_themeAppearance; - -// Returns how the user has their scroll bars configured. -- (ThemeScrollBarArrowStyle)gtm_themeScrollBarArrowStyle; -@end - diff --git a/AppKit/GTMNSWorkspace+Theme.m b/AppKit/GTMNSWorkspace+Theme.m deleted file mode 100644 index 3afb801..0000000 --- a/AppKit/GTMNSWorkspace+Theme.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// GTMNSWorkspace+Theme.m -// -// Copyright 2007-2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#import "GTMNSWorkspace+Theme.h" -#import "GTMGarbageCollection.h" - -@implementation NSWorkspace (GTMWorkspaceThemeAddition) - -- (NSString*)gtm_themeAppearance { - CFStringRef cfTheme; - NSString *nsTheme = nil; - OSStatus theStatus = CopyThemeIdentifier(&cfTheme); - if (theStatus == noErr) { - nsTheme = [GTMNSMakeCollectable(cfTheme) autorelease]; - } - return nsTheme; -} - -- (ThemeScrollBarArrowStyle)gtm_themeScrollBarArrowStyle { - ThemeScrollBarArrowStyle style = kThemeScrollBarArrowsInvalid; - OSStatus theStatus = GetThemeScrollBarArrowStyle(&style); - if (theStatus != noErr) { - style = kThemeScrollBarArrowsInvalid; // COV_NF_LINE - } - return style; -} -@end diff --git a/AppKit/GTMNSWorkspace+ThemeTest.m b/AppKit/GTMNSWorkspace+ThemeTest.m deleted file mode 100644 index ae1cb66..0000000 --- a/AppKit/GTMNSWorkspace+ThemeTest.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// NSWorkspace+ThemeTest.m -// -// Copyright 2007-2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#import <SenTestingKit/SenTestingKit.h> -#import "GTMSenTestCase.h" -#import "GTMNSWorkspace+Theme.h" - -@interface GTMNSWorkspace_ThemeTest : SenTestCase -@end - -@implementation GTMNSWorkspace_ThemeTest - -- (void)testThemeAppearance { - NSString *theme = [[NSWorkspace sharedWorkspace] gtm_themeAppearance]; - STAssertNotNil(theme, nil); - STAssertTrue([theme hasPrefix:(NSString*)kThemeAppearanceAqua], nil); -} - -- (void)testThemeScrollBarArrowStyle { - ThemeScrollBarArrowStyle style = [[NSWorkspace sharedWorkspace] gtm_themeScrollBarArrowStyle]; - STAssertLessThanOrEqual(style, (ThemeScrollBarArrowStyle)kThemeScrollBarArrowsDouble, nil); -} -@end diff --git a/AppKit/TestData/GTMNSBezierPath+CGPathTest.ppc64.tiff b/AppKit/TestData/GTMNSBezierPath+CGPathTest.ppc64.tiff Binary files differnew file mode 100644 index 0000000..33c3e17 --- /dev/null +++ b/AppKit/TestData/GTMNSBezierPath+CGPathTest.ppc64.tiff diff --git a/AppKit/GTMNSBezierPath+CGPathTest.tiff b/AppKit/TestData/GTMNSBezierPath+CGPathTest.tiff Binary files differindex 98ec8f8..98ec8f8 100644 --- a/AppKit/GTMNSBezierPath+CGPathTest.tiff +++ b/AppKit/TestData/GTMNSBezierPath+CGPathTest.tiff diff --git a/AppKit/TestData/GTMNSBezierPath+CGPathTest.x86_64.tiff b/AppKit/TestData/GTMNSBezierPath+CGPathTest.x86_64.tiff Binary files differnew file mode 100644 index 0000000..33c3e17 --- /dev/null +++ b/AppKit/TestData/GTMNSBezierPath+CGPathTest.x86_64.tiff diff --git a/AppKit/TestData/GTMNSBezierPath+RoundRectTest.ppc64.tiff b/AppKit/TestData/GTMNSBezierPath+RoundRectTest.ppc64.tiff Binary files differnew file mode 100644 index 0000000..a4df8f8 --- /dev/null +++ b/AppKit/TestData/GTMNSBezierPath+RoundRectTest.ppc64.tiff diff --git a/AppKit/GTMNSBezierPath+RoundRectTest.tiff b/AppKit/TestData/GTMNSBezierPath+RoundRectTest.tiff Binary files differindex c41ab04..c41ab04 100644 --- a/AppKit/GTMNSBezierPath+RoundRectTest.tiff +++ b/AppKit/TestData/GTMNSBezierPath+RoundRectTest.tiff diff --git a/AppKit/TestData/GTMNSBezierPath+RoundRectTest.x86_64.tiff b/AppKit/TestData/GTMNSBezierPath+RoundRectTest.x86_64.tiff Binary files differnew file mode 100644 index 0000000..a4df8f8 --- /dev/null +++ b/AppKit/TestData/GTMNSBezierPath+RoundRectTest.x86_64.tiff diff --git a/AppKit/GTMNSBezierPath+ShadingTest.10.5.tiff b/AppKit/TestData/GTMNSBezierPath+ShadingTest.10.5.tiff Binary files differindex 040cb0d..040cb0d 100644 --- a/AppKit/GTMNSBezierPath+ShadingTest.10.5.tiff +++ b/AppKit/TestData/GTMNSBezierPath+ShadingTest.10.5.tiff diff --git a/DebugUtils/GTMDebugSelectorValidation.h b/DebugUtils/GTMDebugSelectorValidation.h index b3f1e73..82d00e8 100644 --- a/DebugUtils/GTMDebugSelectorValidation.h +++ b/DebugUtils/GTMDebugSelectorValidation.h @@ -40,39 +40,37 @@ static void GTMAssertSelectorNilOrImplementedWithArguments(id obj, SEL sel, ...) if (obj && sel) { // check that the selector is implemented - if (![obj respondsToSelector:sel]) { - _GTMDevAssert(NO, - @"\"%@\" selector \"%@\" is unimplemented or misnamed", - NSStringFromClass([obj class]), - NSStringFromSelector(sel)); - } else { - const char *expectedArgType; - int argCount = 2; // skip self and _cmd - NSMethodSignature *sig = [obj methodSignatureForSelector:sel]; - - // check that each expected argument is present and of the correct type - while ((expectedArgType = va_arg(argList, const char*)) != 0) { + _GTMDevAssert([obj respondsToSelector:sel], + @"\"%@\" selector \"%@\" is unimplemented or misnamed", + NSStringFromClass([obj class]), + NSStringFromSelector(sel)); + + const char *expectedArgType; + NSUInteger argCount = 2; // skip self and _cmd + NSMethodSignature *sig = [obj methodSignatureForSelector:sel]; + + // check that each expected argument is present and of the correct type + while ((expectedArgType = va_arg(argList, const char*)) != 0) { - if ([sig numberOfArguments] > argCount) { - const char *foundArgType = [sig getArgumentTypeAtIndex:argCount]; + if ([sig numberOfArguments] > argCount) { + const char *foundArgType = [sig getArgumentTypeAtIndex:argCount]; - _GTMDevAssert(0 == strncmp(foundArgType, expectedArgType, strlen(expectedArgType)), - @"\"%@\" selector \"%@\" argument %d should be type %s", - NSStringFromClass([obj class]), - NSStringFromSelector(sel), - (argCount - 2), - expectedArgType); - } - argCount++; + _GTMDevAssert(0 == strncmp(foundArgType, expectedArgType, strlen(expectedArgType)), + @"\"%@\" selector \"%@\" argument %d should be type %s", + NSStringFromClass([obj class]), + NSStringFromSelector(sel), + (argCount - 2), + expectedArgType); } - - // check that the proper number of arguments are present in the selector - _GTMDevAssert(argCount == [sig numberOfArguments], - @"\"%@\" selector \"%@\" should have %d arguments", - NSStringFromClass([obj class]), - NSStringFromSelector(sel), - (argCount - 2)); + argCount++; } + + // check that the proper number of arguments are present in the selector + _GTMDevAssert(argCount == [sig numberOfArguments], + @"\"%@\" selector \"%@\" should have %d arguments", + NSStringFromClass([obj class]), + NSStringFromSelector(sel), + (argCount - 2)); } va_end(argList); diff --git a/DebugUtils/GTMMethodCheck.m b/DebugUtils/GTMMethodCheck.m index 379f006..f91b1a9 100644 --- a/DebugUtils/GTMMethodCheck.m +++ b/DebugUtils/GTMMethodCheck.m @@ -47,11 +47,21 @@ static BOOL ConformsToNSObjectProtocol(Class cls) { return YES; } +// iPhone SDK does not define the |Object| class, so we instead test for the +// |NSObject| class. +#if GTM_IPHONE_SDK + // Iterate through all the protocols |cls| supports looking for NSObject. + if (cls == [NSObject class] + || class_conformsToProtocol(cls, @protocol(NSObject))) { + return YES; + } +#else // Iterate through all the protocols |cls| supports looking for NSObject. if (cls == [Object class] || class_conformsToProtocol(cls, @protocol(NSObject))) { return YES; } +#endif // Recursively check the superclasses. return ConformsToNSObjectProtocol(class_getSuperclass(cls)); diff --git a/Foundation/GTMBase64.h b/Foundation/GTMBase64.h new file mode 100644 index 0000000..169b1c3 --- /dev/null +++ b/Foundation/GTMBase64.h @@ -0,0 +1,183 @@ +// +// GTMBase64.h +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Foundation/Foundation.h> +#import "GTMDefines.h" + +// GTMBase64 +// +/// Helper for handling Base64 and WebSafeBase64 encodings +// +/// The webSafe methods use different character set and also the results aren't +/// always padded to a multiple of 4 characters. This is done so the resulting +/// data can be used in urls and url query arguments without needing any +/// encoding. You must use the webSafe* methods together, the data does not +/// interop with the RFC methods. +// +@interface GTMBase64 : NSObject + +// +// Standard Base64 (RFC) handling +// + +// encodeData: +// +/// Base64 encodes contents of the NSData object. +// +/// Returns: +/// A new autoreleased NSData with the encoded payload. nil for any error. +// ++(NSData *)encodeData:(NSData *)data; + +// decodeData: +// +/// Base64 decodes contents of the NSData object. +// +/// Returns: +/// A new autoreleased NSData with the decoded payload. nil for any error. +// ++(NSData *)decodeData:(NSData *)data; + +// encodeBytes:length: +// +/// Base64 encodes the data pointed at by |bytes|. +// +/// Returns: +/// A new autoreleased NSData with the encoded payload. nil for any error. +// ++(NSData *)encodeBytes:(const void *)bytes length:(NSUInteger)length; + +// decodeBytes:length: +// +/// Base64 decodes the data pointed at by |bytes|. +// +/// Returns: +/// A new autoreleased NSData with the encoded payload. nil for any error. +// ++(NSData *)decodeBytes:(const void *)bytes length:(NSUInteger)length; + +// stringByEncodingData: +// +/// Base64 encodes contents of the NSData object. +// +/// Returns: +/// A new autoreleased NSString with the encoded payload. nil for any error. +// ++(NSString *)stringByEncodingData:(NSData *)data; + +// stringByEncodingBytes:length: +// +/// Base64 encodes the data pointed at by |bytes|. +// +/// Returns: +/// A new autoreleased NSString with the encoded payload. nil for any error. +// ++(NSString *)stringByEncodingBytes:(const void *)bytes length:(NSUInteger)length; + +// decodeString: +// +/// Base64 decodes contents of the NSString. +// +/// Returns: +/// A new autoreleased NSData with the decoded payload. nil for any error. +// ++(NSData *)decodeString:(NSString *)string; + +// +// Modified Base64 encoding so the results can go onto urls. +// +// The changes are in the characters generated and also allows the result to +// not be padded to a multiple of 4. +// Must use the matching call to encode/decode, won't interop with the +// RFC versions. +// + +// webSafeEncodeData:padded: +// +/// WebSafe Base64 encodes contents of the NSData object. If |padded| is YES +/// then padding characters are added so the result length is a multiple of 4. +// +/// Returns: +/// A new autoreleased NSData with the encoded payload. nil for any error. +// ++(NSData *)webSafeEncodeData:(NSData *)data + padded:(BOOL)padded; + +// webSafeDecodeData: +// +/// WebSafe Base64 decodes contents of the NSData object. +// +/// Returns: +/// A new autoreleased NSData with the decoded payload. nil for any error. +// ++(NSData *)webSafeDecodeData:(NSData *)data; + +// webSafeEncodeBytes:length:padded: +// +/// WebSafe Base64 encodes the data pointed at by |bytes|. If |padded| is YES +/// then padding characters are added so the result length is a multiple of 4. +// +/// Returns: +/// A new autoreleased NSData with the encoded payload. nil for any error. +// ++(NSData *)webSafeEncodeBytes:(const void *)bytes + length:(NSUInteger)length + padded:(BOOL)padded; + +// webSafeDecodeBytes:length: +// +/// WebSafe Base64 decodes the data pointed at by |bytes|. +// +/// Returns: +/// A new autoreleased NSData with the encoded payload. nil for any error. +// ++(NSData *)webSafeDecodeBytes:(const void *)bytes length:(NSUInteger)length; + +// stringByWebSafeEncodingData:padded: +// +/// WebSafe Base64 encodes contents of the NSData object. If |padded| is YES +/// then padding characters are added so the result length is a multiple of 4. +// +/// Returns: +/// A new autoreleased NSString with the encoded payload. nil for any error. +// ++(NSString *)stringByWebSafeEncodingData:(NSData *)data + padded:(BOOL)padded; + +// stringByWebSafeEncodingBytes:length:padded: +// +/// WebSafe Base64 encodes the data pointed at by |bytes|. If |padded| is YES +/// then padding characters are added so the result length is a multiple of 4. +// +/// Returns: +/// A new autoreleased NSString with the encoded payload. nil for any error. +// ++(NSString *)stringByWebSafeEncodingBytes:(const void *)bytes + length:(NSUInteger)length + padded:(BOOL)padded; + +// webSafeDecodeString: +// +/// WebSafe Base64 decodes contents of the NSString. +// +/// Returns: +/// A new autoreleased NSData with the decoded payload. nil for any error. +// ++(NSData *)webSafeDecodeString:(NSString *)string; + +@end diff --git a/Foundation/GTMBase64.m b/Foundation/GTMBase64.m new file mode 100644 index 0000000..06d0414 --- /dev/null +++ b/Foundation/GTMBase64.m @@ -0,0 +1,697 @@ +// +// GTMBase64.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMBase64.h" +#import "GTMDefines.h" + +static const char *kBase64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char *kWebSafeBase64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +static const char kBase64PaddingChar = '='; +static const char kBase64InvalidChar = 99; + +static const char kBase64DecodeChars[] = { + // This array was generated by the following code: + // #include <sys/time.h> + // #include <stdlib.h> + // #include <string.h> + // main() + // { + // static const char Base64[] = + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + // char *pos; + // int idx, i, j; + // printf(" "); + // for (i = 0; i < 255; i += 8) { + // for (j = i; j < i + 8; j++) { + // pos = strchr(Base64, j); + // if ((pos == NULL) || (j == 0)) + // idx = 99; + // else + // idx = pos - Base64; + // if (idx == 99) + // printf(" %2d, ", idx); + // else + // printf(" %2d/*%c*/,", idx, j); + // } + // printf("\n "); + // } + // } + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 62/*+*/, 99, 99, 99, 63/*/ */, + 52/*0*/, 53/*1*/, 54/*2*/, 55/*3*/, 56/*4*/, 57/*5*/, 58/*6*/, 59/*7*/, + 60/*8*/, 61/*9*/, 99, 99, 99, 99, 99, 99, + 99, 0/*A*/, 1/*B*/, 2/*C*/, 3/*D*/, 4/*E*/, 5/*F*/, 6/*G*/, + 7/*H*/, 8/*I*/, 9/*J*/, 10/*K*/, 11/*L*/, 12/*M*/, 13/*N*/, 14/*O*/, + 15/*P*/, 16/*Q*/, 17/*R*/, 18/*S*/, 19/*T*/, 20/*U*/, 21/*V*/, 22/*W*/, + 23/*X*/, 24/*Y*/, 25/*Z*/, 99, 99, 99, 99, 99, + 99, 26/*a*/, 27/*b*/, 28/*c*/, 29/*d*/, 30/*e*/, 31/*f*/, 32/*g*/, + 33/*h*/, 34/*i*/, 35/*j*/, 36/*k*/, 37/*l*/, 38/*m*/, 39/*n*/, 40/*o*/, + 41/*p*/, 42/*q*/, 43/*r*/, 44/*s*/, 45/*t*/, 46/*u*/, 47/*v*/, 48/*w*/, + 49/*x*/, 50/*y*/, 51/*z*/, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; + +static const char kWebSafeBase64DecodeChars[] = { + // This array was generated by the following code: + // #include <sys/time.h> + // #include <stdlib.h> + // #include <string.h> + // main() + // { + // static const char Base64[] = + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + // char *pos; + // int idx, i, j; + // printf(" "); + // for (i = 0; i < 255; i += 8) { + // for (j = i; j < i + 8; j++) { + // pos = strchr(Base64, j); + // if ((pos == NULL) || (j == 0)) + // idx = 99; + // else + // idx = pos - Base64; + // if (idx == 99) + // printf(" %2d, ", idx); + // else + // printf(" %2d/*%c*/,", idx, j); + // } + // printf("\n "); + // } + // } + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 62/*-*/, 99, 99, + 52/*0*/, 53/*1*/, 54/*2*/, 55/*3*/, 56/*4*/, 57/*5*/, 58/*6*/, 59/*7*/, + 60/*8*/, 61/*9*/, 99, 99, 99, 99, 99, 99, + 99, 0/*A*/, 1/*B*/, 2/*C*/, 3/*D*/, 4/*E*/, 5/*F*/, 6/*G*/, + 7/*H*/, 8/*I*/, 9/*J*/, 10/*K*/, 11/*L*/, 12/*M*/, 13/*N*/, 14/*O*/, + 15/*P*/, 16/*Q*/, 17/*R*/, 18/*S*/, 19/*T*/, 20/*U*/, 21/*V*/, 22/*W*/, + 23/*X*/, 24/*Y*/, 25/*Z*/, 99, 99, 99, 99, 63/*_*/, + 99, 26/*a*/, 27/*b*/, 28/*c*/, 29/*d*/, 30/*e*/, 31/*f*/, 32/*g*/, + 33/*h*/, 34/*i*/, 35/*j*/, 36/*k*/, 37/*l*/, 38/*m*/, 39/*n*/, 40/*o*/, + 41/*p*/, 42/*q*/, 43/*r*/, 44/*s*/, 45/*t*/, 46/*u*/, 47/*v*/, 48/*w*/, + 49/*x*/, 50/*y*/, 51/*z*/, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; + + +// Tests a charact to see if it's a whitespace character. +// +// Returns: +// YES if the character is a whitespace character. +// NO if the character is not a whitespace character. +// +FOUNDATION_STATIC_INLINE BOOL IsSpace(unsigned char c) { + // we use our own mapping here because we don't want anything w/ locale + // support. + static BOOL kSpaces[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 0-9 + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 10-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20-29 + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 30-39 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 150-159 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 240-249 + 0, 0, 0, 0, 0, 1, // 250-255 + }; + return kSpaces[c]; +} + +// Calculate how long the data will be once it's base64 encoded. +// +// Returns: +// The guessed encoded length for a source length +// +FOUNDATION_STATIC_INLINE NSUInteger CalcEncodedLength(NSUInteger srcLen, + BOOL padded) { + NSUInteger intermediate_result = 8 * srcLen + 5; + NSUInteger len = intermediate_result / 6; + if (padded) { + len = ((len + 3) / 4) * 4; + } + return len; +} + +// Tries to calculate how long the data will be once it's base64 decoded. +// Unlinke the above, this is always an upperbound, since the source data +// could have spaces and might end with the padding characters on them. +// +// Returns: +// The guessed decoded length for a source length +// +FOUNDATION_STATIC_INLINE NSUInteger GuessDecodedLength(NSUInteger srcLen) { + return (srcLen + 3) / 4 * 3; +} + + +@interface GTMBase64 (PrivateMethods) + ++(NSData *)baseEncode:(const void *)bytes + length:(NSUInteger)length + charset:(const char *)charset + padded:(BOOL)padded; + ++(NSData *)baseDecode:(const void *)bytes + length:(NSUInteger)length + charset:(const char*)charset + requirePadding:(BOOL)requirePadding; + ++(NSUInteger)baseEncode:(const char *)srcBytes + srcLen:(NSUInteger)srcLen + destBytes:(char *)destBytes + destLen:(NSUInteger)destLen + charset:(const char *)charset + padded:(BOOL)padded; + ++(NSUInteger)baseDecode:(const char *)srcBytes + srcLen:(NSUInteger)srcLen + destBytes:(char *)destBytes + destLen:(NSUInteger)destLen + charset:(const char *)charset + requirePadding:(BOOL)requirePadding; + +@end + + +@implementation GTMBase64 + +// +// Standard Base64 (RFC) handling +// + ++(NSData *)encodeData:(NSData *)data { + return [self baseEncode:[data bytes] + length:[data length] + charset:kBase64EncodeChars + padded:YES]; +} + ++(NSData *)decodeData:(NSData *)data { + return [self baseDecode:[data bytes] + length:[data length] + charset:kBase64DecodeChars + requirePadding:YES]; +} + ++(NSData *)encodeBytes:(const void *)bytes length:(NSUInteger)length { + return [self baseEncode:bytes + length:length + charset:kBase64EncodeChars + padded:YES]; +} + ++(NSData *)decodeBytes:(const void *)bytes length:(NSUInteger)length { + return [self baseDecode:bytes + length:length + charset:kBase64DecodeChars + requirePadding:YES]; +} + ++(NSString *)stringByEncodingData:(NSData *)data { + NSString *result = nil; + NSData *converted = [self baseEncode:[data bytes] + length:[data length] + charset:kBase64EncodeChars + padded:YES]; + if (converted) { + result = [[[NSString alloc] initWithData:converted + encoding:NSASCIIStringEncoding] autorelease]; + } + return result; +} + ++(NSString *)stringByEncodingBytes:(const void *)bytes length:(NSUInteger)length { + NSString *result = nil; + NSData *converted = [self baseEncode:bytes + length:length + charset:kBase64EncodeChars + padded:YES]; + if (converted) { + result = [[[NSString alloc] initWithData:converted + encoding:NSASCIIStringEncoding] autorelease]; + } + return result; +} + ++(NSData *)decodeString:(NSString *)string { + NSData *result = nil; + NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding]; + if (data) { + result = [self baseDecode:[data bytes] + length:[data length] + charset:kBase64DecodeChars + requirePadding:YES]; + } + return result; +} + +// +// Modified Base64 encoding so the results can go onto urls. +// +// The changes are in the characters generated and also the result isn't +// padded to a multiple of 4. +// Must use the matching call to encode/decode, won't interop with the +// RFC versions. +// + ++(NSData *)webSafeEncodeData:(NSData *)data + padded:(BOOL)padded { + return [self baseEncode:[data bytes] + length:[data length] + charset:kWebSafeBase64EncodeChars + padded:padded]; +} + ++(NSData *)webSafeDecodeData:(NSData *)data { + return [self baseDecode:[data bytes] + length:[data length] + charset:kWebSafeBase64DecodeChars + requirePadding:NO]; +} + ++(NSData *)webSafeEncodeBytes:(const void *)bytes + length:(NSUInteger)length + padded:(BOOL)padded { + return [self baseEncode:bytes + length:length + charset:kWebSafeBase64EncodeChars + padded:padded]; +} + ++(NSData *)webSafeDecodeBytes:(const void *)bytes length:(NSUInteger)length { + return [self baseDecode:bytes + length:length + charset:kWebSafeBase64DecodeChars + requirePadding:NO]; +} + ++(NSString *)stringByWebSafeEncodingData:(NSData *)data + padded:(BOOL)padded { + NSString *result = nil; + NSData *converted = [self baseEncode:[data bytes] + length:[data length] + charset:kWebSafeBase64EncodeChars + padded:padded]; + if (converted) { + result = [[[NSString alloc] initWithData:converted + encoding:NSASCIIStringEncoding] autorelease]; + } + return result; +} + ++(NSString *)stringByWebSafeEncodingBytes:(const void *)bytes + length:(NSUInteger)length + padded:(BOOL)padded { + NSString *result = nil; + NSData *converted = [self baseEncode:bytes + length:length + charset:kWebSafeBase64EncodeChars + padded:padded]; + if (converted) { + result = [[[NSString alloc] initWithData:converted + encoding:NSASCIIStringEncoding] autorelease]; + } + return result; +} + ++(NSData *)webSafeDecodeString:(NSString *)string { + NSData *result = nil; + NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding]; + if (data) { + result = [self baseDecode:[data bytes] + length:[data length] + charset:kWebSafeBase64DecodeChars + requirePadding:NO]; + } + return result; +} + +@end + +@implementation GTMBase64 (PrivateMethods) + +// +// baseEncode:length:charset:padded: +// +// Does the common lifting of creating the dest NSData. it creates & sizes the +// data for the results. |charset| is the characters to use for the encoding +// of the data. |padding| controls if the encoded data should be padded to a +// multiple of 4. +// +// Returns: +// an autorelease NSData with the encoded data, nil if any error. +// ++(NSData *)baseEncode:(const void *)bytes + length:(NSUInteger)length + charset:(const char *)charset + padded:(BOOL)padded { + // how big could it be? + NSUInteger maxLength = CalcEncodedLength(length, padded); + // make space + NSMutableData *result = [NSMutableData data]; + [result setLength:maxLength]; + // do it + NSUInteger finalLength = [self baseEncode:bytes + srcLen:length + destBytes:[result mutableBytes] + destLen:[result length] + charset:charset + padded:padded]; + if (finalLength) { + _GTMDevAssert(finalLength == maxLength, @"how did we calc the length wrong?"); + } else { + // shouldn't happen, this means we ran out of space + result = nil; + } + return result; +} + +// +// baseDecode:length:charset:requirePadding: +// +// Does the common lifting of creating the dest NSData. it creates & sizes the +// data for the results. |charset| is the characters to use for the decoding +// of the data. +// +// Returns: +// an autorelease NSData with the decoded data, nil if any error. +// +// ++(NSData *)baseDecode:(const void *)bytes + length:(NSUInteger)length + charset:(const char *)charset + requirePadding:(BOOL)requirePadding { + // could try to calculate what it will end up as + NSUInteger maxLength = GuessDecodedLength(length); + // make space + NSMutableData *result = [NSMutableData data]; + [result setLength:maxLength]; + // do it + NSUInteger finalLength = [self baseDecode:bytes + srcLen:length + destBytes:[result mutableBytes] + destLen:[result length] + charset:charset + requirePadding:requirePadding]; + if (finalLength) { + if (finalLength != maxLength) { + // resize down to how big it was + [result setLength:finalLength]; + } + } else { + // either an error in the args, or we ran out of space + result = nil; + } + return result; +} + +// +// baseEncode:srcLen:destBytes:destLen:charset:padded: +// +// Encodes the buffer into the larger. returns the length of the encoded +// data, or zero for an error. +// |charset| is the characters to use for the encoding +// |padded| tells if the result should be padded to a multiple of 4. +// +// Returns: +// the length of the encoded data. zero if any error. +// ++(NSUInteger)baseEncode:(const char *)srcBytes + srcLen:(NSUInteger)srcLen + destBytes:(char *)destBytes + destLen:(NSUInteger)destLen + charset:(const char *)charset + padded:(BOOL)padded { + if (!srcLen || !destLen || !srcBytes || !destBytes) { + return 0; + } + + char *curDest = destBytes; + const unsigned char *curSrc = (const unsigned char *)(srcBytes); + + // Three bytes of data encodes to four characters of cyphertext. + // So we can pump through three-byte chunks atomically. + while (srcLen > 2) { + // space? + _GTMDevAssert(destLen >= 4, @"our calc for encoded length was wrong"); + curDest[0] = charset[curSrc[0] >> 2]; + curDest[1] = charset[((curSrc[0] & 0x03) << 4) + (curSrc[1] >> 4)]; + curDest[2] = charset[((curSrc[1] & 0x0f) << 2) + (curSrc[2] >> 6)]; + curDest[3] = charset[curSrc[2] & 0x3f]; + + curDest += 4; + curSrc += 3; + srcLen -= 3; + destLen -= 4; + } + + // now deal with the tail (<=2 bytes) + switch (srcLen) { + case 0: + // Nothing left; nothing more to do. + break; + case 1: + // One byte left: this encodes to two characters, and (optionally) + // two pad characters to round out the four-character cypherblock. + _GTMDevAssert(destLen >= 2, @"our calc for encoded length was wrong"); + curDest[0] = charset[curSrc[0] >> 2]; + curDest[1] = charset[(curSrc[0] & 0x03) << 4]; + curDest += 2; + destLen -= 2; + if (padded) { + _GTMDevAssert(destLen >= 2, @"our calc for encoded length was wrong"); + curDest[0] = kBase64PaddingChar; + curDest[1] = kBase64PaddingChar; + curDest += 2; + destLen -= 2; + } + break; + case 2: + // Two bytes left: this encodes to three characters, and (optionally) + // one pad character to round out the four-character cypherblock. + _GTMDevAssert(destLen >= 3, @"our calc for encoded length was wrong"); + curDest[0] = charset[curSrc[0] >> 2]; + curDest[1] = charset[((curSrc[0] & 0x03) << 4) + (curSrc[1] >> 4)]; + curDest[2] = charset[(curSrc[1] & 0x0f) << 2]; + curDest += 3; + destLen -= 3; + if (padded) { + _GTMDevAssert(destLen >= 1, @"our calc for encoded length was wrong"); + curDest[0] = kBase64PaddingChar; + curDest += 1; + destLen -= 1; + } + break; + } + // return the length + return (curDest - destBytes); +} + +// +// baseDecode:srcLen:destBytes:destLen:charset:requirePadding: +// +// Decodes the buffer into the larger. returns the length of the decoded +// data, or zero for an error. +// |charset| is the character decoding buffer to use +// +// Returns: +// the length of the encoded data. zero if any error. +// ++(NSUInteger)baseDecode:(const char *)srcBytes + srcLen:(NSUInteger)srcLen + destBytes:(char *)destBytes + destLen:(NSUInteger)destLen + charset:(const char *)charset + requirePadding:(BOOL)requirePadding { + if (!srcLen || !destLen || !srcBytes || !destBytes) { + return 0; + } + + int decode; + NSUInteger destIndex = 0; + int state = 0; + char ch = 0; + while (srcLen-- && (ch = *srcBytes++) != 0) { + if (IsSpace(ch)) // Skip whitespace + continue; + + if (ch == kBase64PaddingChar) + break; + + decode = charset[(unsigned int)ch]; + if (decode == kBase64InvalidChar) + return 0; + + // Four cyphertext characters decode to three bytes. + // Therefore we can be in one of four states. + switch (state) { + case 0: + // We're at the beginning of a four-character cyphertext block. + // This sets the high six bits of the first byte of the + // plaintext block. + _GTMDevAssert(destIndex < destLen, @"our calc for decoded length was wrong"); + destBytes[destIndex] = decode << 2; + state = 1; + break; + case 1: + // We're one character into a four-character cyphertext block. + // This sets the low two bits of the first plaintext byte, + // and the high four bits of the second plaintext byte. + _GTMDevAssert((destIndex+1) < destLen, @"our calc for decoded length was wrong"); + destBytes[destIndex] |= decode >> 4; + destBytes[destIndex+1] = (decode & 0x0f) << 4; + destIndex++; + state = 2; + break; + case 2: + // We're two characters into a four-character cyphertext block. + // This sets the low four bits of the second plaintext + // byte, and the high two bits of the third plaintext byte. + // However, if this is the end of data, and those two + // bits are zero, it could be that those two bits are + // leftovers from the encoding of data that had a length + // of two mod three. + _GTMDevAssert((destIndex+1) < destLen, @"our calc for decoded length was wrong"); + destBytes[destIndex] |= decode >> 2; + destBytes[destIndex+1] = (decode & 0x03) << 6; + destIndex++; + state = 3; + break; + case 3: + // We're at the last character of a four-character cyphertext block. + // This sets the low six bits of the third plaintext byte. + _GTMDevAssert(destIndex < destLen, @"our calc for decoded length was wrong"); + destBytes[destIndex] |= decode; + destIndex++; + state = 0; + break; + } + } + + // We are done decoding Base-64 chars. Let's see if we ended + // on a byte boundary, and/or with erroneous trailing characters. + if (ch == kBase64PaddingChar) { // We got a pad char + if ((state == 0) || (state == 1)) { + return 0; // Invalid '=' in first or second position + } + if (srcLen == 0) { + if (state == 2) { // We run out of input but we still need another '=' + return 0; + } + // Otherwise, we are in state 3 and only need this '=' + } else { + if (state == 2) { // need another '=' + while ((ch = *srcBytes++) && (srcLen-- > 0)) { + if (!IsSpace(ch)) + break; + } + if (ch != kBase64PaddingChar) { + return 0; + } + } + // state = 1 or 2, check if all remain padding is space + while ((ch = *srcBytes++) && (srcLen-- > 0)) { + if (!IsSpace(ch)) { + return 0; + } + } + } + } else { + // We ended by seeing the end of the string. + + if (requirePadding) { + // If we require padding, then anything but state 0 is an error. + if (state != 0) { + return 0; + } + } else { + // Make sure we have no partial bytes lying around. Note that we do not + // require trailing '=', so states 2 and 3 are okay too. + if (state == 1) { + return 0; + } + } + } + + // If then next piece of output was valid and got written to it means we got a + // very carefully crafted input that appeared valid but contains some trailing + // bits past the real length, so just toss the thing. + if ((destIndex < destLen) && + (destBytes[destIndex] != 0)) { + return 0; + } + + return destIndex; +} + +@end diff --git a/Foundation/GTMBase64Test.m b/Foundation/GTMBase64Test.m new file mode 100644 index 0000000..358c6ec --- /dev/null +++ b/Foundation/GTMBase64Test.m @@ -0,0 +1,437 @@ +// +// GTMBase64Test.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMSenTestCase.h" +#import "GTMBase64.h" +#include <stdlib.h> // for randiom/srandomdev + +static void FillWithRandom(char *data, NSUInteger len) { + char *max = data + len; + for ( ; data < max ; ++data) { + *data = random() & 0xFF; + } +} + +static BOOL NoEqualChar(NSData *data) { + const char *scan = [data bytes]; + const char *max = scan + [data length]; + for ( ; scan < max ; ++scan) { + if (*scan == '=') { + return NO; + } + } + return YES; +} + +@interface GTMBase64Test : SenTestCase +@end + +@implementation GTMBase64Test + +- (void)setUp { + // seed random from /dev/random + srandomdev(); +} + +- (void)testBase64 { + // generate a range of sizes w/ random content + for (int x = 1 ; x < 1024 ; ++x) { + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + [data setLength:x]; + FillWithRandom([data mutableBytes], [data length]); + + // w/ *Bytes apis + NSData *encoded = [GTMBase64 encodeBytes:[data bytes] length:[data length]]; + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Bytes apis should be a multiple of 4"); + NSData *dataPrime = [GTMBase64 decodeBytes:[encoded bytes] + length:[encoded length]]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Bytes apis"); + + // w/ *Data apis + encoded = [GTMBase64 encodeData:data]; + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Data apis should be a multiple of 4"); + dataPrime = [GTMBase64 decodeData:encoded]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Data apis"); + + // Bytes to String and back + NSString *encodedString = [GTMBase64 stringByEncodingBytes:[data bytes] + length:[data length]]; + STAssertEquals(([encodedString length] % 4), (NSUInteger)0, + @"encoded size for Bytes to Strings should be a multiple of 4"); + dataPrime = [GTMBase64 decodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Bytes to Strings"); + + // Data to String and back + encodedString = [GTMBase64 stringByEncodingData:data]; + STAssertEquals(([encodedString length] % 4), (NSUInteger)0, + @"encoded size for Data to Strings should be a multiple of 4"); + dataPrime = [GTMBase64 decodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Bytes to Strings"); + } + + { + // now test all byte values + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + [data setLength:256]; + unsigned char *scan = (unsigned char*)[data mutableBytes]; + for (int x = 0 ; x <= 255 ; ++x) { + *scan++ = x; + } + + // w/ *Bytes apis + NSData *encoded = [GTMBase64 encodeBytes:[data bytes] length:[data length]]; + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Bytes apis should be a multiple of 4"); + NSData *dataPrime = [GTMBase64 decodeBytes:[encoded bytes] + length:[encoded length]]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Bytes apis"); + + // w/ *Data apis + encoded = [GTMBase64 encodeData:data]; + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Data apis should be a multiple of 4"); + dataPrime = [GTMBase64 decodeData:encoded]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Data apis"); + + // Bytes to String and back + NSString *encodedString = [GTMBase64 stringByEncodingBytes:[data bytes] + length:[data length]]; + STAssertEquals(([encodedString length] % 4), (NSUInteger)0, + @"encoded size for Bytes to Strings should be a multiple of 4"); + dataPrime = [GTMBase64 decodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Bytes to Strings"); + + // Data to String and back + encodedString = [GTMBase64 stringByEncodingData:data]; + STAssertEquals(([encodedString length] % 4), (NSUInteger)0, + @"encoded size for Data to Strings should be a multiple of 4"); + dataPrime = [GTMBase64 decodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Data to Strings"); + } + + { + // test w/ a mix of spacing characters + + // generate some data, encode it, and add spaces + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + [data setLength:253]; // should get some padding chars on the end + FillWithRandom([data mutableBytes], [data length]); + + NSString *encodedString = [GTMBase64 stringByEncodingData:data]; + NSMutableString *encodedAndSpaced = + [[encodedString mutableCopy] autorelease]; + + NSString *spaces[] = { @"\t", @"\n", @"\r", @" " }; + const NSUInteger numSpaces = sizeof(spaces) / sizeof(NSString*); + for (int x = 0 ; x < 512 ; ++x) { + NSUInteger offset = random() % ([encodedAndSpaced length] + 1); + [encodedAndSpaced insertString:spaces[random() % numSpaces] + atIndex:offset]; + } + + // we'll need it as data for apis + NSData *encodedAsData = + [encodedAndSpaced dataUsingEncoding:NSASCIIStringEncoding]; + STAssertNotNil(encodedAsData, @"failed to extract from string"); + STAssertEquals([encodedAsData length], [encodedAndSpaced length], + @"lengths for encoded string and data didn't match?"); + + // all the decode modes + NSData *dataPrime = [GTMBase64 decodeData:encodedAsData]; + STAssertEqualObjects(data, dataPrime, + @"failed Data decode w/ spaces"); + dataPrime = [GTMBase64 decodeBytes:[encodedAsData bytes] + length:[encodedAsData length]]; + STAssertEqualObjects(data, dataPrime, + @"failed Bytes decode w/ spaces"); + dataPrime = [GTMBase64 decodeString:encodedAndSpaced]; + STAssertEqualObjects(data, dataPrime, + @"failed String decode w/ spaces"); + } +} + +- (void)testWebSafeBase64 { + // loop to test w/ and w/o padding + for (int paddedLoop = 0; paddedLoop < 2 ; ++paddedLoop) { + BOOL padded = (paddedLoop == 1); + + // generate a range of sizes w/ random content + for (int x = 1 ; x < 1024 ; ++x) { + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + [data setLength:x]; + FillWithRandom([data mutableBytes], [data length]); + + // w/ *Bytes apis + NSData *encoded = [GTMBase64 webSafeEncodeBytes:[data bytes] + length:[data length] + padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Bytes apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via *Bytes apis had a base64 padding char"); + } + NSData *dataPrime = [GTMBase64 webSafeDecodeBytes:[encoded bytes] + length:[encoded length]]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Bytes apis"); + + // w/ *Data apis + encoded = [GTMBase64 webSafeEncodeData:data padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Data apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via *Data apis had a base64 padding char"); + } + dataPrime = [GTMBase64 webSafeDecodeData:encoded]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Data apis"); + + // Bytes to String and back + NSString *encodedString = + [GTMBase64 stringByWebSafeEncodingBytes:[data bytes] + length:[data length] + padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Bytes apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via Bytes to Strings had a base64 padding char"); + } + dataPrime = [GTMBase64 webSafeDecodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Bytes to Strings"); + + // Data to String and back + encodedString = + [GTMBase64 stringByWebSafeEncodingData:data padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Data apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via Data to Strings had a base64 padding char"); + } + dataPrime = [GTMBase64 webSafeDecodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Data to Strings"); + } + + { + // now test all byte values + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + [data setLength:256]; + unsigned char *scan = (unsigned char*)[data mutableBytes]; + for (int x = 0 ; x <= 255 ; ++x) { + *scan++ = x; + } + + // w/ *Bytes apis + NSData *encoded = + [GTMBase64 webSafeEncodeBytes:[data bytes] + length:[data length] + padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Bytes apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via *Bytes apis had a base64 padding char"); + } + NSData *dataPrime = [GTMBase64 webSafeDecodeBytes:[encoded bytes] + length:[encoded length]]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Bytes apis"); + + // w/ *Data apis + encoded = [GTMBase64 webSafeEncodeData:data padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Data apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via *Data apis had a base64 padding char"); + } + dataPrime = [GTMBase64 webSafeDecodeData:encoded]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Data apis"); + + // Bytes to String and back + NSString *encodedString = + [GTMBase64 stringByWebSafeEncodingBytes:[data bytes] + length:[data length] + padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Bytes apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via Bytes to Strings had a base64 padding char"); + } + dataPrime = [GTMBase64 webSafeDecodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Bytes to Strings"); + + // Data to String and back + encodedString = + [GTMBase64 stringByWebSafeEncodingData:data padded:padded]; + if (padded) { + STAssertEquals(([encoded length] % 4), (NSUInteger)0, + @"encoded size via *Data apis should be a multiple of 4"); + } else { + STAssertTrue(NoEqualChar(encoded), + @"encoded via Data to Strings had a base64 padding char"); + } + dataPrime = [GTMBase64 webSafeDecodeString:encodedString]; + STAssertEqualObjects(data, dataPrime, + @"failed to round trip for Data to Strings"); + } + + { + // test w/ a mix of spacing characters + + // generate some data, encode it, and add spaces + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + + [data setLength:253]; // should get some padding chars on the end + FillWithRandom([data mutableBytes], [data length]); + + NSString *encodedString = [GTMBase64 stringByWebSafeEncodingData:data + padded:padded]; + NSMutableString *encodedAndSpaced = + [[encodedString mutableCopy] autorelease]; + + NSString *spaces[] = { @"\t", @"\n", @"\r", @" " }; + const NSUInteger numSpaces = sizeof(spaces) / sizeof(NSString*); + for (int x = 0 ; x < 512 ; ++x) { + NSUInteger offset = random() % ([encodedAndSpaced length] + 1); + [encodedAndSpaced insertString:spaces[random() % numSpaces] + atIndex:offset]; + } + + // we'll need it as data for apis + NSData *encodedAsData = + [encodedAndSpaced dataUsingEncoding:NSASCIIStringEncoding]; + STAssertNotNil(encodedAsData, @"failed to extract from string"); + STAssertEquals([encodedAsData length], [encodedAndSpaced length], + @"lengths for encoded string and data didn't match?"); + + // all the decode modes + NSData *dataPrime = [GTMBase64 webSafeDecodeData:encodedAsData]; + STAssertEqualObjects(data, dataPrime, + @"failed Data decode w/ spaces"); + dataPrime = [GTMBase64 webSafeDecodeBytes:[encodedAsData bytes] + length:[encodedAsData length]]; + STAssertEqualObjects(data, dataPrime, + @"failed Bytes decode w/ spaces"); + dataPrime = [GTMBase64 webSafeDecodeString:encodedAndSpaced]; + STAssertEqualObjects(data, dataPrime, + @"failed String decode w/ spaces"); + } + } // paddedLoop +} + +- (void)testErrors { + const int something = 0; + NSString *nonAscString = [NSString stringWithUTF8String:"This test ©™®๒०᠐٧"]; + + STAssertNil([GTMBase64 encodeData:nil], @"it worked?"); + STAssertNil([GTMBase64 decodeData:nil], @"it worked?"); + STAssertNil([GTMBase64 encodeBytes:NULL length:10], @"it worked?"); + STAssertNil([GTMBase64 encodeBytes:&something length:0], @"it worked?"); + STAssertNil([GTMBase64 decodeBytes:NULL length:10], @"it worked?"); + STAssertNil([GTMBase64 decodeBytes:&something length:0], @"it worked?"); + STAssertNil([GTMBase64 stringByEncodingData:nil], @"it worked?"); + STAssertNil([GTMBase64 stringByEncodingBytes:NULL length:10], @"it worked?"); + STAssertNil([GTMBase64 stringByEncodingBytes:&something length:0], @"it worked?"); + STAssertNil([GTMBase64 decodeString:nil], @"it worked?"); + // test some pads at the end that aren't right + STAssertNil([GTMBase64 decodeString:@"=="], @"it worked?"); // just pads + STAssertNil([GTMBase64 decodeString:@"vw="], @"it worked?"); // missing pad (in state 2) + STAssertNil([GTMBase64 decodeString:@"vw"], @"it worked?"); // missing pad (in state 2) + STAssertNil([GTMBase64 decodeString:@"NNw"], @"it worked?"); // missing pad (in state 3) + STAssertNil([GTMBase64 decodeString:@"vw=v"], @"it worked?"); // missing pad, has something else + STAssertNil([GTMBase64 decodeString:@"v="], @"it worked?"); // missing a needed char, has pad instead + STAssertNil([GTMBase64 decodeString:@"v"], @"it worked?"); // missing a needed char + STAssertNil([GTMBase64 decodeString:@"vw== vw"], @"it worked?"); + STAssertNil([GTMBase64 decodeString:nonAscString], @"it worked?"); + STAssertNil([GTMBase64 decodeString:@"@@@not valid###"], @"it worked?"); + // carefully crafted bad input to make sure we don't overwalk + STAssertNil([GTMBase64 decodeString:@"WD=="], @"it worked?"); + + STAssertNil([GTMBase64 webSafeEncodeData:nil padded:YES], @"it worked?"); + STAssertNil([GTMBase64 webSafeDecodeData:nil], @"it worked?"); + STAssertNil([GTMBase64 webSafeEncodeBytes:NULL length:10 padded:YES], + @"it worked?"); + STAssertNil([GTMBase64 webSafeEncodeBytes:&something length:0 padded:YES], + @"it worked?"); + STAssertNil([GTMBase64 webSafeDecodeBytes:NULL length:10], @"it worked?"); + STAssertNil([GTMBase64 webSafeDecodeBytes:&something length:0], @"it worked?"); + STAssertNil([GTMBase64 stringByWebSafeEncodingData:nil padded:YES], + @"it worked?"); + STAssertNil([GTMBase64 stringByWebSafeEncodingBytes:NULL + length:10 + padded:YES], + @"it worked?"); + STAssertNil([GTMBase64 stringByWebSafeEncodingBytes:&something + length:0 + padded:YES], + @"it worked?"); + STAssertNil([GTMBase64 webSafeDecodeString:nil], @"it worked?"); + // test some pads at the end that aren't right + STAssertNil([GTMBase64 webSafeDecodeString:@"=="], @"it worked?"); // just pad chars + STAssertNil([GTMBase64 webSafeDecodeString:@"aw="], @"it worked?"); // missing pad + STAssertNil([GTMBase64 webSafeDecodeString:@"aw=a"], @"it worked?"); // missing pad, has something else + STAssertNil([GTMBase64 webSafeDecodeString:@"a"], @"it worked?"); // missing a needed char + STAssertNil([GTMBase64 webSafeDecodeString:@"a="], @"it worked?"); // missing a needed char, has pad instead + STAssertNil([GTMBase64 webSafeDecodeString:@"aw== a"], @"it worked?"); // missing pad + STAssertNil([GTMBase64 webSafeDecodeString:nonAscString], @"it worked?"); + STAssertNil([GTMBase64 webSafeDecodeString:@"@@@not valid###"], @"it worked?"); + // carefully crafted bad input to make sure we don't overwalk + STAssertNil([GTMBase64 webSafeDecodeString:@"WD=="], @"it worked?"); + + // make sure our local helper is working right + STAssertFalse(NoEqualChar([NSData dataWithBytes:"aa=zz" length:5]), @""); +} + +@end diff --git a/Foundation/GTMCalculatedRange.h b/Foundation/GTMCalculatedRange.h index 5f51b3e..5710da6 100644 --- a/Foundation/GTMCalculatedRange.h +++ b/Foundation/GTMCalculatedRange.h @@ -20,6 +20,7 @@ // #import <Foundation/Foundation.h> +#import "GTMDefines.h" /// Allows you to calculate a value based on defined stops in a range. // @@ -46,7 +47,7 @@ // item: the object to place at |position|. // position: the position in the range to put |item|. // -- (void)insertStop:(id)item atPosition:(float)position; +- (void)insertStop:(id)item atPosition:(CGFloat)position; // Removes a stop from the range at |position|. // @@ -56,7 +57,7 @@ // Returns: // YES if there is a stop at |position| that has been removed // NO if there is not a stop at the |position| -- (BOOL)removeStopAtPosition:(float)position; +- (BOOL)removeStopAtPosition:(CGFloat)position; // Removes stop |index| from the range. Stops are ordered // based on position where index of x < index of y if position @@ -66,13 +67,13 @@ // item: the object to place at |position|. // position: the position in the range to put |item|. // -- (void)removeStopAtIndex:(unsigned int)index; +- (void)removeStopAtIndex:(NSUInteger)index; // Returns the number of stops in the range. // // Returns: // number of stops -- (unsigned int)stopCount; +- (NSUInteger)stopCount; // Returns the value at position |position|. // This function should be overridden by subclasses to calculate a @@ -85,7 +86,7 @@ // // Returns: // value for position -- (id)valueAtPosition:(float)position; +- (id)valueAtPosition:(CGFloat)position; // Returns the |index|'th stop and position in the set. // Throws an exception if out of range. @@ -97,5 +98,5 @@ // // Returns: // the stop at the index. -- (id)stopAtIndex:(unsigned int)index position:(float*)outPosition; +- (id)stopAtIndex:(NSUInteger)index position:(CGFloat*)outPosition; @end diff --git a/Foundation/GTMCalculatedRange.m b/Foundation/GTMCalculatedRange.m index 8562d8a..435ad65 100644 --- a/Foundation/GTMCalculatedRange.m +++ b/Foundation/GTMCalculatedRange.m @@ -22,21 +22,21 @@ // position. @interface GTMCalculatedRangeStopPrivate : NSObject { id item_; // the item (STRONG) - float position_; // + CGFloat position_; // } -+ (id)stopWithObject:(id)item position:(float)inPosition; -- (id)initWithObject:(id)item position:(float)inPosition; ++ (id)stopWithObject:(id)item position:(CGFloat)inPosition; +- (id)initWithObject:(id)item position:(CGFloat)inPosition; - (id)item; -- (float)position; +- (CGFloat)position; @end @implementation GTMCalculatedRangeStopPrivate -+ (id)stopWithObject:(id)item position:(float)inPosition { ++ (id)stopWithObject:(id)item position:(CGFloat)inPosition { return [[[[self class] alloc] initWithObject:item position:inPosition] autorelease]; } -- (id)initWithObject:(id)item position:(float)inPosition { +- (id)initWithObject:(id)item position:(CGFloat)inPosition { self = [super init]; if (self != nil) { item_ = [item retain]; @@ -54,7 +54,7 @@ return item_; } -- (float)position { +- (CGFloat)position { return position_; } @@ -76,8 +76,8 @@ [super dealloc]; } -- (void)insertStop:(id)item atPosition:(float)position { - unsigned int index = 0; +- (void)insertStop:(id)item atPosition:(CGFloat)position { + NSUInteger index = 0; NSEnumerator *theEnumerator = [storage_ objectEnumerator]; GTMCalculatedRangeStopPrivate *theStop; while (nil != (theStop = [theEnumerator nextObject])) { @@ -85,15 +85,17 @@ index += 1; } else if ([theStop position] == position) { + // remove and stop the enum since we just modified the object [storage_ removeObjectAtIndex:index]; + break; } } [storage_ insertObject:[GTMCalculatedRangeStopPrivate stopWithObject:item position:position] - atIndex:index]; + atIndex:index]; } -- (BOOL)removeStopAtPosition:(float)position { - unsigned int index = 0; +- (BOOL)removeStopAtPosition:(CGFloat)position { + NSUInteger index = 0; BOOL foundStop = NO; NSEnumerator *theEnumerator = [storage_ objectEnumerator]; GTMCalculatedRangeStopPrivate *theStop; @@ -111,15 +113,15 @@ return foundStop; } -- (void)removeStopAtIndex:(unsigned int)index { +- (void)removeStopAtIndex:(NSUInteger)index { [storage_ removeObjectAtIndex:index]; } -- (unsigned int)stopCount { +- (NSUInteger)stopCount { return [storage_ count]; } -- (id)stopAtIndex:(unsigned int)index position:(float*)outPosition { +- (id)stopAtIndex:(NSUInteger)index position:(CGFloat*)outPosition { GTMCalculatedRangeStopPrivate *theStop = [storage_ objectAtIndex:index]; if (nil != outPosition) { *outPosition = [theStop position]; @@ -127,7 +129,7 @@ return [theStop item]; } -- (id)valueAtPosition:(float)position { +- (id)valueAtPosition:(CGFloat)position { id theValue = nil; GTMCalculatedRangeStopPrivate *theStop; NSEnumerator *theEnumerator = [storage_ objectEnumerator]; diff --git a/Foundation/GTMCalculatedRangeTest.m b/Foundation/GTMCalculatedRangeTest.m index 0c374c1..1d716c8 100644 --- a/Foundation/GTMCalculatedRangeTest.m +++ b/Foundation/GTMCalculatedRangeTest.m @@ -26,14 +26,14 @@ @implementation GTMCalculatedRangeTest NSString *kStrings[] = { @"Fee", @"Fi", @"Fo", @"Fum" }; -const unsigned int kStringCount = sizeof(kStrings) / sizeof(NSString*); -const float kOddPosition = 0.14159265f; -const float kExistingPosition = 0.5f; -const unsigned int kExisitingIndex = 2; +const NSUInteger kStringCount = sizeof(kStrings) / sizeof(NSString*); +const CGFloat kOddPosition = 0.14159265f; +const CGFloat kExistingPosition = 0.5f; +const NSUInteger kExisitingIndex = 2; - (void)setUp { range_ = [[GTMCalculatedRange alloc] init]; - for(unsigned int i = kStringCount; i > 0; --i) { + for(NSUInteger i = kStringCount; i > 0; --i) { [range_ insertStop:kStrings[kStringCount - i] atPosition: 1.0f / i]; } } @@ -43,12 +43,22 @@ const unsigned int kExisitingIndex = 2; } - (void)testInsertStop { + // new position NSString *theString = @"I smell the blood of an Englishman!"; - [range_ insertStop:theString atPosition: kOddPosition]; + [range_ insertStop:theString atPosition:kOddPosition]; STAssertEquals([range_ stopCount], kStringCount + 1, @"Stop count was bad"); NSString *getString = [range_ valueAtPosition:kOddPosition]; STAssertNotNil(getString, @"String was bad"); STAssertEquals(theString, getString, @"Stops weren't equal"); + // existing position + NSString *theStringTake2 = @"I smell the blood of an Englishman! Take 2"; + [range_ insertStop:theStringTake2 atPosition:kOddPosition]; + STAssertEquals([range_ stopCount], kStringCount + 1, @"Stop count was bad"); + getString = [range_ valueAtPosition:kOddPosition]; + STAssertNotNil(getString, @"String was bad"); + STAssertEquals(theStringTake2, getString, @"Stops weren't equal"); + STAssertNotEquals(theString, getString, @"Should be the new value"); + STAssertNotEqualObjects(theString, getString, @"Should be the new value"); } - (void)testRemoveStopAtPosition { @@ -74,7 +84,7 @@ const unsigned int kExisitingIndex = 2; } - (void)testStopAtIndex { - float thePosition; + CGFloat thePosition; STAssertEqualObjects([range_ stopAtIndex:kStringCount - 1 position:nil], kStrings[kStringCount - 1], nil); STAssertEqualObjects([range_ stopAtIndex:kExisitingIndex position:&thePosition], kStrings[kExisitingIndex], nil); @@ -83,5 +93,9 @@ const unsigned int kExisitingIndex = 2; STAssertThrows([range_ stopAtIndex:kStringCount position:nil], nil); } +- (void)testDescription { + // we expect a description of atleast a few chars + STAssertGreaterThan([[range_ description] length], (NSUInteger)10, nil); +} @end diff --git a/Foundation/GTMGeometryUtils.h b/Foundation/GTMGeometryUtils.h index 32c8745..a58ac13 100644 --- a/Foundation/GTMGeometryUtils.h +++ b/Foundation/GTMGeometryUtils.h @@ -19,15 +19,17 @@ // the License. // -#include <Foundation/Foundation.h> +#import <Foundation/Foundation.h> +#import "GTMDefines.h" -typedef enum { +enum { GTMScaleProportionally = 0, // Fit proportionally GTMScaleToFit, // Forced fit (distort if necessary) GTMScaleNone // Don't scale (clip) -} GTMScaling; +}; +typedef NSUInteger GTMScaling; -typedef enum { +enum { GTMRectAlignCenter = 0, GTMRectAlignTop, GTMRectAlignTopLeft, @@ -37,7 +39,8 @@ typedef enum { GTMRectAlignBottomLeft, GTMRectAlignBottomRight, GTMRectAlignRight -} GTMRectAlignment; +}; +typedef NSUInteger GTMRectAlignment; #pragma mark Miscellaneous @@ -49,10 +52,14 @@ typedef enum { // // Returns: // Distance -CG_INLINE float GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { - float dX = pt1.x - pt2.x; - float dY = pt1.y - pt2.y; +CG_INLINE CGFloat GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { + CGFloat dX = pt1.x - pt2.x; + CGFloat dY = pt1.y - pt2.y; +#if CGFLOAT_IS_DOUBLE + return sqrt(dX * dX + dY * dY); +#else return sqrtf(dX * dX + dY * dY); +#endif } #pragma mark - @@ -314,7 +321,7 @@ CG_INLINE CGRect GTMCGRectOfSize(CGSize size) { // // Returns: // Converted Rect -CG_INLINE NSRect GTMNSRectScale(NSRect inRect, float xScale, float yScale) { +CG_INLINE NSRect GTMNSRectScale(NSRect inRect, CGFloat xScale, CGFloat yScale) { return NSMakeRect(inRect.origin.x, inRect.origin.y, inRect.size.width * xScale, inRect.size.height * yScale); } @@ -328,7 +335,7 @@ CG_INLINE NSRect GTMNSRectScale(NSRect inRect, float xScale, float yScale) { // // Returns: // Converted Rect -CG_INLINE CGRect GTMCGRectScale(CGRect inRect, float xScale, float yScale) { +CG_INLINE CGRect GTMCGRectScale(CGRect inRect, CGFloat xScale, CGFloat yScale) { return CGRectMake(inRect.origin.x, inRect.origin.y, inRect.size.width * xScale, inRect.size.height * yScale); } diff --git a/Foundation/GTMGeometryUtils.m b/Foundation/GTMGeometryUtils.m index f5b38dc..0e893ff 100644 --- a/Foundation/GTMGeometryUtils.m +++ b/Foundation/GTMGeometryUtils.m @@ -79,13 +79,13 @@ NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, GTMRectAlignment align NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, GTMScaling scaling) { switch (scaling) { case GTMScaleProportionally: { - float height = NSHeight(scalee); - float width = NSWidth(scalee); + CGFloat height = NSHeight(scalee); + CGFloat width = NSWidth(scalee); if (isnormal(height) && isnormal(width) && (height > size.height || width > size.width)) { - float horiz = size.width / width; - float vert = size.height / height; - float newScale = horiz < vert ? horiz : vert; + CGFloat horiz = size.width / width; + CGFloat vert = size.height / height; + CGFloat newScale = horiz < vert ? horiz : vert; scalee = GTMNSRectScale(scalee, newScale, newScale); } break; diff --git a/Foundation/GTMGeometryUtilsTest.m b/Foundation/GTMGeometryUtilsTest.m index 2fb0c68..606ea6b 100644 --- a/Foundation/GTMGeometryUtilsTest.m +++ b/Foundation/GTMGeometryUtilsTest.m @@ -64,11 +64,11 @@ - (void)testGTMDistanceBetweenPoints { NSPoint pt1 = NSMakePoint(0, 0); NSPoint pt2 = NSMakePoint(3, 4); - STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 5.0f, nil); - STAssertEquals(GTMDistanceBetweenPoints(pt2, pt1), 5.0f, nil); + STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), (CGFloat)5.0, nil); + STAssertEquals(GTMDistanceBetweenPoints(pt2, pt1), (CGFloat)5.0, nil); pt1 = NSMakePoint(1, 1); pt2 = NSMakePoint(1, 1); - STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 0.0f, nil); + STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), (CGFloat)0.0, nil); } - (void)testGTMAlignRectangles { @@ -109,37 +109,37 @@ CGPoint cgPoint = GTMCGMidLeft(cgRect); STAssertEquals(point.x, cgPoint.x, nil); STAssertEquals(point.y, cgPoint.y, nil); - STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil); - STAssertEqualsWithAccuracy(point.x, 0.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)0.0, (CGFloat)0.01, nil); point = GTMNSMidRight(rect); cgPoint = GTMCGMidRight(cgRect); STAssertEquals(point.x, cgPoint.x, nil); STAssertEquals(point.y, cgPoint.y, nil); - STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil); - STAssertEqualsWithAccuracy(point.x, 2.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)2.0, (CGFloat)0.01, nil); point = GTMNSMidTop(rect); cgPoint = GTMCGMidTop(cgRect); STAssertEquals(point.x, cgPoint.x, nil); STAssertEquals(point.y, cgPoint.y, nil); - STAssertEqualsWithAccuracy(point.y, 2.0f, 0.01f, nil); - STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.y, (CGFloat)2.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); point = GTMNSMidBottom(rect); cgPoint = GTMCGMidBottom(cgRect); STAssertEquals(point.x, cgPoint.x, nil); STAssertEquals(point.y, cgPoint.y, nil); - STAssertEqualsWithAccuracy(point.y, 0.0f, 0.01f, nil); - STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.y, (CGFloat)0.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); } - (void)testGTMRectScaling { NSRect rect = NSMakeRect(1.0f, 2.0f, 5.0f, 10.0f); - NSRect rect2 = NSMakeRect(1.0f, 2.0f, 1.0f, 12.0f); - STAssertEquals(GTMNSRectScale(rect, 0.2f, 1.2f), + NSRect rect2 = NSMakeRect((CGFloat)1.0, (CGFloat)2.0, (CGFloat)1.0, (CGFloat)12.0); + STAssertEquals(GTMNSRectScale(rect, (CGFloat)0.2, (CGFloat)1.2), rect2, nil); - STAssertEquals(GTMCGRectScale(GTMNSRectToCGRect(rect), 0.2f, 1.2f), + STAssertEquals(GTMCGRectScale(GTMNSRectToCGRect(rect), (CGFloat)0.2, (CGFloat)1.2), GTMNSRectToCGRect(rect2), nil); } diff --git a/Foundation/GTMHTTPFetcher.h b/Foundation/GTMHTTPFetcher.h new file mode 100644 index 0000000..181283a --- /dev/null +++ b/Foundation/GTMHTTPFetcher.h @@ -0,0 +1,500 @@ +// +// GTMHTTPFetcher.h +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +// This is essentially a wrapper around NSURLConnection for POSTs and GETs. +// If setPostData: is called, then POST is assumed. +// +// When would you use this instead of NSURLConnection? +// +// - When you just want the result from a GET or POST +// - When you want the "standard" behavior for connections (redirection handling +// an so on) +// - When you want to avoid cookie collisions with Safari and other applications +// - When you want to provide if-modified-since headers +// - When you need to set a credential for the http +// - When you want to avoid changing WebKit's cookies +// +// This is assumed to be a one-shot fetch request; don't reuse the object +// for a second fetch. +// +// The fetcher may be created auto-released, in which case it will release +// itself after the fetch completion callback. The fetcher +// is implicitly retained as long as a connection is pending. +// +// But if you may need to cancel the fetcher, allocate it with initWithRequest: +// and have the delegate release the fetcher in the callbacks. +// +// Sample usage: +// +// NSURLRequest *request = [NSURLRequest requestWithURL:myURL]; +// GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher httpFetcherWithRequest:request]; +// +// [myFetcher setPostData:[postString dataUsingEncoding:NSUTF8StringEncoding]]; // for POSTs +// +// [myFetcher setCredential:[NSURLCredential authCredentialWithUsername:@"foo" +// password:@"bar"]]; // optional http credential +// +// [myFetcher setFetchHistory:myMutableDictionary]; // optional, for persisting modified-dates +// +// [myFetcher beginFetchWithDelegate:self +// didFinishSelector:@selector(myFetcher:finishedWithData:) +// didFailSelector:@selector(myFetcher:failedWithError:)]; +// +// Upon fetch completion, the callback selectors are invoked; they should have +// these signatures (you can use any callback method names you want so long as +// the signatures match these): +// +// - (void)myFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)retrievedData; +// - (void)myFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error; +// +// NOTE: Fetches may retrieve data from the server even though the server +// returned an error. The failWithError selector is called when the server +// status is >= 300 (along with any server-supplied data, usually +// some html explaining the error). +// Status codes are at <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html> +// +// +// Proxies: +// +// Proxy handling is invisible so long as the system has a valid credential in +// the keychain, which is normally true (else most NSURL-based apps would have +// difficulty.) But when there is a proxy authetication error, the the fetcher +// will call the failedWithError: method with the NSURLChallenge in the error's +// userInfo. The error method can get the challenge info like this: +// +// NSURLAuthenticationChallenge *challenge +// = [[error userInfo] objectForKey:kGTMHTTPFetcherErrorChallengeKey]; +// BOOL isProxyChallenge = [[challenge protectionSpace] isProxy]; +// +// If a proxy error occurs, you can ask the user for the proxy username/password +// and call fetcher's setProxyCredential: to provide those for the +// next attempt to fetch. +// +// +// Cookies: +// +// There are three supported mechanisms for remembering cookies between fetches. +// +// By default, GTMHTTPFetcher uses a mutable array held statically to track +// cookies for all instantiated fetchers. This avoids server cookies being set +// by servers for the application from interfering with Safari cookie settings, +// and vice versa. The fetcher cookies are lost when the application quits. +// +// To rely instead on WebKit's global NSHTTPCookieStorage, call +// setCookieStorageMethod: with kGTMHTTPFetcherCookieStorageMethodSystemDefault. +// +// If you provide a fetch history (such as for periodic checks, described +// below) then the cookie storage mechanism is set to use the fetch +// history rather than the static storage. +// +// +// Fetching for periodic checks: +// +// The fetcher object can track "Last-modified" dates on returned data and +// provide an "If-modified-since" header. This allows the server to save +// bandwidth by providing a "Nothing changed" status message instead of response +// data. +// +// To get this behavior, provide a persistent mutable dictionary to setFetchHistory:, +// and look for the failedWithError: callback with code 304 +// (kGTMHTTPFetcherStatusNotModified) like this: +// +// - (void)myFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error { +// if ([[error domain] isEqual:kGTMHTTPFetcherStatusDomain] && +// ([error code] == kGTMHTTPFetcherStatusNotModified)) { +// // [[error userInfo] objectForKey:kGTMHTTPFetcherStatusDataKey] is +// // empty; use the data from the previous finishedWithData: for this URL +// } else { +// // handle other server status code +// } +// } +// +// The fetchHistory mutable dictionary should be maintained by the client between +// fetches and given to each fetcher intended to have the If-modified-since header +// or the same cookie storage. +// +// +// Monitoring received data +// +// The optional received data selector should have the signature +// +// - (void)myFetcher:(GTMHTTPFetcher *)fetcher receivedData:(NSData *)dataReceivedSoFar; +// +// The bytes received so far are [dataReceivedSoFar length]. This number may go down +// if a redirect causes the download to begin again from a new server. +// If supplied by the server, the anticipated total download size is available as +// [[myFetcher response] expectedContentLength] (may be -1 for unknown +// download sizes.) +// +// +// Automatic retrying of fetches +// +// The fetcher can optionally create a timer and reattempt certain kinds of +// fetch failures (status codes 408, request timeout; 503, service unavailable; +// 504, gateway timeout; networking errors NSURLErrorTimedOut and +// NSURLErrorNetworkConnectionLost.) The user may set a retry selector to +// customize the type of errors which will be retried. +// +// Retries are done in an exponential-backoff fashion (that is, after 1 second, +// 2, 4, 8, and so on.) +// +// Enabling automatic retries looks like this: +// [myFetcher setIsRetryEnabled:YES]; +// +// With retries enabled, the success or failure callbacks are called only +// when no more retries will be attempted. Calling the fetcher's stopFetching +// method will terminate the retry timer, without the finished or failure +// selectors being invoked. +// +// Optionally, the client may set the maximum retry interval: +// [myFetcher setMaxRetryInterval:60.]; // in seconds; default is 600 seconds +// +// Also optionally, the client may provide a callback selector to determine +// if a status code or other error should be retried. +// [myFetcher setRetrySelector:@selector(myFetcher:willRetry:forError:)]; +// +// If set, the retry selector should have the signature: +// -(BOOL)fetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error +// and return YES to set the retry timer or NO to fail without additional +// fetch attempts. +// +// The retry method may return the |suggestedWillRetry| argument to get the +// default retry behavior. Server status codes are present in the error +// argument, and have the domain kGTMHTTPFetcherStatusDomain. The user's method +// may look something like this: +// +// -(BOOL)myFetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error { +// +// // perhaps examine [error domain] and [error code], or [fetcher retryCount] +// // +// // return YES to start the retry timer, NO to proceed to the failure +// // callback, or |suggestedWillRetry| to get default behavior for the +// // current error domain and code values. +// return suggestedWillRetry; +// } + + + +#pragma once + +#import <Foundation/Foundation.h> + +#import "GTMDefines.h" + +#undef _EXTERN +#undef _INITIALIZE_AS +#ifdef GTMHTTPFETCHER_DEFINE_GLOBALS +#define _EXTERN +#define _INITIALIZE_AS(x) =x +#else +#define _EXTERN extern +#define _INITIALIZE_AS(x) +#endif + +// notifications & errors +_EXTERN NSString* const kGTMHTTPFetcherErrorDomain _INITIALIZE_AS(@"com.google.GTMHTTPFetcher"); +_EXTERN NSString* const kGTMHTTPFetcherStatusDomain _INITIALIZE_AS(@"com.google.HTTPStatus"); +_EXTERN NSString* const kGTMHTTPFetcherErrorChallengeKey _INITIALIZE_AS(@"challenge"); +_EXTERN NSString* const kGTMHTTPFetcherStatusDataKey _INITIALIZE_AS(@"data"); // any data returns w/ a kGTMHTTPFetcherStatusDomain error + + +// fetch history mutable dictionary keys +_EXTERN NSString* const kGTMHTTPFetcherHistoryLastModifiedKey _INITIALIZE_AS(@"FetchHistoryLastModified"); +_EXTERN NSString* const kGTMHTTPFetcherHistoryDatedDataKey _INITIALIZE_AS(@"FetchHistoryDatedDataCache"); +_EXTERN NSString* const kGTMHTTPFetcherHistoryCookiesKey _INITIALIZE_AS(@"FetchHistoryCookies"); + +enum { + kGTMHTTPFetcherErrorDownloadFailed = -1, + kGTMHTTPFetcherErrorAuthenticationChallengeFailed = -2, + + kGTMHTTPFetcherStatusNotModified = 304 +}; + +enum { + kGTMHTTPFetcherCookieStorageMethodStatic = 0, + kGTMHTTPFetcherCookieStorageMethodFetchHistory = 1, + kGTMHTTPFetcherCookieStorageMethodSystemDefault = 2 +}; +typedef NSUInteger GTMHTTPFetcherCookieStorageMethod; + +/// async retrieval of an http get or post +@interface GTMHTTPFetcher : NSObject { + NSMutableURLRequest *request_; + NSURLConnection *connection_; // while connection_ is non-nil, delegate_ is retained + NSMutableData *downloadedData_; + NSURLCredential *credential_; // username & password + NSURLCredential *proxyCredential_; // credential supplied to proxy servers + NSData *postData_; + NSInputStream *postStream_; + NSMutableData *loggedStreamData_; + NSURLResponse *response_; // set in connection:didReceiveResponse: + id delegate_; // WEAK (though retained during an open connection) + SEL finishedSEL_; // should by implemented by delegate + SEL failedSEL_; // should be implemented by delegate + SEL receivedDataSEL_; // optional, set with setReceivedDataSelector + id userData_; // retained, if set by caller + NSArray *runLoopModes_; // optional, for 10.5 and later + NSMutableDictionary *fetchHistory_; // if supplied by the caller, used for Last-Modified-Since checks and cookies + BOOL shouldCacheDatedData_; // if true, remembers and returns data marked with a last-modified date + GTMHTTPFetcherCookieStorageMethod cookieStorageMethod_; // constant from above + + BOOL isRetryEnabled_; // user wants auto-retry + SEL retrySEL_; // optional; set with setRetrySelector + NSTimer *retryTimer_; + unsigned int retryCount_; + NSTimeInterval maxRetryInterval_; // default 600 seconds + NSTimeInterval minRetryInterval_; // random between 1 and 2 seconds + NSTimeInterval retryFactor_; // default interval multiplier is 2 + NSTimeInterval lastRetryInterval_; +} + +/// create a fetcher +// +// httpFetcherWithRequest will return an autoreleased fetcher, but if +// the connection is successfully created, the connection should retain the +// fetcher for the life of the connection as well. So the caller doesn't have +// to retain the fetcher explicitly unless they want to be able to cancel it. ++ (GTMHTTPFetcher *)httpFetcherWithRequest:(NSURLRequest *)request; + +// designated initializer +- (id)initWithRequest:(NSURLRequest *)request; + +- (NSMutableURLRequest *)request; +- (void)setRequest:(NSURLRequest *)theRequest; + +// setting the credential is optional; it is used if the connection receives +// an authentication challenge +- (NSURLCredential *)credential; +- (void)setCredential:(NSURLCredential *)theCredential; + +// setting the proxy credential is optional; it is used if the connection +// receives an authentication challenge from a proxy +- (NSURLCredential *)proxyCredential; +- (void)setProxyCredential:(NSURLCredential *)theCredential; + + +// if post data or stream is not set, then a GET retrieval method is assumed +- (NSData *)postData; +- (void)setPostData:(NSData *)theData; + +// beware: In 10.4, NSInputStream fails to copy or retain +// the data it was initialized with, contrary to docs. +// NOTE: if logging is enabled and GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING is +// 1, postStream will return a GTMProgressMonitorInputStream that wraps your +// stream (so the upload can be logged). +- (NSInputStream *)postStream; +- (void)setPostStream:(NSInputStream *)theStream; + +- (GTMHTTPFetcherCookieStorageMethod)cookieStorageMethod; +- (void)setCookieStorageMethod:(GTMHTTPFetcherCookieStorageMethod)method; + +// returns cookies from the currently appropriate cookie storage +- (NSArray *)cookiesForURL:(NSURL *)theURL; + +// the delegate is not retained except during the connection +- (id)delegate; +- (void)setDelegate:(id)theDelegate; + +// the delegate's optional receivedData selector has a signature like: +// - (void)myFetcher:(GTMHTTPFetcher *)fetcher receivedData:(NSData *)dataReceivedSoFar; +- (SEL)receivedDataSelector; +- (void)setReceivedDataSelector:(SEL)theSelector; + + +// retrying; see comments at the top of the file. Calling +// setIsRetryEnabled(YES) resets the min and max retry intervals. +- (BOOL)isRetryEnabled; +- (void)setIsRetryEnabled:(BOOL)flag; + +// retry selector is optional for retries. +// +// If present, it should have the signature: +// -(BOOL)fetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error +// and return YES to cause a retry. See comments at the top of this file. +- (SEL)retrySelector; +- (void)setRetrySelector:(SEL)theSel; + +// retry intervals must be strictly less than maxRetryInterval, else +// they will be limited to maxRetryInterval and no further retries will +// be attempted. Setting maxRetryInterval to 0.0 will reset it to the +// default value, 600 seconds. +- (NSTimeInterval)maxRetryInterval; +- (void)setMaxRetryInterval:(NSTimeInterval)secs; + +// Starting retry interval. Setting minRetryInterval to 0.0 will reset it +// to a random value between 1.0 and 2.0 seconds. Clients should normally not +// call this except for unit testing. +- (NSTimeInterval)minRetryInterval; +- (void)setMinRetryInterval:(NSTimeInterval)secs; + +// Multiplier used to increase the interval between retries, typically 2.0. +// Clients should not need to call this. +- (double)retryFactor; +- (void)setRetryFactor:(double)multiplier; + +// number of retries attempted +- (unsigned int)retryCount; + +// interval delay to precede next retry +- (NSTimeInterval)nextRetryInterval; + +/// Begin fetching the request. +// +/// |delegate| can optionally implement the two selectors |finishedSEL| and +/// |networkFailedSEL| or pass nil for them. +/// Returns YES if the fetch is initiated. Delegate is retained between +/// the beginFetch call until after the finish/fail callbacks. +// +// finishedSEL has a signature like: +// - (void)fetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data +// failedSEL has a signature like: +// - (void)fetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error +// + +- (BOOL)beginFetchWithDelegate:(id)delegate + didFinishSelector:(SEL)finishedSEL + didFailSelector:(SEL)networkFailedSEL; + +// Returns YES if this is in the process of fetching a URL +- (BOOL)isFetching; + +/// Cancel the fetch of the request that's currently in progress +- (void)stopFetching; + +/// return the status code from the server response +- (NSInteger)statusCode; + +/// the response, once it's been received +- (NSURLResponse *)response; +- (void)setResponse:(NSURLResponse *)response; + +// Fetch History is a useful, but a little complex at times... +// +// The caller should provide a mutable dictionary that can be used for storing +// Last-Modified-Since checks and cookie storage (setFetchHistory implicity +// calls setCookieStorageMethod w/ +// kGTMHTTPFetcherCookieStorageMethodFetchHistory if you passed a dictionary, +// kGTMHTTPFetcherCookieStorageMethodStatic if you passed nil. +// +// The caller can hold onto the dictionary to reuse the modification dates and +// cookies across multiple fetcher instances. +// +// With a fetch history dictionary setup, the http fetcher cache has the +// modification dates returned by the servers cached, and future fetches will +// return 304 to indicate the data hasn't changed since then (the data in the +// NSError object will be of length zero to show nothing was fetched). This +// reduces load on the server when the response data has not changed. See +// shouldCacheDatedData below for additional 304 support. +// +// Side effect: setFetchHistory: implicitly calls setCookieStorageMethod: +- (NSMutableDictionary *)fetchHistory; +- (void)setFetchHistory:(NSMutableDictionary *)fetchHistory; + +// For fetched data with a last-modified date, the fetcher can optionally cache +// the response data in the fetch history and return cached data instead of a +// 304 error. Set this to NO if you want to manually handle last-modified and +// status 304 (Not changed) rather than be delivered cached data from previous +// fetches. Default is NO. When a cache result is returned, the didFinish +// selector is called with the data, and [fetcher status] returns 200. +// +// If the caller has already provided a fetchHistory dictionary, they can also +// enable fetcher handling of 304 (not changed) status responses. By setting +// shouldCacheDatedData to YES, the fetcher will save any response that has a +// last modifed reply header into the fetchHistory. Then any future fetches +// using that same fetchHistory will automatically load the cached response and +// return it to the caller (with a status of 200) in place of the 304 server +// reply. +- (BOOL)shouldCacheDatedData; +- (void)setShouldCacheDatedData:(BOOL)flag; + +// Delete the last-modified dates and cached data from the fetch history. +- (void)clearDatedDataHistory; + +/// userData is retained for the convenience of the caller +- (id)userData; +- (void)setUserData:(id)theObj; + +// using the fetcher while a modal dialog is displayed requires setting the +// run-loop modes to include NSModalPanelRunLoopMode +// +// setting run loop modes does nothing if they are not supported, +// such as on 10.4 +- (NSArray *)runLoopModes; +- (void)setRunLoopModes:(NSArray *)modes; + ++ (BOOL)doesSupportRunLoopModes; ++ (NSArray *)defaultRunLoopModes; ++ (void)setDefaultRunLoopModes:(NSArray *)modes; + +// users who wish to replace GTMHTTPFetcher's use of NSURLConnection +// can do so globally here. The replacement should be a subclass of +// NSURLConnection. ++ (Class)connectionClass; ++ (void)setConnectionClass:(Class)theClass; + +@end + +// GTM HTTP Logging +// +// All traffic using GTMHTTPFetcher can be easily logged. Call +// +// [GTMHTTPFetcher setIsLoggingEnabled:YES]; +// +// to begin generating log files. +// +// Log files are put into a folder on the desktop called "GTMHTTPDebugLogs" +// unless another directory is specified with +setLoggingDirectory. +// +// Each run of an application gets a separate set of log files. An html +// file is generated to simplify browsing the run's http transactions. +// The html file includes javascript links for inline viewing of uploaded +// and downloaded data. +// +// A symlink is created in the logs folder to simplify finding the html file +// for the latest run of the application; the symlink is called +// +// AppName_http_log_newest.html +// +// For better viewing of XML logs, use Camino or Firefox rather than Safari. +// +// Projects may define GTM_HTTPFETCHER_ENABLE_LOGGING to 0 to remove all of the +// logging code (it defaults to 1). By default, any data uploaded via PUT/POST +// w/ and NSInputStream will not be logged. You can enable this logging by +// defining GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING to 1 (it defaults to 0). +// + +@interface GTMHTTPFetcher (GTMHTTPFetcherLogging) + +// Note: the default logs directory is ~/Desktop/GTMHTTPDebugLogs; it will be +// created as needed. If a custom directory is set, the directory should +// already exist. ++ (void)setLoggingDirectory:(NSString *)path; ++ (NSString *)loggingDirectory; + +// client apps can turn logging on and off ++ (void)setIsLoggingEnabled:(BOOL)flag; ++ (BOOL)isLoggingEnabled; + +// client apps can optionally specify process name and date string used in +// log file names ++ (void)setLoggingProcessName:(NSString *)str; ++ (NSString *)loggingProcessName; + ++ (void)setLoggingDateStamp:(NSString *)str; ++ (NSString *)loggingDateStamp; +@end diff --git a/Foundation/GTMHTTPFetcher.m b/Foundation/GTMHTTPFetcher.m new file mode 100644 index 0000000..9853d0d --- /dev/null +++ b/Foundation/GTMHTTPFetcher.m @@ -0,0 +1,1889 @@ +// +// GTMHTTPFetcher.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#define GTMHTTPFETCHER_DEFINE_GLOBALS 1 + +#import "GTMHTTPFetcher.h" +#import "GTMDebugSelectorValidation.h" + +@interface GTMHTTPFetcher (GTMHTTPFetcherLoggingInternal) +- (void)logFetchWithError:(NSError *)error; +- (void)logCapturePostStream; +@end + +// Make sure that if logging is disabled, the InputStream logging is also +// diabled. +#if !GTM_HTTPFETCHER_ENABLE_LOGGING +# undef GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING +# define GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING 0 +#endif // GTM_HTTPFETCHER_ENABLE_LOGGING + +#if GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING +#import "GTMProgressMonitorInputStream.h" +@interface GTMInputStreamLogger : GTMProgressMonitorInputStream +// GTMInputStreamLogger wraps any NSInputStream used for uploading so we can +// capture a copy of the data for the log +@end +#endif // !GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 +@interface NSURLConnection (LeopardMethodsOnTigerBuilds) +- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately; +- (void)start; +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +@end +#endif + +NSString* const kGTMLastModifiedHeader = @"Last-Modified"; +NSString* const kGTMIfModifiedSinceHeader = @"If-Modified-Since"; + + +NSMutableArray* gGTMFetcherStaticCookies = nil; +Class gGTMFetcherConnectionClass = nil; +NSArray *gGTMFetcherDefaultRunLoopModes = nil; + +const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes + +@interface GTMHTTPFetcher (PrivateMethods) +- (void)setCookies:(NSArray *)newCookies + inArray:(NSMutableArray *)cookieStorageArray; +- (NSArray *)cookiesForURL:(NSURL *)theURL inArray:(NSMutableArray *)cookieStorageArray; +- (void)handleCookiesForResponse:(NSURLResponse *)response; +- (BOOL)shouldRetryNowForStatus:(NSInteger)status error:(NSError *)error; +- (void)destroyRetryTimer; +- (void)beginRetryTimer; +- (void)primeTimerWithNewTimeInterval:(NSTimeInterval)secs; +- (void)retryFetch; +@end + +@implementation GTMHTTPFetcher + ++ (GTMHTTPFetcher *)httpFetcherWithRequest:(NSURLRequest *)request { + return [[[GTMHTTPFetcher alloc] initWithRequest:request] autorelease]; +} + ++ (void)initialize { + if (!gGTMFetcherStaticCookies) { + gGTMFetcherStaticCookies = [[NSMutableArray alloc] init]; + } +} + +- (id)init { + return [self initWithRequest:nil]; +} + +- (id)initWithRequest:(NSURLRequest *)request { + if ((self = [super init]) != nil) { + + request_ = [request mutableCopy]; + + [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic]; + } + return self; +} + +// TODO: do we need finalize to call stopFetching? + +- (void)dealloc { + [self stopFetching]; // releases connection_ + + [request_ release]; + [downloadedData_ release]; + [credential_ release]; + [proxyCredential_ release]; + [postData_ release]; + [postStream_ release]; + [loggedStreamData_ release]; + [response_ release]; + [userData_ release]; + [runLoopModes_ release]; + [fetchHistory_ release]; + [self destroyRetryTimer]; + + [super dealloc]; +} + +#pragma mark - + +// Begin fetching the URL. |delegate| is not retained +// The delegate must provide and implement the finished and failed selectors. +// +// finishedSEL has a signature like: +// - (void)fetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data +// failedSEL has a signature like: +// - (void)fetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error + +- (BOOL)beginFetchWithDelegate:(id)delegate + didFinishSelector:(SEL)finishedSEL + didFailSelector:(SEL)failedSEL { + + GTMAssertSelectorNilOrImplementedWithArguments(delegate, finishedSEL, @encode(GTMHTTPFetcher *), @encode(NSData *), NULL); + GTMAssertSelectorNilOrImplementedWithArguments(delegate, failedSEL, @encode(GTMHTTPFetcher *), @encode(NSError *), NULL); + GTMAssertSelectorNilOrImplementedWithArguments(delegate, receivedDataSEL_, @encode(GTMHTTPFetcher *), @encode(NSData *), NULL); + GTMAssertSelectorNilOrImplementedWithArguments(delegate, retrySEL_, @encode(GTMHTTPFetcher *), @encode(BOOL), @encode(NSError *), NULL); + + if (connection_ != nil) { + _GTMDevAssert(connection_ != nil, + @"fetch object %@ being reused; this should never happen", + self); + goto CannotBeginFetch; + } + + if (request_ == nil) { + _GTMDevAssert(request_ != nil, @"beginFetchWithDelegate requires a request"); + goto CannotBeginFetch; + } + + [downloadedData_ release]; + downloadedData_ = nil; + + [self setDelegate:delegate]; + finishedSEL_ = finishedSEL; + failedSEL_ = failedSEL; + + if (postData_ || postStream_) { + if ([request_ HTTPMethod] == nil || [[request_ HTTPMethod] isEqual:@"GET"]) { + [request_ setHTTPMethod:@"POST"]; + } + + if (postData_) { + [request_ setHTTPBody:postData_]; + } else { + + // if logging is enabled, it needs a buffer to accumulate data from any + // NSInputStream used for uploading. Logging will wrap the input + // stream with a stream that lets us keep a copy the data being read. + if ([GTMHTTPFetcher isLoggingEnabled] && postStream_ != nil) { + loggedStreamData_ = [[NSMutableData alloc] init]; + [self logCapturePostStream]; + } + + [request_ setHTTPBodyStream:postStream_]; + } + } + + if (fetchHistory_) { + + // If this URL is in the history, set the Last-Modified header field + + // if we have a history, we're tracking across fetches, so we don't + // want to pull results from a cache + [request_ setCachePolicy:NSURLRequestReloadIgnoringCacheData]; + + NSDictionary* lastModifiedDict = [fetchHistory_ objectForKey:kGTMHTTPFetcherHistoryLastModifiedKey]; + NSString* urlString = [[request_ URL] absoluteString]; + NSString* lastModifiedStr = [lastModifiedDict objectForKey:urlString]; + + // servers don't want last-modified-ifs on POSTs, so check for a body + if (lastModifiedStr + && [request_ HTTPBody] == nil + && [request_ HTTPBodyStream] == nil) { + + [request_ addValue:lastModifiedStr forHTTPHeaderField:kGTMIfModifiedSinceHeader]; + } + } + + // get cookies for this URL from our storage array, if + // we have a storage array + if (cookieStorageMethod_ != kGTMHTTPFetcherCookieStorageMethodSystemDefault) { + + NSArray *cookies = [self cookiesForURL:[request_ URL]]; + + if ([cookies count]) { + + NSDictionary *headerFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + NSString *cookieHeader = [headerFields objectForKey:@"Cookie"]; // key used in header dictionary + if (cookieHeader) { + [request_ addValue:cookieHeader forHTTPHeaderField:@"Cookie"]; // header name + } + } + } + + // finally, start the connection + + Class connectionClass = [[self class] connectionClass]; + + NSArray *runLoopModes = nil; + + if ([[self class] doesSupportRunLoopModes]) { + + // use the connection-specific run loop modes, if they were provided, + // or else use the GTMHTTPFetcher default run loop modes, if any + if (runLoopModes_) { + runLoopModes = runLoopModes_; + } else { + runLoopModes = gGTMFetcherDefaultRunLoopModes; + } + } + + if ([runLoopModes count] == 0) { + + // if no run loop modes were specified, then we'll start the connection + // on the current run loop in the current mode + connection_ = [[connectionClass connectionWithRequest:request_ + delegate:self] retain]; + } else { + + // schedule on current run loop in the specified modes + connection_ = [[connectionClass alloc] initWithRequest:request_ + delegate:self + startImmediately:NO]; + + for (int idx = 0; idx < [runLoopModes count]; idx++) { + NSString *mode = [runLoopModes objectAtIndex:idx]; + [connection_ scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:mode]; + } + [connection_ start]; + } + + if (!connection_) { + _GTMDevAssert(connection_ != nil, + @"beginFetchWithDelegate could not create a connection"); + goto CannotBeginFetch; + } + + // we'll retain the delegate only during the outstanding connection (similar + // to what Cocoa does with performSelectorOnMainThread:) since we'd crash + // if the delegate was released in the interim. We don't retain the selector + // at other times, to avoid vicious retain loops. This retain is balanced in + // the -stopFetch method. + [delegate_ retain]; + + downloadedData_ = [[NSMutableData alloc] init]; + return YES; + +CannotBeginFetch: + + if (failedSEL) { + + NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain + code:kGTMHTTPFetcherErrorDownloadFailed + userInfo:nil]; + + [[self retain] autorelease]; // in case the callback releases us + + [delegate performSelector:failedSEL_ + withObject:self + withObject:error]; + } + + return NO; +} + +// Returns YES if this is in the process of fetching a URL, or waiting to +// retry +- (BOOL)isFetching { + return (connection_ != nil || retryTimer_ != nil); +} + +// Returns the status code set in connection:didReceiveResponse: +- (NSInteger)statusCode { + + NSInteger statusCode; + + if (response_ != nil + && [response_ respondsToSelector:@selector(statusCode)]) { + + statusCode = [(NSHTTPURLResponse *)response_ statusCode]; + } else { + // Default to zero, in hopes of hinting "Unknown" (we can't be + // sure that things are OK enough to use 200). + statusCode = 0; + } + return statusCode; +} + +// Cancel the fetch of the URL that's currently in progress. +- (void)stopFetching { + [self destroyRetryTimer]; + + if (connection_) { + // in case cancelling the connection calls this recursively, we want + // to ensure that we'll only release the connection and delegate once, + // so first set connection_ to nil + + NSURLConnection* oldConnection = connection_; + connection_ = nil; + + // this may be called in a callback from the connection, so use autorelease + [oldConnection cancel]; + [oldConnection autorelease]; + + // balance the retain done when the connection was opened + [delegate_ release]; + } +} + +- (void)retryFetch { + + id holdDelegate = [[delegate_ retain] autorelease]; + + [self stopFetching]; + + [self beginFetchWithDelegate:holdDelegate + didFinishSelector:finishedSEL_ + didFailSelector:failedSEL_]; +} + +#pragma mark NSURLConnection Delegate Methods + +// +// NSURLConnection Delegate Methods +// + +// This method just says "follow all redirects", which _should_ be the default behavior, +// According to file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Conceptual/URLLoadingSystem +// but the redirects were not being followed until I added this method. May be +// a bug in the NSURLConnection code, or the documentation. +// +// In OS X 10.4.8 and earlier, the redirect request doesn't +// get the original's headers and body. This causes POSTs to fail. +// So we construct a new request, a copy of the original, with overrides from the +// redirect. +// +// Docs say that if redirectResponse is nil, just return the redirectRequest. + +- (NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)redirectRequest + redirectResponse:(NSURLResponse *)redirectResponse { + + if (redirectRequest && redirectResponse) { + NSMutableURLRequest *newRequest = [[request_ mutableCopy] autorelease]; + // copy the URL + NSURL *redirectURL = [redirectRequest URL]; + NSURL *url = [newRequest URL]; + + // disallow scheme changes (say, from https to http) + NSString *redirectScheme = [url scheme]; + NSString *newScheme = [redirectURL scheme]; + NSString *newResourceSpecifier = [redirectURL resourceSpecifier]; + + if ([redirectScheme caseInsensitiveCompare:@"http"] == NSOrderedSame + && newScheme != nil + && [newScheme caseInsensitiveCompare:@"https"] == NSOrderedSame) { + + // allow the change from http to https + redirectScheme = newScheme; + } + + NSString *newUrlString = [NSString stringWithFormat:@"%@:%@", + redirectScheme, newResourceSpecifier]; + + NSURL *newURL = [NSURL URLWithString:newUrlString]; + [newRequest setURL:newURL]; + + // any headers in the redirect override headers in the original. + NSDictionary *redirectHeaders = [redirectRequest allHTTPHeaderFields]; + if (redirectHeaders) { + NSEnumerator *enumerator = [redirectHeaders keyEnumerator]; + NSString *key; + while (nil != (key = [enumerator nextObject])) { + NSString *value = [redirectHeaders objectForKey:key]; + [newRequest setValue:value forHTTPHeaderField:key]; + } + } + redirectRequest = newRequest; + + // save cookies from the response + [self handleCookiesForResponse:redirectResponse]; + + // log the response we just received + [self setResponse:redirectResponse]; + [self logFetchWithError:nil]; + + // update the request for future logging + [self setRequest:redirectRequest]; +} + return redirectRequest; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + + // this method is called when the server has determined that it + // has enough information to create the NSURLResponse + // it can be called multiple times, for example in the case of a + // redirect, so each time we reset the data. + [downloadedData_ setLength:0]; + + [self setResponse:response]; + + // save cookies from the response + [self handleCookiesForResponse:response]; +} + + +// handleCookiesForResponse: handles storage of cookies for responses passed to +// connection:willSendRequest:redirectResponse: and connection:didReceiveResponse: +- (void)handleCookiesForResponse:(NSURLResponse *)response { + + if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodSystemDefault) { + + // do nothing special for NSURLConnection's default storage mechanism + + } else if ([response respondsToSelector:@selector(allHeaderFields)]) { + + // grab the cookies from the header as NSHTTPCookies and store them either + // into our static array or into the fetchHistory + + NSDictionary *responseHeaderFields = [(NSHTTPURLResponse *)response allHeaderFields]; + if (responseHeaderFields) { + + NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseHeaderFields + forURL:[response URL]]; + if ([cookies count] > 0) { + + NSMutableArray *cookieArray = nil; + + // static cookies are stored in gGTMFetcherStaticCookies; fetchHistory + // cookies are stored in fetchHistory_'s kGTMHTTPFetcherHistoryCookiesKey + + if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodStatic) { + + cookieArray = gGTMFetcherStaticCookies; + + } else if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodFetchHistory + && fetchHistory_ != nil) { + + cookieArray = [fetchHistory_ objectForKey:kGTMHTTPFetcherHistoryCookiesKey]; + if (cookieArray == nil) { + cookieArray = [NSMutableArray array]; + [fetchHistory_ setObject:cookieArray forKey:kGTMHTTPFetcherHistoryCookiesKey]; + } + } + + if (cookieArray) { + @synchronized(cookieArray) { + [self setCookies:cookies inArray:cookieArray]; + } + } + } + } + } +} + +-(void)connection:(NSURLConnection *)connection + didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + + if ([challenge previousFailureCount] <= 2) { + + NSURLCredential *credential = credential_; + + if ([[challenge protectionSpace] isProxy] && proxyCredential_ != nil) { + credential = proxyCredential_; + } + + // Here, if credential is still nil, then we *could* try to get it from + // NSURLCredentialStorage's defaultCredentialForProtectionSpace:. + // We don't, because we're assuming: + // + // - for server credentials, we only want ones supplied by the program + // calling http fetcher + // - for proxy credentials, if one were necessary and available in the + // keychain, it would've been found automatically by NSURLConnection + // and this challenge delegate method never would've been called + // anyway + + if (credential) { + // try the credential + [[challenge sender] useCredential:credential + forAuthenticationChallenge:challenge]; + return; + } + } + + // If we don't have credentials, or we've already failed auth 3x... + [[challenge sender] cancelAuthenticationChallenge:challenge]; + + // report the error, putting the challenge as a value in the userInfo + // dictionary + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:challenge + forKey:kGTMHTTPFetcherErrorChallengeKey]; + + NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain + code:kGTMHTTPFetcherErrorAuthenticationChallengeFailed + userInfo:userInfo]; + + [self connection:connection didFailWithError:error]; +} + + + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + [downloadedData_ appendData:data]; + + if (receivedDataSEL_) { + [delegate_ performSelector:receivedDataSEL_ + withObject:self + withObject:downloadedData_]; + } +} + +- (void)updateFetchHistory { + + if (fetchHistory_) { + + NSString* urlString = [[request_ URL] absoluteString]; + if ([response_ respondsToSelector:@selector(allHeaderFields)]) { + NSDictionary *headers = [(NSHTTPURLResponse *)response_ allHeaderFields]; + NSString* lastModifiedStr = [headers objectForKey:kGTMLastModifiedHeader]; + + // get the dictionary mapping URLs to last-modified dates + NSMutableDictionary* lastModifiedDict = [fetchHistory_ objectForKey:kGTMHTTPFetcherHistoryLastModifiedKey]; + if (!lastModifiedDict) { + lastModifiedDict = [NSMutableDictionary dictionary]; + [fetchHistory_ setObject:lastModifiedDict forKey:kGTMHTTPFetcherHistoryLastModifiedKey]; + } + + NSMutableDictionary* datedDataCache = nil; + if (shouldCacheDatedData_) { + // get the dictionary mapping URLs to cached, dated data + datedDataCache = [fetchHistory_ objectForKey:kGTMHTTPFetcherHistoryDatedDataKey]; + if (!datedDataCache) { + datedDataCache = [NSMutableDictionary dictionary]; + [fetchHistory_ setObject:datedDataCache forKey:kGTMHTTPFetcherHistoryDatedDataKey]; + } + } + + NSInteger statusCode = [self statusCode]; + if (statusCode != kGTMHTTPFetcherStatusNotModified) { + + // save this last modified date string for successful results (<300) + // If there's no last modified string, clear the dictionary + // entry for this URL. Also cache or delete the data, if appropriate + // (when datedDataCache is non-nil.) + if (lastModifiedStr && statusCode < 300) { + [lastModifiedDict setValue:lastModifiedStr forKey:urlString]; + [datedDataCache setValue:downloadedData_ forKey:urlString]; + } else { + [lastModifiedDict removeObjectForKey:urlString]; + [datedDataCache removeObjectForKey:urlString]; + } + } + } + } +} + +// for error 304's ("Not Modified") where we've cached the data, return status +// 200 ("OK") to the caller (but leave the fetcher status as 304) +// and copy the cached data to downloadedData_. +// For other errors or if there's no cached data, just return the actual status. +- (NSInteger)statusAfterHandlingNotModifiedError { + + NSInteger status = [self statusCode]; + if (status == kGTMHTTPFetcherStatusNotModified && shouldCacheDatedData_) { + + // get the dictionary of URLs and data + NSString* urlString = [[request_ URL] absoluteString]; + + NSDictionary* datedDataCache = [fetchHistory_ objectForKey:kGTMHTTPFetcherHistoryDatedDataKey]; + NSData* cachedData = [datedDataCache objectForKey:urlString]; + + if (cachedData) { + // copy our stored data, and forge the status to pass on to the delegate + [downloadedData_ setData:cachedData]; + status = 200; + } + } + return status; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + + [self updateFetchHistory]; + + [[self retain] autorelease]; // in case the callback releases us + + [self logFetchWithError:nil]; + + NSInteger status = [self statusAfterHandlingNotModifiedError]; + + if (status >= 300) { + + if ([self shouldRetryNowForStatus:status error:nil]) { + + [self beginRetryTimer]; + + } else { + // not retrying + + // did they want failure notifications? + if (failedSEL_) { + + NSDictionary *userInfo = + [NSDictionary dictionaryWithObject:downloadedData_ + forKey:kGTMHTTPFetcherStatusDataKey]; + NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain + code:status + userInfo:userInfo]; + + [delegate_ performSelector:failedSEL_ + withObject:self + withObject:error]; + } + // we're done fetching + [self stopFetching]; + } + + } else if (finishedSEL_) { + + // successful http status (under 300) + [delegate_ performSelector:finishedSEL_ + withObject:self + withObject:downloadedData_]; + [self stopFetching]; + } + +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + + [self logFetchWithError:error]; + + if ([self shouldRetryNowForStatus:0 error:error]) { + + [self beginRetryTimer]; + + } else { + + if (failedSEL_) { + [[self retain] autorelease]; // in case the callback releases us + + [delegate_ performSelector:failedSEL_ + withObject:self + withObject:error]; + } + + [self stopFetching]; + } +} + +#pragma mark Retries + +- (BOOL)isRetryError:(NSError *)error { + + struct retryRecord { + NSString *const domain; + int code; + }; + + struct retryRecord retries[] = { + { kGTMHTTPFetcherStatusDomain, 408 }, // request timeout + { kGTMHTTPFetcherStatusDomain, 503 }, // service unavailable + { kGTMHTTPFetcherStatusDomain, 504 }, // request timeout + { NSURLErrorDomain, NSURLErrorTimedOut }, + { NSURLErrorDomain, NSURLErrorNetworkConnectionLost }, + { nil, 0 } + }; + + // NSError's isEqual always returns false for equal but distinct instances + // of NSError, so we have to compare the domain and code values explicitly + + for (int idx = 0; retries[idx].domain != nil; idx++) { + + if ([[error domain] isEqual:retries[idx].domain] + && [error code] == retries[idx].code) { + + return YES; + } + } + return NO; +} + + +// shouldRetryNowForStatus:error: returns YES if the user has enabled retries +// and the status or error is one that is suitable for retrying. "Suitable" +// means either the isRetryError:'s list contains the status or error, or the +// user's retrySelector: is present and returns YES when called. +- (BOOL)shouldRetryNowForStatus:(NSInteger)status + error:(NSError *)error { + + if ([self isRetryEnabled]) { + + if ([self nextRetryInterval] < [self maxRetryInterval]) { + + if (error == nil) { + // make an error for the status + error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain + code:status + userInfo:nil]; + } + + BOOL willRetry = [self isRetryError:error]; + + if (retrySEL_) { + NSMethodSignature *signature = [delegate_ methodSignatureForSelector:retrySEL_]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setSelector:retrySEL_]; + [invocation setTarget:delegate_]; + [invocation setArgument:&self atIndex:2]; + [invocation setArgument:&willRetry atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + + [invocation getReturnValue:&willRetry]; + } + + return willRetry; + } + } + + return NO; +} + +- (void)beginRetryTimer { + + NSTimeInterval nextInterval = [self nextRetryInterval]; + NSTimeInterval maxInterval = [self maxRetryInterval]; + + NSTimeInterval newInterval = MIN(nextInterval, maxInterval); + + [self primeTimerWithNewTimeInterval:newInterval]; +} + +- (void)primeTimerWithNewTimeInterval:(NSTimeInterval)secs { + + [self destroyRetryTimer]; + + lastRetryInterval_ = secs; + + retryTimer_ = [NSTimer scheduledTimerWithTimeInterval:secs + target:self + selector:@selector(retryTimerFired:) + userInfo:nil + repeats:NO]; + [retryTimer_ retain]; +} + +- (void)retryTimerFired:(NSTimer *)timer { + + [self destroyRetryTimer]; + + retryCount_++; + + [self retryFetch]; +} + +- (void)destroyRetryTimer { + + [retryTimer_ invalidate]; + [retryTimer_ autorelease]; + retryTimer_ = nil; +} + +- (unsigned int)retryCount { + return retryCount_; +} + +- (NSTimeInterval)nextRetryInterval { + // the next wait interval is the factor (2.0) times the last interval, + // but never less than the minimum interval + NSTimeInterval secs = lastRetryInterval_ * retryFactor_; + secs = MIN(secs, maxRetryInterval_); + secs = MAX(secs, minRetryInterval_); + + return secs; +} + +- (BOOL)isRetryEnabled { + return isRetryEnabled_; +} + +- (void)setIsRetryEnabled:(BOOL)flag { + + if (flag && !isRetryEnabled_) { + // We defer initializing these until the user calls setIsRetryEnabled + // to avoid seeding the random number generator if it's not needed. + // However, it means min and max intervals for this fetcher are reset + // as a side effect of calling setIsRetryEnabled. + // + // seed the random value, and make an initial retry interval + // random between 1.0 and 2.0 seconds + srandomdev(); + [self setMinRetryInterval:0.0]; + [self setMaxRetryInterval:kDefaultMaxRetryInterval]; + [self setRetryFactor:2.0]; + lastRetryInterval_ = 0.0; + } + isRetryEnabled_ = flag; +}; + +- (SEL)retrySelector { + return retrySEL_; +} + +- (void)setRetrySelector:(SEL)theSelector { + retrySEL_ = theSelector; +} + +- (NSTimeInterval)maxRetryInterval { + return maxRetryInterval_; +} + +- (void)setMaxRetryInterval:(NSTimeInterval)secs { + if (secs > 0) { + maxRetryInterval_ = secs; + } else { + maxRetryInterval_ = kDefaultMaxRetryInterval; + } +} + +- (double)minRetryInterval { + return minRetryInterval_; +} + +- (void)setMinRetryInterval:(NSTimeInterval)secs { + if (secs > 0) { + minRetryInterval_ = secs; + } else { + // set min interval to a random value between 1.0 and 2.0 seconds + // so that if multiple clients start retrying at the same time, they'll + // repeat at different times and avoid overloading the server + minRetryInterval_ = 1.0 + ((double)(random() & 0x0FFFF) / (double) 0x0FFFF); + } +} + +- (double)retryFactor { + return retryFactor_; +} + +- (void)setRetryFactor:(double)multiplier { + retryFactor_ = multiplier; +} + +#pragma mark Getters and Setters + +- (NSMutableURLRequest *)request { + return request_; +} + +- (void)setRequest:(NSURLRequest *)theRequest { + [request_ autorelease]; + request_ = [theRequest mutableCopy]; +} + +- (NSURLCredential *)credential { + return credential_; +} + +- (void)setCredential:(NSURLCredential *)theCredential { + [credential_ autorelease]; + credential_ = [theCredential retain]; +} + +- (NSURLCredential *)proxyCredential { + return proxyCredential_; +} + +- (void)setProxyCredential:(NSURLCredential *)theCredential { + [proxyCredential_ autorelease]; + proxyCredential_ = [theCredential retain]; +} + +- (NSData *)postData { + return postData_; +} + +- (void)setPostData:(NSData *)theData { + [postData_ autorelease]; + postData_ = [theData retain]; +} + +- (NSInputStream *)postStream { + return postStream_; +} + +- (void)setPostStream:(NSInputStream *)theStream { + [postStream_ autorelease]; + postStream_ = [theStream retain]; +} + +- (GTMHTTPFetcherCookieStorageMethod)cookieStorageMethod { + return cookieStorageMethod_; +} + +- (void)setCookieStorageMethod:(GTMHTTPFetcherCookieStorageMethod)method { + + cookieStorageMethod_ = method; + + if (method == kGTMHTTPFetcherCookieStorageMethodSystemDefault) { + [request_ setHTTPShouldHandleCookies:YES]; + } else { + [request_ setHTTPShouldHandleCookies:NO]; + } +} + +- (id)delegate { + return delegate_; +} + +- (void)setDelegate:(id)theDelegate { + + // we retain delegate_ only during the life of the connection + if (connection_) { + [delegate_ autorelease]; + delegate_ = [theDelegate retain]; + } else { + delegate_ = theDelegate; + } +} + +- (SEL)receivedDataSelector { + return receivedDataSEL_; +} + +- (void)setReceivedDataSelector:(SEL)theSelector { + receivedDataSEL_ = theSelector; +} + +- (NSURLResponse *)response { + return response_; +} + +- (void)setResponse:(NSURLResponse *)response { + [response_ autorelease]; + response_ = [response retain]; +} + +- (NSMutableDictionary *)fetchHistory { + return fetchHistory_; +} + +- (void)setFetchHistory:(NSMutableDictionary *)fetchHistory { + [fetchHistory_ autorelease]; + fetchHistory_ = [fetchHistory retain]; + + if (fetchHistory_ != nil) { + [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodFetchHistory]; + } else { + [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic]; + } +} + +- (void)setShouldCacheDatedData:(BOOL)flag { + shouldCacheDatedData_ = flag; + if (!flag) { + [self clearDatedDataHistory]; + } +} + +- (BOOL)shouldCacheDatedData { + return shouldCacheDatedData_; +} + +// delete last-modified dates and cached data from the fetch history +- (void)clearDatedDataHistory { + [fetchHistory_ removeObjectForKey:kGTMHTTPFetcherHistoryLastModifiedKey]; + [fetchHistory_ removeObjectForKey:kGTMHTTPFetcherHistoryDatedDataKey]; +} + +- (id)userData { + return userData_; +} + +- (void)setUserData:(id)theObj { + [userData_ autorelease]; + userData_ = [theObj retain]; +} + +- (NSArray *)runLoopModes { + return runLoopModes_; +} + +- (void)setRunLoopModes:(NSArray *)modes { + [runLoopModes_ autorelease]; + runLoopModes_ = [modes retain]; +} + ++ (BOOL)doesSupportRunLoopModes { + SEL sel = @selector(initWithRequest:delegate:startImmediately:); + return [NSURLConnection instancesRespondToSelector:sel]; +} + ++ (NSArray *)defaultRunLoopModes { + return gGTMFetcherDefaultRunLoopModes; +} + ++ (void)setDefaultRunLoopModes:(NSArray *)modes { + [gGTMFetcherDefaultRunLoopModes autorelease]; + gGTMFetcherDefaultRunLoopModes = [modes retain]; +} + ++ (Class)connectionClass { + if (gGTMFetcherConnectionClass == nil) { + gGTMFetcherConnectionClass = [NSURLConnection class]; + } + return gGTMFetcherConnectionClass; +} + ++ (void)setConnectionClass:(Class)theClass { + gGTMFetcherConnectionClass = theClass; +} + +#pragma mark Cookies + +// return a cookie from the array with the same name, domain, and path as the +// given cookie, or else return nil if none found +// +// Both the cookie being tested and all cookies in cookieStorageArray should +// be valid (non-nil name, domains, paths) +- (NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie + inArray:(NSArray *)cookieStorageArray { + + NSUInteger numberOfCookies = [cookieStorageArray count]; + NSString *name = [cookie name]; + NSString *domain = [cookie domain]; + NSString *path = [cookie path]; + + _GTMDevAssert(name && domain && path, + @"Invalid cookie (name:%@ domain:%@ path:%@)", + name, domain, path); + + for (NSUInteger idx = 0; idx < numberOfCookies; idx++) { + + NSHTTPCookie *storedCookie = [cookieStorageArray objectAtIndex:idx]; + + if ([[storedCookie name] isEqual:name] + && [[storedCookie domain] isEqual:domain] + && [[storedCookie path] isEqual:path]) { + + return storedCookie; + } + } + return nil; +} + +// remove any expired cookies from the array, excluding cookies with nil +// expirations +- (void)removeExpiredCookiesInArray:(NSMutableArray *)cookieStorageArray { + + // count backwards since we're deleting items from the array + for (NSInteger idx = [cookieStorageArray count] - 1; idx >= 0; idx--) { + + NSHTTPCookie *storedCookie = [cookieStorageArray objectAtIndex:idx]; + + NSDate *expiresDate = [storedCookie expiresDate]; + if (expiresDate && [expiresDate timeIntervalSinceNow] < 0) { + [cookieStorageArray removeObjectAtIndex:idx]; + } + } +} + + +// retrieve all cookies appropriate for the given URL, considering +// domain, path, cookie name, expiration, security setting. +// Side effect: removed expired cookies from the storage array +- (NSArray *)cookiesForURL:(NSURL *)theURL inArray:(NSMutableArray *)cookieStorageArray { + + [self removeExpiredCookiesInArray:cookieStorageArray]; + + NSMutableArray *foundCookies = [NSMutableArray array]; + + // we'll prepend "." to the desired domain, since we want the + // actual domain "nytimes.com" to still match the cookie domain ".nytimes.com" + // when we check it below with hasSuffix + NSString *host = [theURL host]; + NSString *path = [theURL path]; + NSString *scheme = [theURL scheme]; + + NSString *domain = nil; + if ([host isEqual:@"localhost"]) { + // the domain stored into NSHTTPCookies for localhost is "localhost.local" + domain = @"localhost.local"; + } else { + if (host) { + domain = [@"." stringByAppendingString:host]; + } + } + + NSUInteger numberOfCookies = [cookieStorageArray count]; + for (NSUInteger idx = 0; idx < numberOfCookies; idx++) { + + NSHTTPCookie *storedCookie = [cookieStorageArray objectAtIndex:idx]; + + NSString *cookieDomain = [storedCookie domain]; + NSString *cookiePath = [storedCookie path]; + BOOL cookieIsSecure = [storedCookie isSecure]; + + BOOL domainIsOK = [domain hasSuffix:cookieDomain]; + BOOL pathIsOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath]; + BOOL secureIsOK = (!cookieIsSecure) || [scheme isEqual:@"https"]; + + if (domainIsOK && pathIsOK && secureIsOK) { + [foundCookies addObject:storedCookie]; + } + } + return foundCookies; +} + +// return cookies for the given URL using the current cookie storage method +- (NSArray *)cookiesForURL:(NSURL *)theURL { + + NSArray *cookies = nil; + NSMutableArray *cookieStorageArray = nil; + + if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodStatic) { + cookieStorageArray = gGTMFetcherStaticCookies; + } else if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodFetchHistory) { + cookieStorageArray = [fetchHistory_ objectForKey:kGTMHTTPFetcherHistoryCookiesKey]; + } else { + // kGTMHTTPFetcherCookieStorageMethodSystemDefault + cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:theURL]; + } + + if (cookieStorageArray) { + + @synchronized(cookieStorageArray) { + + // cookiesForURL returns a new array of immutable NSCookie objects + // from cookieStorageArray + cookies = [self cookiesForURL:theURL + inArray:cookieStorageArray]; + } + } + return cookies; +} + + +// add all cookies in the array |newCookies| to the storage array, +// replacing cookies in the storage array as appropriate +// Side effect: removes expired cookies from the storage array +- (void)setCookies:(NSArray *)newCookies + inArray:(NSMutableArray *)cookieStorageArray { + + [self removeExpiredCookiesInArray:cookieStorageArray]; + + NSEnumerator *newCookieEnum = [newCookies objectEnumerator]; + NSHTTPCookie *newCookie; + + while ((newCookie = [newCookieEnum nextObject]) != nil) { + + if ([[newCookie name] length] > 0 + && [[newCookie domain] length] > 0 + && [[newCookie path] length] > 0) { + + // remove the cookie if it's currently in the array + NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie + inArray:cookieStorageArray]; + if (oldCookie) { + [cookieStorageArray removeObject:oldCookie]; + } + + // make sure the cookie hasn't already expired + NSDate *expiresDate = [newCookie expiresDate]; + if ((!expiresDate) || [expiresDate timeIntervalSinceNow] > 0) { + [cookieStorageArray addObject:newCookie]; + } + + } else { + _GTMDevAssert(NO, @"Cookie incomplete: %@", newCookie); + } + } +} +@end + +#pragma mark Logging + +// NOTE: Threads and Logging +// +// All the NSURLConnection callbacks happen on one thread, so we don't have +// to put any synchronization into the logging code. Yes, the state around +// logging (it's directory, etc.) could use it, but for now, that's punted. + + +// We don't invoke Leopard methods on 10.4, because we check if the methods are +// implemented before invoking it, but we need to be able to compile without +// warnings. +// This declaration means if you target <=10.4, this method will compile +// without complaint in this source, so you must test with +// -respondsToSelector:, too. +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 +@interface NSFileManager (LeopardMethodsOnTigerBuilds) +- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error; +@end +#endif +// The iPhone Foundation removes the deprecated removeFileAtPath:handler: +#if GTM_IPHONE_SDK +@interface NSFileManager (TigerMethodsOniPhoneBuilds) +- (BOOL)removeFileAtPath:(NSString *)path handler:(id)handler; +@end +#endif + +@implementation GTMHTTPFetcher (GTMHTTPFetcherLogging) + +// if GTM_HTTPFETCHER_ENABLE_LOGGING is defined by the user's project then +// logging code will be compiled into the framework + +#if !GTM_HTTPFETCHER_ENABLE_LOGGING +- (void)logFetchWithError:(NSError *)error {} + ++ (void)setLoggingDirectory:(NSString *)path {} ++ (NSString *)loggingDirectory {return nil;} + ++ (void)setIsLoggingEnabled:(BOOL)flag {} ++ (BOOL)isLoggingEnabled {return NO;} + ++ (void)setLoggingProcessName:(NSString *)str {} ++ (NSString *)loggingProcessName {return nil;} + ++ (void)setLoggingDateStamp:(NSString *)str {} ++ (NSString *)loggingDateStamp {return nil;} + +- (void)appendLoggedStreamData:(NSData *)newData {} +- (void)logCapturePostStream {} +#else // GTM_HTTPFETCHER_ENABLE_LOGGING + +// fetchers come and fetchers go, but statics are forever +static BOOL gIsLoggingEnabled = NO; +static NSString *gLoggingDirectoryPath = nil; +static NSString *gLoggingDateStamp = nil; +static NSString* gLoggingProcessName = nil; + ++ (void)setLoggingDirectory:(NSString *)path { + [gLoggingDirectoryPath autorelease]; + gLoggingDirectoryPath = [path copy]; +} + ++ (NSString *)loggingDirectory { + + if (!gLoggingDirectoryPath) { + +#if GTM_IPHONE_SDK + // default to a directory called GTMHTTPDebugLogs into a sandbox-safe + // directory that a devloper can find easily, the application home + NSArray *arr = [NSArray arrayWithObject:NSHomeDirectory()]; +#else + // default to a directory called GTMHTTPDebugLogs in the desktop folder + NSArray *arr = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, + NSUserDomainMask, YES); +#endif + + if ([arr count] > 0) { + NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs"; + + NSString *desktopPath = [arr objectAtIndex:0]; + NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName]; + + BOOL doesFolderExist; + BOOL isDir = NO; + NSFileManager *fileManager = [NSFileManager defaultManager]; + doesFolderExist = [fileManager fileExistsAtPath:logsFolderPath + isDirectory:&isDir]; + + if (!doesFolderExist) { + // make the directory + doesFolderExist = [fileManager createDirectoryAtPath:logsFolderPath + attributes:nil]; + } + + if (doesFolderExist) { + // it's there; store it in the global + gLoggingDirectoryPath = [logsFolderPath copy]; + } + } + } + return gLoggingDirectoryPath; +} + ++ (void)setIsLoggingEnabled:(BOOL)flag { + gIsLoggingEnabled = flag; +} + ++ (BOOL)isLoggingEnabled { + return gIsLoggingEnabled; +} + ++ (void)setLoggingProcessName:(NSString *)str { + [gLoggingProcessName release]; + gLoggingProcessName = [str copy]; +} + ++ (NSString *)loggingProcessName { + + // get the process name (once per run) replacing spaces with underscores + if (!gLoggingProcessName) { + + NSString *procName = [[NSProcessInfo processInfo] processName]; + NSMutableString *loggingProcessName; + loggingProcessName = [[NSMutableString alloc] initWithString:procName]; + + [loggingProcessName replaceOccurrencesOfString:@" " + withString:@"_" + options:0 + range:NSMakeRange(0, [gLoggingProcessName length])]; + gLoggingProcessName = loggingProcessName; + } + return gLoggingProcessName; +} + ++ (void)setLoggingDateStamp:(NSString *)str { + [gLoggingDateStamp release]; + gLoggingDateStamp = [str copy]; +} + ++ (NSString *)loggingDateStamp { + // we'll pick one date stamp per run, so a run that starts at a later second + // will get a unique results html file + if (!gLoggingDateStamp) { + // produce a string like 08-21_01-41-23PM + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:@"M-dd_hh-mm-ssa"]; + + gLoggingDateStamp = [[formatter stringFromDate:[NSDate date]] retain] ; + } + return gLoggingDateStamp; +} + +- (NSString *)cleanParameterFollowing:(NSString *)paramName + fromString:(NSString *)originalStr { + // We don't want the password written to disk + // + // find "&Passwd=" in the string, and replace it and the stuff that + // follows it with "Passwd=_snip_" + + NSRange passwdRange = [originalStr rangeOfString:@"&Passwd="]; + if (passwdRange.location != NSNotFound) { + + // we found Passwd=; find the & that follows the parameter + NSUInteger origLength = [originalStr length]; + NSRange restOfString = NSMakeRange(passwdRange.location+1, + origLength - passwdRange.location - 1); + NSRange rangeOfFollowingAmp = [originalStr rangeOfString:@"&" + options:0 + range:restOfString]; + NSRange replaceRange; + if (rangeOfFollowingAmp.location == NSNotFound) { + // found no other & so replace to end of string + replaceRange = NSMakeRange(passwdRange.location, + rangeOfFollowingAmp.location - passwdRange.location); + } else { + // another parameter after &Passwd=foo + replaceRange = NSMakeRange(passwdRange.location, + rangeOfFollowingAmp.location - passwdRange.location); + } + + NSMutableString *result = [NSMutableString stringWithString:originalStr]; + NSString *replacement = [NSString stringWithFormat:@"%@_snip_", paramName]; + + [result replaceCharactersInRange:replaceRange withString:replacement]; + return result; + } + return originalStr; +} + +// stringFromStreamData creates a string given the supplied data +// +// If NSString can create a UTF-8 string from the data, then that is returned. +// +// Otherwise, this routine tries to find a MIME boundary at the beginning of +// the data block, and uses that to break up the data into parts. Each part +// will be used to try to make a UTF-8 string. For parts that fail, a +// replacement string showing the part header and <<n bytes>> is supplied +// in place of the binary data. + +- (NSString *)stringFromStreamData:(NSData *)data { + + if (data == nil) return nil; + + // optimistically, see if the whole data block is UTF-8 + NSString *streamDataStr = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + if (streamDataStr) return streamDataStr; + + // Munge a buffer by replacing non-ASCII bytes with underscores, + // and turn that munged buffer an NSString. That gives us a string + // we can use with NSScanner. + NSMutableData *mutableData = [NSMutableData dataWithData:data]; + unsigned char *bytes = [mutableData mutableBytes]; + + for (int idx = 0; idx < [mutableData length]; idx++) { + if (bytes[idx] > 0x7F || bytes[idx] == 0) { + bytes[idx] = '_'; + } + } + + NSString *mungedStr = [[[NSString alloc] initWithData:mutableData + encoding:NSUTF8StringEncoding] autorelease]; + if (mungedStr != nil) { + + // scan for the boundary string + NSString *boundary = nil; + NSScanner *scanner = [NSScanner scannerWithString:mungedStr]; + + if ([scanner scanUpToString:@"\r\n" intoString:&boundary] + && [boundary hasPrefix:@"--"]) { + + // we found a boundary string; use it to divide the string into parts + NSArray *mungedParts = [mungedStr componentsSeparatedByString:boundary]; + + // look at each of the munged parts in the original string, and try to + // convert those into UTF-8 + NSMutableArray *origParts = [NSMutableArray array]; + NSUInteger offset = 0; + for (int partIdx = 0; partIdx < [mungedParts count]; partIdx++) { + + NSString *mungedPart = [mungedParts objectAtIndex:partIdx]; + NSUInteger partSize = [mungedPart length]; + + NSRange range = NSMakeRange(offset, partSize); + NSData *origPartData = [data subdataWithRange:range]; + + NSString *origPartStr = [[[NSString alloc] initWithData:origPartData + encoding:NSUTF8StringEncoding] autorelease]; + if (origPartStr) { + // we could make this original part into UTF-8; use the string + [origParts addObject:origPartStr]; + } else { + // this part can't be made into UTF-8; scan the header, if we can + NSString *header = nil; + NSScanner *headerScanner = [NSScanner scannerWithString:mungedPart]; + if (![headerScanner scanUpToString:@"\r\n\r\n" intoString:&header]) { + // we couldn't find a header + header = @""; + } + + // make a part string with the header and <<n bytes>> + NSString *binStr = [NSString stringWithFormat:@"\r%@\r<<%u bytes>>\r", + header, partSize - [header length]]; + [origParts addObject:binStr]; + } + offset += partSize + [boundary length]; + } + + // rejoin the original parts + streamDataStr = [origParts componentsJoinedByString:boundary]; + } + } + + if (!streamDataStr) { + // give up; just make a string showing the uploaded bytes + streamDataStr = [NSString stringWithFormat:@"<<%u bytes>>", [data length]]; + } + return streamDataStr; +} + +// logFetchWithError is called following a successful or failed fetch attempt +// +// This method does all the work for appending to and creating log files + +- (void)logFetchWithError:(NSError *)error { + + if (![[self class] isLoggingEnabled]) return; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + // TODO: add Javascript to display response data formatted in hex + + NSString *logDirectory = [[self class] loggingDirectory]; + NSString *processName = [[self class] loggingProcessName]; + NSString *dateStamp = [[self class] loggingDateStamp]; + + // each response's NSData goes into its own xml or txt file, though all + // responses for this run of the app share a main html file. This + // counter tracks all fetch responses for this run of the app. + static int zResponseCounter = 0; + zResponseCounter++; + + // file name for the html file containing plain text in a <textarea> + NSString *responseDataUnformattedFileName = nil; + + // file name for the "formatted" (raw) data file + NSString *responseDataFormattedFileName = nil; + NSUInteger responseDataLength = [downloadedData_ length]; + + NSURLResponse *response = [self response]; + NSString *responseBaseName = nil; + + // if there's response data, decide what kind of file to put it in based + // on the first bytes of the file or on the mime type supplied by the server + if (responseDataLength) { + NSString *responseDataExtn = nil; + + // generate a response file base name like + // SyncProto_http_response_10-16_01-56-58PM_3 + responseBaseName = [NSString stringWithFormat:@"%@_http_response_%@_%d", + processName, dateStamp, zResponseCounter]; + + NSString *dataStr = [[[NSString alloc] initWithData:downloadedData_ + encoding:NSUTF8StringEncoding] autorelease]; + if (dataStr) { + // we were able to make a UTF-8 string from the response data + + NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceCharacterSet]; + dataStr = [dataStr stringByTrimmingCharactersInSet:whitespaceSet]; + + // save a plain-text version of the response data in an html cile + // containing a wrapped, scrollable <textarea> + // + // we'll use <textarea rows="33" cols="108" readonly=true wrap=soft> + // </textarea> to fit inside our iframe + responseDataUnformattedFileName = [responseBaseName stringByAppendingPathExtension:@"html"]; + NSString *textFilePath = [logDirectory stringByAppendingPathComponent:responseDataUnformattedFileName]; + + NSString* wrapFmt = @"<textarea rows=\"33\" cols=\"108\" readonly=true" + " wrap=soft>\n%@\n</textarea>"; + NSString* wrappedStr = [NSString stringWithFormat:wrapFmt, dataStr]; + [wrappedStr writeToFile:textFilePath + atomically:NO + encoding:NSUTF8StringEncoding + error:nil]; + + // now determine the extension for the "formatted" file, which is really + // the raw data written with an appropriate extension + + // for known file types, we'll write the data to a file with the + // appropriate extension + if ([dataStr hasPrefix:@"<?xml"]) { + responseDataExtn = @"xml"; + } else if ([dataStr hasPrefix:@"<html"]) { + responseDataExtn = @"html"; + } else { + // add more types of identifiable text here + } + + } else if ([[response MIMEType] isEqual:@"image/jpeg"]) { + responseDataExtn = @"jpg"; + } else if ([[response MIMEType] isEqual:@"image/gif"]) { + responseDataExtn = @"gif"; + } else if ([[response MIMEType] isEqual:@"image/png"]) { + responseDataExtn = @"png"; + } else { + // add more non-text types here + } + + // if we have an extension, save the raw data in a file with that + // extension to be our "formatted" display file + if (responseDataExtn) { + responseDataFormattedFileName = [responseBaseName stringByAppendingPathExtension:responseDataExtn]; + NSString *formattedFilePath = [logDirectory stringByAppendingPathComponent:responseDataFormattedFileName]; + + [downloadedData_ writeToFile:formattedFilePath atomically:NO]; + } + } + + // we'll have one main html file per run of the app + NSString *htmlName = [NSString stringWithFormat:@"%@_http_log_%@.html", + processName, dateStamp]; + NSString *htmlPath =[logDirectory stringByAppendingPathComponent:htmlName]; + + // if the html file exists (from logging previous fetches) we don't need + // to re-write the header or the scripts + BOOL didFileExist = [fileManager fileExistsAtPath:htmlPath]; + + NSMutableString* outputHTML = [NSMutableString string]; + NSURLRequest *request = [self request]; + + // we need file names for the various div's that we're going to show and hide, + // names unique to this response's bundle of data, so we format our div + // names with the counter that we incremented earlier + NSString *requestHeadersName = [NSString stringWithFormat:@"RequestHeaders%d", zResponseCounter]; + NSString *postDataName = [NSString stringWithFormat:@"PostData%d", zResponseCounter]; + + NSString *responseHeadersName = [NSString stringWithFormat:@"ResponseHeaders%d", zResponseCounter]; + NSString *responseDataDivName = [NSString stringWithFormat:@"ResponseData%d", zResponseCounter]; + NSString *dataIFrameID = [NSString stringWithFormat:@"DataIFrame%d", zResponseCounter]; + + // we need a header to say we'll have UTF-8 text + if (!didFileExist) { + [outputHTML appendFormat:@"<html><head><meta http-equiv=\"content-type\" " + "content=\"text/html; charset=UTF-8\"><title>%@ HTTP fetch log %@</title>", + processName, dateStamp]; + } + + // write style sheets for each hideable element; each style sheet is + // customized with our current response number, since they'll share + // the html page with other responses + NSString *styleFormat = @"<style type=\"text/css\">div#%@ " + "{ margin: 0px 20px 0px 20px; display: none; }</style>\n"; + + [outputHTML appendFormat:styleFormat, requestHeadersName]; + [outputHTML appendFormat:styleFormat, postDataName]; + [outputHTML appendFormat:styleFormat, responseHeadersName]; + [outputHTML appendFormat:styleFormat, responseDataDivName]; + + if (!didFileExist) { + // write javascript functions. The first one shows/hides the layer + // containing the iframe. + NSString *scriptFormat = @"<script type=\"text/javascript\"> " + "function toggleLayer(whichLayer){ var style2 = document.getElementById(whichLayer).style; " + "style2.display = style2.display ? \"\":\"block\";}</script>\n"; + [outputHTML appendFormat:scriptFormat]; + + // the second function is passed the src file; if it's what's shown, it + // toggles the iframe's visibility. If some other src is shown, it shows + // the iframe and loads the new source. Note we want to load the source + // whenever we show the iframe too since Firefox seems to format it wrong + // when showing it if we don't reload it. + NSString *toggleIFScriptFormat = @"<script type=\"text/javascript\"> " + "function toggleIFrame(whichLayer,iFrameID,newsrc)" + "{ \n var iFrameElem=document.getElementById(iFrameID); " + "if (iFrameElem.src.indexOf(newsrc) != -1) { toggleLayer(whichLayer); } " + "else { document.getElementById(whichLayer).style.display=\"block\"; } " + "iFrameElem.src=newsrc; }</script>\n</head>\n<body>\n"; + [outputHTML appendFormat:toggleIFScriptFormat]; + } + + // now write the visible html elements + + // write the date & time + [outputHTML appendFormat:@"<b>%@</b><br>", [[NSDate date] description]]; + + // write the request URL + [outputHTML appendFormat:@"<b>request:</b> %@ <i>URL:</i> <code>%@</code><br>\n", + [request HTTPMethod], [request URL]]; + + // write the request headers, toggleable + NSDictionary *requestHeaders = [request allHTTPHeaderFields]; + if ([requestHeaders count]) { + NSString *requestHeadersFormat = @"<a href=\"javascript:toggleLayer('%@');\">" + "request headers (%d)</a><div id=\"%@\"><pre>%@</pre></div><br>\n"; + [outputHTML appendFormat:requestHeadersFormat, + requestHeadersName, // layer name + [requestHeaders count], + requestHeadersName, + [requestHeaders description]]; // description gives a human-readable dump + } else { + [outputHTML appendString:@"<i>Request headers: none</i><br>"]; + } + + // write the request post data, toggleable + NSData *postData = postData_; + if (loggedStreamData_) { + postData = loggedStreamData_; + } + + if ([postData length]) { + NSString *postDataFormat = @"<a href=\"javascript:toggleLayer('%@');\">" + "posted data (%d bytes)</a><div id=\"%@\">%@</div><br>\n"; + NSString *postDataStr = [self stringFromStreamData:postData]; + if (postDataStr) { + NSString *postDataTextAreaFmt = @"<pre>%@</pre>"; + if ([postDataStr rangeOfString:@"<"].location != NSNotFound) { + postDataTextAreaFmt = @"<textarea rows=\"15\" cols=\"100\"" + " readonly=true wrap=soft>\n%@\n</textarea>"; + } + NSString *cleanedPostData = [self cleanParameterFollowing:@"&Passwd=" + fromString:postDataStr]; + NSString *postDataTextArea = [NSString stringWithFormat: + postDataTextAreaFmt, cleanedPostData]; + + [outputHTML appendFormat:postDataFormat, + postDataName, // layer name + [postData length], + postDataName, + postDataTextArea]; + } + } else { + // no post data + } + + // write the response status, MIME type, URL + if (response) { + NSString *statusString = @""; + if ([response respondsToSelector:@selector(statusCode)]) { + NSInteger status = [(NSHTTPURLResponse *)response statusCode]; + statusString = @"200"; + if (status != 200) { + // purple for errors + statusString = [NSString stringWithFormat:@"<FONT COLOR=\"#FF00FF\">%d</FONT>", + status]; + } + } + + // show the response URL only if it's different from the request URL + NSString *responseURLStr = @""; + NSURL *responseURL = [response URL]; + + if (responseURL && ![responseURL isEqual:[request URL]]) { + NSString *responseURLFormat = @"<br><FONT COLOR=\"#FF00FF\">response URL:" + "</FONT> <code>%@</code>"; + responseURLStr = [NSString stringWithFormat:responseURLFormat, + [responseURL absoluteString]]; + } + + NSDictionary *responseHeaders = nil; + if ([response respondsToSelector:@selector(allHeaderFields)]) { + responseHeaders = [(NSHTTPURLResponse *)response allHeaderFields]; + } + [outputHTML appendFormat:@"<b>response:</b> <i>status:</i> %@ <i> " + " MIMEType:</i><code> %@</code>%@<br>\n", + statusString, + [response MIMEType], + responseURLStr, + responseHeaders ? [responseHeaders description] : @""]; + + // write the response headers, toggleable + if ([responseHeaders count]) { + + NSString *cookiesSet = [responseHeaders objectForKey:@"Set-Cookie"]; + + NSString *responseHeadersFormat = @"<a href=\"javascript:toggleLayer(" + "'%@');\">response headers (%d) %@</a><div id=\"%@\"><pre>%@</pre>" + "</div><br>\n"; + [outputHTML appendFormat:responseHeadersFormat, + responseHeadersName, + [responseHeaders count], + (cookiesSet ? @"<i>sets cookies</i>" : @""), + responseHeadersName, + [responseHeaders description]]; + + } else { + [outputHTML appendString:@"<i>Response headers: none</i><br>\n"]; + } + } + + // error + if (error) { + [outputHTML appendFormat:@"<b>error:</b> %@ <br>\n", [error description]]; + } + + // write the response data. We have links to show formatted and text + // versions, but they both show it in the same iframe, and both + // links also toggle visible/hidden + if (responseDataFormattedFileName || responseDataUnformattedFileName) { + + // response data, toggleable links -- formatted and text versions + if (responseDataFormattedFileName) { + [outputHTML appendFormat:@"response data (%d bytes) formatted <b>%@</b> ", + responseDataLength, + [responseDataFormattedFileName pathExtension]]; + + // inline (iframe) link + NSString *responseInlineFormattedDataNameFormat = @" <a " + "href=\"javascript:toggleIFrame('%@','%@','%@');\">inline</a>\n"; + [outputHTML appendFormat:responseInlineFormattedDataNameFormat, + responseDataDivName, // div ID + dataIFrameID, // iframe ID (for reloading) + responseDataFormattedFileName]; // src to reload + + // plain link (so the user can command-click it into another tab) + [outputHTML appendFormat:@" <a href=\"%@\">stand-alone</a><br>\n", + [responseDataFormattedFileName + stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + } + if (responseDataUnformattedFileName) { + [outputHTML appendFormat:@"response data (%d bytes) plain text ", + responseDataLength]; + + // inline (iframe) link + NSString *responseInlineDataNameFormat = @" <a href=\"" + "javascript:toggleIFrame('%@','%@','%@');\">inline</a> \n"; + [outputHTML appendFormat:responseInlineDataNameFormat, + responseDataDivName, // div ID + dataIFrameID, // iframe ID (for reloading) + responseDataUnformattedFileName]; // src to reload + + // plain link (so the user can command-click it into another tab) + [outputHTML appendFormat:@" <a href=\"%@\">stand-alone</a><br>\n", + [responseDataUnformattedFileName + stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + } + + // make the iframe + NSString *divHTMLFormat = @"<div id=\"%@\">%@</div><br>\n"; + NSString *src = responseDataFormattedFileName ? + responseDataFormattedFileName : responseDataUnformattedFileName; + NSString *escapedSrc = [src stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *iframeFmt = @" <iframe src=\"%@\" id=\"%@\" width=800 height=400>" + "\n<a href=\"%@\">%@</a>\n </iframe>\n"; + NSString *dataIFrameHTML = [NSString stringWithFormat:iframeFmt, + escapedSrc, dataIFrameID, escapedSrc, src]; + [outputHTML appendFormat:divHTMLFormat, + responseDataDivName, dataIFrameHTML]; + } else { + // could not parse response data; just show the length of it + [outputHTML appendFormat:@"<i>Response data: %d bytes </i>\n", + responseDataLength]; + } + + [outputHTML appendString:@"<br><hr><p>"]; + + // append the HTML to the main output file + const char* htmlBytes = [outputHTML UTF8String]; + NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:htmlPath + append:YES]; + [stream open]; + [stream write:(const uint8_t *) htmlBytes maxLength:strlen(htmlBytes)]; + [stream close]; + + // make a symlink to the latest html + NSString *symlinkName = [NSString stringWithFormat:@"%@_http_log_newest.html", + processName]; + NSString *symlinkPath = [logDirectory stringByAppendingPathComponent:symlinkName]; + + // removeFileAtPath might be going away, but removeItemAtPath does not exist + // in 10.4 + if ([fileManager respondsToSelector:@selector(removeFileAtPath:handler:)]) { + [fileManager removeFileAtPath:symlinkPath handler:nil]; + } else if ([fileManager respondsToSelector:@selector(removeItemAtPath:error:)]) { + // To make the next line compile when targeting 10.4, we declare + // removeItemAtPath:error: in an @interface above + [fileManager removeItemAtPath:symlinkPath error:NULL]; + } + + [fileManager createSymbolicLinkAtPath:symlinkPath pathContent:htmlPath]; +} + +- (void)logCapturePostStream { + +#if GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING + // This is called when beginning a fetch. The caller should have already + // verified that logging is enabled, and should have allocated + // loggedStreamData_ as a mutable object. + + // If we're logging, we need to wrap the upload stream with our monitor + // stream subclass that will call us back with the bytes being read from the + // stream + + // our wrapper will retain the old post stream + [postStream_ autorelease]; + + // length can be + postStream_ = [GTMInputStreamLogger inputStreamWithStream:postStream_ + length:0]; + [postStream_ retain]; + + // we don't really want monitoring callbacks; our subclass will be + // calling our appendLoggedStreamData: method at every read instead + [(GTMInputStreamLogger *)postStream_ setMonitorDelegate:self + selector:nil]; +#endif // GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING +} + +- (void)appendLoggedStreamData:(NSData *)newData { + [loggedStreamData_ appendData:newData]; +} + +#endif // GTM_HTTPFETCHER_ENABLE_LOGGING +@end + +#if GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING +@implementation GTMInputStreamLogger +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { + + // capture the read stream data, and pass it to the delegate to append to + NSInteger result = [super read:buffer maxLength:len]; + if (result >= 0) { + NSData *data = [NSData dataWithBytes:buffer length:result]; + [monitorDelegate_ appendLoggedStreamData:data]; + } + return result; +} +@end +#endif // GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING diff --git a/Foundation/GTMHTTPFetcherTest.m b/Foundation/GTMHTTPFetcherTest.m new file mode 100644 index 0000000..16c8d0f --- /dev/null +++ b/Foundation/GTMHTTPFetcherTest.m @@ -0,0 +1,543 @@ +// +// GTMHTTPFetcherTest.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <SenTestingKit/SenTestingKit.h> +#import <unistd.h> +#import "GTMHTTPFetcher.h" +#import "GTMSenTestCase.h" + +@interface GTMHTTPFetcherTest : SenTestCase { + // these ivars are checked after fetches, and are reset by resetFetchResponse + NSData *fetchedData_; + NSError *fetcherError_; + NSInteger fetchedStatus_; + NSURLResponse *fetchedResponse_; + NSMutableURLRequest *fetchedRequest_; + + // setup/teardown ivars + NSMutableDictionary *fetchHistory_; + NSTask *server_; // python http server + BOOL didServerLaunch_; // Tracks the state of our server + BOOL didServerDie_; + NSMutableData *launchBuffer_; // Storage for output from our server +} +@end + +@interface GTMHTTPFetcherTest (PrivateMethods) +- (GTMHTTPFetcher *)doFetchWithURLString:(NSString *)urlString + cachingDatedData:(BOOL)doCaching; + +- (GTMHTTPFetcher *)doFetchWithURLString:(NSString *)urlString + cachingDatedData:(BOOL)doCaching + retrySelector:(SEL)retrySel + maxRetryInterval:(NSTimeInterval)maxRetryInterval + userData:(id)userData; + +- (NSString *)fileURLStringToTestFileName:(NSString *)name; +@end + +@implementation GTMHTTPFetcherTest + +static const int kServerPortNumber = 54579; +static const NSTimeInterval kRunLoopInterval = 0.01; +// The bogus-fetch test can take >10s to pass. Pick something way higher +// to avoid failing. +static const NSTimeInterval kGiveUpInterval = 60.0; // bail on the test if 60 seconds elapse + +static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; + +- (void)gotData:(NSNotification*)notification { + // our server sends out a string to confirm that it launched + NSFileHandle *handle = [notification object]; + NSData *launchMessageData = [handle availableData]; + [launchBuffer_ appendData:launchMessageData]; + NSString *launchStr = + [[[NSString alloc] initWithData:launchBuffer_ + encoding:NSUTF8StringEncoding] autorelease]; + didServerLaunch_ = + [launchStr rangeOfString:@"started GTMHTTPFetcherTestServer"].location != NSNotFound; + if (!didServerLaunch_) { + _GTMDevLog(@"gotData launching httpserver: %@", launchStr); + [handle readInBackgroundAndNotify]; + } +} + +- (void)didDie:(NSNotification*)notification { + _GTMDevLog(@"server died"); + didServerDie_ = YES; +} + + +- (void)setUp { + fetchHistory_ = [[NSMutableDictionary alloc] init]; + + // run the python http server, located in the Tests directory + NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; + STAssertNotNil(testBundle, nil); + + NSString *serverPath = + [testBundle pathForResource:@"GTMHTTPFetcherTestServer" ofType:@""]; + STAssertNotNil(serverPath, nil); + + NSArray *argArray = [NSArray arrayWithObjects:serverPath, + @"-p", [NSString stringWithFormat:@"%d", kServerPortNumber], + @"-r", [serverPath stringByDeletingLastPathComponent], nil]; + + server_ = [[NSTask alloc] init]; + [server_ setArguments:argArray]; + [server_ setLaunchPath:@"/usr/bin/python"]; + [server_ setEnvironment:[NSDictionary dictionary]]; // don't inherit anything from us + + // pipes will be cleaned up when server_ is torn down. + NSPipe *outputPipe = [NSPipe pipe]; + NSPipe *errorPipe = [NSPipe pipe]; + + [server_ setStandardOutput:outputPipe]; + [server_ setStandardError:errorPipe]; + + NSFileHandle *outputHandle = [outputPipe fileHandleForReading]; + NSFileHandle *errorHandle = [errorPipe fileHandleForReading]; + + didServerLaunch_ = NO; + didServerDie_ = NO; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(gotData:) + name:NSFileHandleDataAvailableNotification + object:outputHandle]; + [center addObserver:self + selector:@selector(gotData:) + name:NSFileHandleDataAvailableNotification + object:errorHandle]; + [center addObserver:self + selector:@selector(didDie:) + name:NSTaskDidTerminateNotification + object:server_]; + + [launchBuffer_ autorelease]; + launchBuffer_ = [[NSMutableData data] retain]; + [outputHandle waitForDataInBackgroundAndNotify]; + [errorHandle waitForDataInBackgroundAndNotify]; + [server_ launch]; + + NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:kGiveUpInterval]; + while ((!didServerDie_ && !didServerLaunch_) && + [giveUpDate timeIntervalSinceNow] > 0) { + NSDate* loopIntervalDate = + [NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; + } + + [center removeObserver:self]; + + STAssertTrue(didServerLaunch_ && [server_ isRunning] && !didServerDie_, + @"Python http server not launched.\n" + "Args:%@\n" + "Environment:%@\n", + [argArray componentsJoinedByString:@" "], [server_ environment]); +} + +- (void)resetFetchResponse { + [fetchedData_ release]; + fetchedData_ = nil; + + [fetcherError_ release]; + fetcherError_ = nil; + + [fetchedRequest_ release]; + fetchedRequest_ = nil; + + [fetchedResponse_ release]; + fetchedResponse_ = nil; + + fetchedStatus_ = 0; +} + +- (void)tearDown { + [server_ terminate]; + [server_ waitUntilExit]; + [server_ release]; + server_ = nil; + [launchBuffer_ release]; + launchBuffer_ = nil; + [self resetFetchResponse]; + + [fetchHistory_ release]; + fetchHistory_ = nil; +} + +- (void)testValidFetch { + + NSString *urlString = [self fileURLStringToTestFileName:kValidFileName]; + + [self doFetchWithURLString:urlString cachingDatedData:YES]; + + STAssertNotNil(fetchedData_, + @"failed to fetch data, status:%ld error:%@, URL:%@", + (long)fetchedStatus_, fetcherError_, urlString); + STAssertNotNil(fetchedResponse_, + @"failed to get fetch response, status:%ld error:%@", + (long)fetchedStatus_, fetcherError_); + STAssertNotNil(fetchedRequest_, @"failed to get fetch request, URL %@", + urlString); + STAssertNil(fetcherError_, @"fetching data gave error: %@", fetcherError_); + STAssertEquals(fetchedStatus_, (NSInteger)200, + @"fetching data expected status 200, instead got %ld, for URL %@", + (long)fetchedStatus_, urlString); + + // no cookies should be sent with our first request + NSDictionary *headers = [fetchedRequest_ allHTTPHeaderFields]; + NSString *cookiesSent = [headers objectForKey:@"Cookie"]; + STAssertNil(cookiesSent, @"Cookies sent unexpectedly: %@", cookiesSent); + + + // cookies should have been set by the response; specifically, TestCookie + // should be set to the name of the file requested + NSDictionary *responseHeaders; + + responseHeaders = [(NSHTTPURLResponse *)fetchedResponse_ allHeaderFields]; + NSString *cookiesSetString = [responseHeaders objectForKey:@"Set-Cookie"]; + NSString *cookieExpected = [NSString stringWithFormat:@"TestCookie=%@", + kValidFileName]; + STAssertEqualObjects(cookiesSetString, cookieExpected, @"Unexpected cookie"); + + // make a copy of the fetched data to compare with our next fetch from the + // cache + NSData *originalFetchedData = [[fetchedData_ copy] autorelease]; + + // Now fetch again so the "If modified since" header will be set (because + // we're calling setFetchHistory: below) and caching ON, and verify that we + // got a good data from the cache, along with a "Not modified" status + + [self resetFetchResponse]; + + [self doFetchWithURLString:urlString cachingDatedData:YES]; + + STAssertEqualObjects(fetchedData_, originalFetchedData, + @"cache data mismatch"); + + STAssertNotNil(fetchedData_, + @"failed to fetch data, status:%ld error:%@, URL:%@", + (long)fetchedStatus_, fetcherError_, urlString); + STAssertNotNil(fetchedResponse_, + @"failed to get fetch response, status:%;d error:%@", + (long)fetchedStatus_, fetcherError_); + STAssertNotNil(fetchedRequest_, @"failed to get fetch request, URL %@", + urlString); + STAssertNil(fetcherError_, @"fetching data gave error: %@", fetcherError_); + + STAssertEquals(fetchedStatus_, (NSInteger)kGTMHTTPFetcherStatusNotModified, // 304 + @"fetching data expected status 304, instead got %ld, for URL %@", + (long)fetchedStatus_, urlString); + + // the TestCookie set previously should be sent with this request + cookiesSent = [[fetchedRequest_ allHTTPHeaderFields] objectForKey:@"Cookie"]; + STAssertEqualObjects(cookiesSent, cookieExpected, @"Cookie not sent"); + + // Now fetch twice without caching enabled, and verify that we got a + // "Not modified" status, along with a non-nil but empty NSData (which + // is normal for that status code) + + [self resetFetchResponse]; + + [fetchHistory_ removeAllObjects]; + + [self doFetchWithURLString:urlString cachingDatedData:NO]; + + STAssertEqualObjects(fetchedData_, originalFetchedData, + @"cache data mismatch"); + + [self resetFetchResponse]; + [self doFetchWithURLString:urlString cachingDatedData:NO]; + + STAssertNotNil(fetchedData_, @""); + STAssertEquals([fetchedData_ length], (NSUInteger)0, @"unexpected data"); + STAssertEquals(fetchedStatus_, (NSInteger)kGTMHTTPFetcherStatusNotModified, + @"fetching data expected status 304, instead got %d", fetchedStatus_); + STAssertNil(fetcherError_, @"unexpected error: %@", fetcherError_); + +} + +- (void)testBogusFetch { + // fetch a live, invalid URL + NSString *badURLString = @"http://localhost:86/"; + [self doFetchWithURLString:badURLString cachingDatedData:NO]; + + const int kServiceUnavailableStatus = 503; + + if (fetchedStatus_ == kServiceUnavailableStatus) { + // some proxies give a "service unavailable" error for bogus fetches + } else { + + if (fetchedData_) { + NSString *str = [[[NSString alloc] initWithData:fetchedData_ + encoding:NSUTF8StringEncoding] autorelease]; + STAssertNil(fetchedData_, @"fetched unexpected data: %@", str); + } + + STAssertNotNil(fetcherError_, @"failed to receive fetching error"); + STAssertEquals(fetchedStatus_, (NSInteger)0, + @"fetching data expected no status from no response, instead got %d", + fetchedStatus_); + } + + // fetch with a specific status code from our http server + [self resetFetchResponse]; + + NSString *invalidWebPageFile = + [kValidFileName stringByAppendingString:@"?status=400"]; + NSString *statusUrlString = + [self fileURLStringToTestFileName:invalidWebPageFile]; + + [self doFetchWithURLString:statusUrlString cachingDatedData:NO]; + + STAssertNotNil(fetchedData_, @"fetch lacked data with error info"); + STAssertNil(fetcherError_, @"expected bad status but got an error"); + STAssertEquals(fetchedStatus_, (NSInteger)400, + @"unexpected status, error=%@", fetcherError_); +} + +- (void)testRetryFetches { + + GTMHTTPFetcher *fetcher; + + NSString *invalidFile = [kValidFileName stringByAppendingString:@"?status=503"]; + NSString *urlString = [self fileURLStringToTestFileName:invalidFile]; + + SEL countRetriesSel = @selector(countRetriesFetcher:willRetry:forError:); + SEL fixRequestSel = @selector(fixRequestFetcher:willRetry:forError:); + + // + // test: retry until timeout, then expect failure with status message + // + + NSNumber *lotsOfRetriesNumber = [NSNumber numberWithInt:1000]; + + fetcher= [self doFetchWithURLString:urlString + cachingDatedData:NO + retrySelector:countRetriesSel + maxRetryInterval:5.0 // retry intervals of 1, 2, 4 + userData:lotsOfRetriesNumber]; + + STAssertNotNil(fetchedData_, @"error data is expected"); + STAssertEquals(fetchedStatus_, (NSInteger)503, nil); + STAssertEquals([fetcher retryCount], 3U, @"retry count unexpected"); + + // + // test: retry twice, then give up + // + [self resetFetchResponse]; + + NSNumber *twoRetriesNumber = [NSNumber numberWithInt:2]; + + fetcher= [self doFetchWithURLString:urlString + cachingDatedData:NO + retrySelector:countRetriesSel + maxRetryInterval:10.0 // retry intervals of 1, 2, 4, 8 + userData:twoRetriesNumber]; + + STAssertNotNil(fetchedData_, @"error data is expected"); + STAssertEquals(fetchedStatus_, (NSInteger)503, nil); + STAssertEquals([fetcher retryCount], 2U, @"retry count unexpected"); + + + // + // test: retry, making the request succeed on the first retry + // by fixing the URL + // + [self resetFetchResponse]; + + fetcher= [self doFetchWithURLString:urlString + cachingDatedData:NO + retrySelector:fixRequestSel + maxRetryInterval:30.0 // should only retry once due to selector + userData:lotsOfRetriesNumber]; + + STAssertNotNil(fetchedData_, @"data is expected"); + STAssertEquals(fetchedStatus_, (NSInteger)200, nil); + STAssertEquals([fetcher retryCount], 1U, @"retry count unexpected"); +} + +#pragma mark - + +- (GTMHTTPFetcher *)doFetchWithURLString:(NSString *)urlString + cachingDatedData:(BOOL)doCaching { + + return [self doFetchWithURLString:(NSString *)urlString + cachingDatedData:(BOOL)doCaching + retrySelector:nil + maxRetryInterval:0 + userData:nil]; +} + +- (GTMHTTPFetcher *)doFetchWithURLString:(NSString *)urlString + cachingDatedData:(BOOL)doCaching + retrySelector:(SEL)retrySel + maxRetryInterval:(NSTimeInterval)maxRetryInterval + userData:(id)userData { + + NSURL *url = [NSURL URLWithString:urlString]; + NSURLRequest *req = [NSURLRequest requestWithURL:url + cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:kGiveUpInterval]; + GTMHTTPFetcher *fetcher = [GTMHTTPFetcher httpFetcherWithRequest:req]; + + STAssertNotNil(fetcher, @"Failed to allocate fetcher"); + + // setting the fetch history will add the "If-modified-since" header + // to repeat requests + [fetcher setFetchHistory:fetchHistory_]; + if (doCaching != [fetcher shouldCacheDatedData]) { + // only set the value when it changes since setting it to nil clears out + // some of the state and our tests need the state between some non caching + // fetches. + [fetcher setShouldCacheDatedData:doCaching]; + } + + if (retrySel) { + [fetcher setIsRetryEnabled:YES]; + [fetcher setRetrySelector:retrySel]; + [fetcher setMaxRetryInterval:maxRetryInterval]; + [fetcher setUserData:userData]; + + // we force a minimum retry interval for unit testing; otherwise, + // we'd have no idea how many retries will occur before the max + // retry interval occurs, since the minimum would be random + [fetcher setMinRetryInterval:1.0]; + } + + BOOL isFetching = + [fetcher beginFetchWithDelegate:self + didFinishSelector:@selector(testFetcher:finishedWithData:) + didFailSelector:@selector(testFetcher:failedWithError:)]; + STAssertTrue(isFetching, @"Begin fetch failed"); + + if (isFetching) { + + // Give time for the fetch to happen, but give up if 10 seconds elapse with + // no response + NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:kGiveUpInterval]; + while ((!fetchedData_ && !fetcherError_) && + [giveUpDate timeIntervalSinceNow] > 0) { + NSDate* loopIntervalDate = + [NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; + } + } + + return fetcher; +} + +- (NSString *)fileURLStringToTestFileName:(NSString *)name { + + // we need to create http URLs referring to the desired + // resource to be found by the python http server running locally + + // return a localhost:port URL for the test file + NSString *urlString = [NSString stringWithFormat:@"http://localhost:%d/%@", + kServerPortNumber, name]; + + + // we exclude the "?status=" that would indicate that the URL + // should cause a retryable error + NSRange range = [name rangeOfString:@"?status="]; + if (range.length > 0) { + name = [name substringToIndex:range.location]; + } + + // we exclude the ".auth" extension that would indicate that the URL + // should be tested with authentication + if ([[name pathExtension] isEqual:@"auth"]) { + name = [name stringByDeletingPathExtension]; + } + + // just for sanity, let's make sure we see the file locally, so + // we can expect the Python http server to find it too + NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; + STAssertNotNil(testBundle, nil); + + NSString *filePath = + [testBundle pathForResource:[name stringByDeletingPathExtension] + ofType:[name pathExtension]]; + STAssertNotNil(filePath, nil); + + return urlString; +} + + + +- (void)testFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data { + fetchedData_ = [data copy]; + fetchedStatus_ = [fetcher statusCode]; // this implicitly tests that the fetcher has kept the response + fetchedRequest_ = [[fetcher request] retain]; + fetchedResponse_ = [[fetcher response] retain]; +} + +- (void)testFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error { + // if it's a status error, don't hang onto the error, just the status/data + if ([[error domain] isEqual:kGTMHTTPFetcherStatusDomain]) { + fetchedData_ = [[[error userInfo] objectForKey:kGTMHTTPFetcherStatusDataKey] copy]; + fetchedStatus_ = [error code]; // this implicitly tests that the fetcher has kept the response + } else { + fetcherError_ = [error retain]; + fetchedStatus_ = [fetcher statusCode]; + } +} + + +// Selector for allowing up to N retries, where N is an NSNumber in the +// fetcher's userData +- (BOOL)countRetriesFetcher:(GTMHTTPFetcher *)fetcher + willRetry:(BOOL)suggestedWillRetry + forError:(NSError *)error { + + int count = [fetcher retryCount]; + int allowedRetryCount = [[fetcher userData] intValue]; + + BOOL shouldRetry = (count < allowedRetryCount); + + STAssertEquals([fetcher nextRetryInterval], pow(2.0, [fetcher retryCount]), + @"unexpected next retry interval (expected %f, was %f)", + pow(2.0, [fetcher retryCount]), + [fetcher nextRetryInterval]); + + return shouldRetry; +} + +// Selector for retrying and changing the request to one that will succeed +- (BOOL)fixRequestFetcher:(GTMHTTPFetcher *)fetcher + willRetry:(BOOL)suggestedWillRetry + forError:(NSError *)error { + + STAssertEquals([fetcher nextRetryInterval], pow(2.0, [fetcher retryCount]), + @"unexpected next retry interval (expected %f, was %f)", + pow(2.0, [fetcher retryCount]), + [fetcher nextRetryInterval]); + + // fix it - change the request to a URL which does not have a status value + NSString *urlString = [self fileURLStringToTestFileName:kValidFileName]; + + NSURL *url = [NSURL URLWithString:urlString]; + [fetcher setRequest:[NSURLRequest requestWithURL:url]]; + + return YES; // do the retry fetch; it should succeed now +} + +@end + diff --git a/Foundation/GTMNSData+zlib.h b/Foundation/GTMNSData+zlib.h index 2c937c9..df31d24 100644 --- a/Foundation/GTMNSData+zlib.h +++ b/Foundation/GTMNSData+zlib.h @@ -17,6 +17,7 @@ // #import <Foundation/Foundation.h> +#import "GTMDefines.h" /// Helpers for dealing w/ zlib inflate/deflate calls. @interface NSData (GTMZLibAdditions) @@ -25,7 +26,7 @@ // // Uses the default compression level. + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes - length:(unsigned)length; + length:(NSUInteger)length; /// Return an autoreleased NSData w/ the result of gzipping the payload of |data|. // @@ -36,7 +37,7 @@ // // |level| can be 1-9, any other values will be clipped to that range. + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes - length:(unsigned)length + length:(NSUInteger)length compressionLevel:(int)level; /// Return an autoreleased NSData w/ the result of gzipping the payload of |data| using |level| compression level. @@ -50,7 +51,7 @@ // // Uses the default compression level. + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes - length:(unsigned)length; + length:(NSUInteger)length; /// Return an autoreleased NSData w/ the result of deflating the payload of |data|. // @@ -61,7 +62,7 @@ // // |level| can be 1-9, any other values will be clipped to that range. + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes - length:(unsigned)length + length:(NSUInteger)length compressionLevel:(int)level; /// Return an autoreleased NSData w/ the result of deflating the payload of |data| using |level| compression level. @@ -73,7 +74,7 @@ // // The bytes to decompress can be zlib or gzip payloads. + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes - length:(unsigned)length; + length:(NSUInteger)length; /// Return an autoreleased NSData w/ the result of decompressing the payload of |data|. // diff --git a/Foundation/GTMNSData+zlib.m b/Foundation/GTMNSData+zlib.m index 514477f..8ba1ddc 100644 --- a/Foundation/GTMNSData+zlib.m +++ b/Foundation/GTMNSData+zlib.m @@ -24,19 +24,25 @@ @interface NSData (GTMZlibAdditionsPrivate) + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes - length:(unsigned)length + length:(NSUInteger)length compressionLevel:(int)level useGzip:(BOOL)useGzip; @end @implementation NSData (GTMZlibAdditionsPrivate) + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes - length:(unsigned)length + length:(NSUInteger)length compressionLevel:(int)level useGzip:(BOOL)useGzip { if (!bytes || !length) { return nil; } + + // TODO: support 64bit inputs + // avail_in is a uInt, so if length > UINT_MAX we actually need to loop + // feeding the data until we've gotten it all in. not supporting this + // at the moment. + _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths"); if (level == Z_DEFAULT_COMPRESSION) { // the default value is actually outside the range, so we have to let it @@ -70,7 +76,7 @@ unsigned char output[kChunkSize]; // setup the input - strm.avail_in = length; + strm.avail_in = (unsigned int)length; strm.next_in = (unsigned char*)bytes; // loop to collect the data @@ -119,7 +125,7 @@ @implementation NSData (GTMZLibAdditions) + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes - length:(unsigned)length { + length:(NSUInteger)length { return [self gtm_dataByCompressingBytes:bytes length:length compressionLevel:Z_DEFAULT_COMPRESSION @@ -134,7 +140,7 @@ } // gtm_dataByGzippingData: + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes - length:(unsigned)length + length:(NSUInteger)length compressionLevel:(int)level { return [self gtm_dataByCompressingBytes:bytes length:length @@ -151,7 +157,7 @@ } // gtm_dataByGzippingData:level: + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes - length:(unsigned)length { + length:(NSUInteger)length { return [self gtm_dataByCompressingBytes:bytes length:length compressionLevel:Z_DEFAULT_COMPRESSION @@ -166,7 +172,7 @@ } // gtm_dataByDeflatingData: + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes - length:(unsigned)length + length:(NSUInteger)length compressionLevel:(int)level { return [self gtm_dataByCompressingBytes:bytes length:length @@ -183,16 +189,22 @@ } // gtm_dataByDeflatingData:level: + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes - length:(unsigned)length { + length:(NSUInteger)length { if (!bytes || !length) { return nil; } + + // TODO: support 64bit inputs + // avail_in is a uInt, so if length > UINT_MAX we actually need to loop + // feeding the data until we've gotten it all in. not supporting this + // at the moment. + _GTMDevAssert(length <= UINT_MAX, @"Currently don't support >32bit lengths"); z_stream strm; bzero(&strm, sizeof(z_stream)); // setup the input - strm.avail_in = length; + strm.avail_in = (unsigned int)length; strm.next_in = (unsigned char*)bytes; int windowBits = 15; // 15 to enable any window size diff --git a/Foundation/GTMNSData+zlibTest.m b/Foundation/GTMNSData+zlibTest.m index 38dbce3..1d09e35 100644 --- a/Foundation/GTMNSData+zlibTest.m +++ b/Foundation/GTMNSData+zlibTest.m @@ -105,12 +105,12 @@ static BOOL HasGzipHeader(NSData *data) { STAssertNil([NSData gtm_dataByInflatingData:data], nil); // test deflated data runs that end before they are done - for (int x = 1 ; x < [deflated length] ; ++x) { + for (NSUInteger x = 1 ; x < [deflated length] ; ++x) { STAssertNil([NSData gtm_dataByInflatingBytes:[deflated bytes] length:x], nil); } // test gzipped data runs that end before they are done - for (int x = 1 ; x < [gzipped length] ; ++x) { + for (NSUInteger x = 1 ; x < [gzipped length] ; ++x) { STAssertNil([NSData gtm_dataByInflatingBytes:[gzipped bytes] length:x], nil); } @@ -175,21 +175,21 @@ static BOOL HasGzipHeader(NSData *data) { // w/ *Bytes apis, default level NSData *deflated = [NSData gtm_dataByDeflatingBytes:[data bytes] length:[data length]]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], 0U, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] length:[deflated length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); // w/ *Data apis, default level deflated = [NSData gtm_dataByDeflatingData:data]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], 0U, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); dataPrime = [NSData gtm_dataByInflatingData:deflated]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); // loop over the compression levels @@ -199,21 +199,21 @@ static BOOL HasGzipHeader(NSData *data) { length:[data length] compressionLevel:level]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], 0U, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] length:[deflated length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); // w/ *Data apis, using our level deflated = [NSData gtm_dataByDeflatingData:data compressionLevel:level]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], 0U, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); dataPrime = [NSData gtm_dataByInflatingData:deflated]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); } @@ -240,21 +240,21 @@ static BOOL HasGzipHeader(NSData *data) { // w/ *Bytes apis, default level NSData *gzipped = [NSData gtm_dataByGzippingBytes:[data bytes] length:[data length]]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], 0U, @"failed to gzip data block"); + STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] length:[gzipped length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); // w/ *Data apis, default level gzipped = [NSData gtm_dataByGzippingData:data]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], 0U, @"failed to gzip data block"); + STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); dataPrime = [NSData gtm_dataByInflatingData:gzipped]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); // loop over the compression levels @@ -264,21 +264,21 @@ static BOOL HasGzipHeader(NSData *data) { length:[data length] compressionLevel:level]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], 0U, @"failed to gzip data block"); + STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] length:[gzipped length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); // w/ *Data apis, using our level gzipped = [NSData gtm_dataByGzippingData:data compressionLevel:level]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], 0U, @"failed to gzip data block"); + STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); dataPrime = [NSData gtm_dataByInflatingData:gzipped]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], 0U, @"failed to inflate data block"); + STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); } diff --git a/Foundation/GTMNSEnumerator+Filter.m b/Foundation/GTMNSEnumerator+Filter.m index 5ac3a7c..0fda2ca 100644 --- a/Foundation/GTMNSEnumerator+Filter.m +++ b/Foundation/GTMNSEnumerator+Filter.m @@ -45,7 +45,7 @@ // someone would have to subclass or directly create an object of this // class, and this class is private to this impl. - _GTMDevAssert(base, @"can't bas a nil base enumerator"); + _GTMDevAssert(base, @"can't initWithBase: a nil base enumerator"); base_ = [base retain]; operation_ = filter; other_ = [optionalOther retain]; diff --git a/Foundation/GTMNSString+HTML.m b/Foundation/GTMNSString+HTML.m index a6abb0e..5178ba9 100644 --- a/Foundation/GTMNSString+HTML.m +++ b/Foundation/GTMNSString+HTML.m @@ -19,8 +19,6 @@ #import "GTMDefines.h" #import "GTMNSString+HTML.h" -#import "GTMNSString+Utilities.h" -#import "GTMMethodCheck.h" typedef struct { NSString *escapeSequence; @@ -373,23 +371,38 @@ static int EscapeMapCompare(const void *ucharVoid, const void *mapVoid) { } @implementation NSString (GTMNSStringHTMLAdditions) -GTM_METHOD_CHECK(NSString, gtm_UTF16StringWithLength:); - (NSString *)gtm_stringByEscapingHTMLUsingTable:(HTMLEscapeMap*)table - ofSize:(int)size + ofSize:(NSUInteger)size escapingUnicode:(BOOL)escapeUnicode { - int length = [self length]; + NSUInteger length = [self length]; if (!length) { - return nil; + return self; } NSMutableString *finalString = [NSMutableString string]; - NSMutableData *data2 = [NSMutableData dataWithCapacity:sizeof(unichar) * length]; - const unichar *buffer = (const unichar *)[self gtm_UTF16StringWithLength:nil]; + + // this block is common between GTMNSString+HTML and GTMNSString+XML but + // it's so short that it isn't really worth trying to share. + const unichar *buffer = CFStringGetCharactersPtr((CFStringRef)self); + if (!buffer) { + size_t memsize = length * sizeof(UniChar); + + // nope, alloc buffer and fetch the chars ourselves + buffer = malloc(memsize); + if (!buffer) { + // COV_NF_START - Memory fail case + _GTMDevLog(@"couldn't alloc buffer"); + return nil; + // COV_NF_END + } + [self getCharacters:(void*)buffer]; + [NSData dataWithBytesNoCopy:(void*)buffer length:memsize]; + } if (!buffer || !data2) { - // COV_NF_BEGIN + // COV_NF_START _GTMDevLog(@"Unable to allocate buffer or data2"); return nil; // COV_NF_END @@ -397,9 +410,9 @@ GTM_METHOD_CHECK(NSString, gtm_UTF16StringWithLength:); unichar *buffer2 = (unichar *)[data2 mutableBytes]; - int buffer2Length = 0; + NSUInteger buffer2Length = 0; - for (int i = 0; i < length; ++i) { + for (NSUInteger i = 0; i < length; ++i) { HTMLEscapeMap *val = bsearch(&buffer[i], table, size / sizeof(HTMLEscapeMap), sizeof(HTMLEscapeMap), EscapeMapCompare); @@ -459,7 +472,7 @@ GTM_METHOD_CHECK(NSString, gtm_UTF16StringWithLength:); } NSRange escapeRange = NSMakeRange(subrange.location, semiColonRange.location - subrange.location + 1); NSString *escapeString = [self substringWithRange:escapeRange]; - unsigned length = [escapeString length]; + NSUInteger length = [escapeString length]; // a squence must be longer than 3 (<) and less than 11 (ϑ) if (length > 3 && length < 11) { if ([escapeString characterAtIndex:1] == '#') { diff --git a/Foundation/GTMNSString+HTMLTest.m b/Foundation/GTMNSString+HTMLTest.m index c7b931a..a56c5a5 100644 --- a/Foundation/GTMNSString+HTMLTest.m +++ b/Foundation/GTMNSString+HTMLTest.m @@ -55,6 +55,9 @@ STAssertEqualObjects([string gtm_stringByEscapingForHTML], [NSString stringWithUTF8String:"abcا1ب<تdef&"], @"HTML escaping failed"); + + // test empty string + STAssertEqualObjects([@"" gtm_stringByEscapingForHTML], @"", nil); } // testStringByEscapingHTML - (void)testStringByEscapingAsciiHTML { diff --git a/Foundation/GTMNSString+Utilities.h b/Foundation/GTMNSString+Utilities.h deleted file mode 100644 index 3b4ee00..0000000 --- a/Foundation/GTMNSString+Utilities.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// GTMNSString+Utilities.h -// Misc NSString Utilities -// -// Copyright 2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#import <Foundation/Foundation.h> - -@interface NSString (GTMNSStringUtilitiesAdditions) - -// Returns a a UTF16 buffer. Avoids copying the data if at all -// possible for fastest possible/least memory access to the underlying -// unicode characters (UTF16). This returned buffer is NOT null terminated. -// *DANGER* -// Since we avoid copying data you can only be guaranteed access to -// the bytes of the data for the lifetime of the string that you have extracted -// the data from. This exists to allow speedy access to the underlying buffer -// and guaranteed memory cleanup if memory needs to be allocated. -// Do not free the returned pointer. -// -// Args: -// length - returns the number of unichars in the buffer. Send in nil if -// you don't care. -// -// Returns: -// pointer to the buffer. Nil on failure. -- (const unichar*)gtm_UTF16StringWithLength:(size_t*)length; -@end diff --git a/Foundation/GTMNSString+Utilities.m b/Foundation/GTMNSString+Utilities.m deleted file mode 100644 index 3419d43..0000000 --- a/Foundation/GTMNSString+Utilities.m +++ /dev/null @@ -1,48 +0,0 @@ -// -// GTMNSString+Utilities.m -// Misc NSString Utilities -// -// Copyright 2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - -#import "GTMDefines.h" -#import "GTMNSString+Utilities.h" - -@implementation NSString (GTMNSStringUtilitiesAdditions) - -- (const unichar*)gtm_UTF16StringWithLength:(size_t*)length { - size_t size = [self length]; - const UniChar *buffer = CFStringGetCharactersPtr((CFStringRef)self); - if (!buffer) { - size_t memsize = size * sizeof(UniChar); - - // nope, alloc buffer and fetch the chars ourselves - buffer = malloc(memsize); - if (!buffer) { - // COV_NF_BEGIN - Memory fail case - _GTMDevLog(@"couldn't alloc buffer"); - return nil; - // COV_NF_END - } - [self getCharacters:(void*)buffer]; - [NSData dataWithBytesNoCopy:(void*)buffer length:size]; - } - if (length) { - *length = size; - } - return buffer; -} - -@end diff --git a/Foundation/GTMNSString+UtilitiesTest.m b/Foundation/GTMNSString+UtilitiesTest.m deleted file mode 100644 index 8394aaf..0000000 --- a/Foundation/GTMNSString+UtilitiesTest.m +++ /dev/null @@ -1,62 +0,0 @@ -// -// GTMNSString+UtilitiesTest.m -// Misc NSString Utilities -// -// Copyright 2006-2008 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy -// of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -// - - -#import "GTMSenTestCase.h" -#import "GTMNSString+Utilities.h" - -@interface GTMNSString_UtilitiesTest : SenTestCase -@end - -@implementation GTMNSString_UtilitiesTest - -- (void)testStringWithLength { - NSString *string = @""; - size_t length; - const unichar *buffer = [string gtm_UTF16StringWithLength:&length]; - STAssertNotNULL(buffer, @"Buffer shouldn't be NULL"); - STAssertEquals(length, 0LU, @"Length should be 0"); - - UniChar unicharBytes[] = { 0x50, 0x51, 0x52 }; - string = [[[NSString alloc] initWithCharactersNoCopy:unicharBytes - length:3 - freeWhenDone:NO] autorelease]; - buffer = [string gtm_UTF16StringWithLength:&length]; - STAssertEquals(buffer, - (const unichar*)unicharBytes, - @"Pointers should be equal"); - STAssertEquals(length, - 3UL, - nil); - - char utf8Bytes[] = { 0x50, 0x51, 0x52, 0x0 }; - string = [NSString stringWithUTF8String:utf8Bytes]; - buffer = [string gtm_UTF16StringWithLength:&length]; - STAssertNotEquals(buffer, - (const unichar*)utf8Bytes, - @"Pointers should not be equal"); - STAssertEquals(length, - 3UL, - nil); - buffer = [string gtm_UTF16StringWithLength:nil]; - STAssertNotEquals(buffer, - (const unichar*)utf8Bytes, - @"Pointers should not be equal"); -} -@end diff --git a/Foundation/GTMNSString+XML.m b/Foundation/GTMNSString+XML.m index 7ea97b4..bc2f130 100644 --- a/Foundation/GTMNSString+XML.m +++ b/Foundation/GTMNSString+XML.m @@ -19,18 +19,17 @@ #import "GTMDefines.h" #import "GTMNSString+XML.h" #import "GTMGarbageCollection.h" -#import "GTMNSString+Utilities.h" -#import "GTMMethodCheck.h" - -typedef enum { - kGMXMLCharModeEncodeQUOT = 0, - kGMXMLCharModeEncodeAMP = 1, - kGMXMLCharModeEncodeAPOS = 2, - kGMXMLCharModeEncodeLT = 3, - kGMXMLCharModeEncodeGT = 4, - kGMXMLCharModeValid = 99, - kGMXMLCharModeInvalid = 100, -} GMXMLCharMode; + +enum { + kGTMXMLCharModeEncodeQUOT = 0, + kGTMXMLCharModeEncodeAMP = 1, + kGTMXMLCharModeEncodeAPOS = 2, + kGTMXMLCharModeEncodeLT = 3, + kGTMXMLCharModeEncodeGT = 4, + kGTMXMLCharModeValid = 99, + kGTMXMLCharModeInvalid = 100, +}; +typedef NSUInteger GTMXMLCharMode; static NSString *gXMLEntityList[] = { // this must match the above order @@ -41,7 +40,7 @@ static NSString *gXMLEntityList[] = { @">", }; -FOUNDATION_STATIC_INLINE GMXMLCharMode XMLModeForUnichar(UniChar c) { +FOUNDATION_STATIC_INLINE GTMXMLCharMode XMLModeForUnichar(UniChar c) { // Per XML spec Section 2.2 Characters // ( http://www.w3.org/TR/REC-xml/#charsets ) @@ -53,42 +52,42 @@ FOUNDATION_STATIC_INLINE GMXMLCharMode XMLModeForUnichar(UniChar c) { if (c >= 0x20) { switch (c) { case 34: - return kGMXMLCharModeEncodeQUOT; + return kGTMXMLCharModeEncodeQUOT; case 38: - return kGMXMLCharModeEncodeAMP; + return kGTMXMLCharModeEncodeAMP; case 39: - return kGMXMLCharModeEncodeAPOS; + return kGTMXMLCharModeEncodeAPOS; case 60: - return kGMXMLCharModeEncodeLT; + return kGTMXMLCharModeEncodeLT; case 62: - return kGMXMLCharModeEncodeGT; + return kGTMXMLCharModeEncodeGT; default: - return kGMXMLCharModeValid; + return kGTMXMLCharModeValid; } } else { if (c == '\n') - return kGMXMLCharModeValid; + return kGTMXMLCharModeValid; if (c == '\r') - return kGMXMLCharModeValid; + return kGTMXMLCharModeValid; if (c == '\t') - return kGMXMLCharModeValid; - return kGMXMLCharModeInvalid; + return kGTMXMLCharModeValid; + return kGTMXMLCharModeInvalid; } } if (c < 0xE000) - return kGMXMLCharModeInvalid; + return kGTMXMLCharModeInvalid; if (c <= 0xFFFD) - return kGMXMLCharModeValid; + return kGTMXMLCharModeValid; // UniChar can't have the following values // if (c < 0x10000) - // return kGMXMLCharModeInvalid; + // return kGTMXMLCharModeInvalid; // if (c <= 0x10FFFF) - // return kGMXMLCharModeValid; + // return kGTMXMLCharModeValid; - return kGMXMLCharModeInvalid; + return kGTMXMLCharModeInvalid; } // XMLModeForUnichar @@ -103,26 +102,42 @@ static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { // we can't use the CF call here because it leaves the invalid chars // in the string. - int length = [src length]; + NSUInteger length = [src length]; if (!length) { - return nil; + return src; } NSMutableString *finalString = [NSMutableString string]; - const UniChar *buffer = [src gtm_UTF16StringWithLength:nil]; - _GTMDevAssert(buffer, @"couldn't alloc buffer"); + + // this block is common between GTMNSString+HTML and GTMNSString+XML but + // it's so short that it isn't really worth trying to share. + const UniChar *buffer = CFStringGetCharactersPtr((CFStringRef)src); + if (!buffer) { + size_t memsize = length * sizeof(UniChar); + + // nope, alloc buffer and fetch the chars ourselves + buffer = malloc(memsize); + if (!buffer) { + // COV_NF_START - Memory fail case + _GTMDevLog(@"couldn't alloc buffer"); + return nil; + // COV_NF_END + } + [src getCharacters:(void*)buffer]; + [NSData dataWithBytesNoCopy:(void*)buffer length:memsize]; + } const UniChar *goodRun = buffer; - int goodRunLength = 0; + NSUInteger goodRunLength = 0; - for (int i = 0; i < length; ++i) { + for (NSUInteger i = 0; i < length; ++i) { - GMXMLCharMode cMode = XMLModeForUnichar(buffer[i]); + GTMXMLCharMode cMode = XMLModeForUnichar(buffer[i]); // valid chars go as is, and if we aren't doing entities, then // everything goes as is. - if ((cMode == kGMXMLCharModeValid) || - (!escaping && (cMode != kGMXMLCharModeInvalid))) { + if ((cMode == kGTMXMLCharModeValid) || + (!escaping && (cMode != kGTMXMLCharModeInvalid))) { // goes as is goodRunLength += 1; } else { @@ -137,7 +152,7 @@ static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { } // if it wasn't invalid, add the encoded version - if (cMode != kGMXMLCharModeInvalid) { + if (cMode != kGTMXMLCharModeInvalid) { // add this encoded [finalString appendString:gXMLEntityList[cMode]]; } @@ -157,7 +172,6 @@ static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { } // AutoreleasedCloneForXML @implementation NSString (GTMNSStringXMLAdditions) -GTM_METHOD_CHECK(NSString, gtm_UTF16StringWithLength:); - (NSString *)gtm_stringBySanitizingAndEscapingForXML { return AutoreleasedCloneForXML(self, YES); diff --git a/Foundation/GTMNSString+XMLTest.m b/Foundation/GTMNSString+XMLTest.m index 926708f..f1e964d 100644 --- a/Foundation/GTMNSString+XMLTest.m +++ b/Foundation/GTMNSString+XMLTest.m @@ -53,6 +53,9 @@ STAssertEqualObjects([ascString gtm_stringBySanitizingAndEscapingForXML], @"abcde\nf", @"Sanitize and Escape for XML from asc buffer failed"); + + // test empty string + STAssertEqualObjects([@"" gtm_stringBySanitizingAndEscapingForXML], @"", nil); } - (void)testStringBySanitizingToXMLSpec { @@ -82,6 +85,9 @@ STAssertEqualObjects([ascString gtm_stringBySanitizingToXMLSpec], @"abcde\nf", @"Sanitize and Escape for XML from asc buffer failed"); + + // test empty string + STAssertEqualObjects([@"" gtm_stringBySanitizingToXMLSpec], @"", nil); } @end diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h index 325a752..ab34cfb 100644 --- a/Foundation/GTMObjC2Runtime.h +++ b/Foundation/GTMObjC2Runtime.h @@ -16,8 +16,8 @@ // the License. // -#import <objc/objc-runtime.h> -#import <objc/Object.h> +#import <objc/objc-api.h> +#import "GTMDefines.h" // These functions exist for code that we want to compile on both the < 10.5 // sdks and on the >= 10.5 sdks without warnings. It basically reimplements @@ -35,6 +35,18 @@ #define AT_REQUIRED #endif +// The file objc-runtime.h was moved to runtime.h and in Leopard, objc-runtime.h +// was just a wrapper around runtime.h. For the iPhone SDK, this objc-runtime.h +// is removed in the iPhoneOS2.0 SDK. +// +// The |Object| class was removed in the iPhone2.0 SDK too. +#if GTM_IPHONE_SDK +#import <objc/runtime.h> +#else +#import <objc/objc-runtime.h> +#import <objc/Object.h> +#endif + #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 #import "objc/Protocol.h" diff --git a/Foundation/GTMObjC2Runtime.m b/Foundation/GTMObjC2Runtime.m index 00a3c6e..df3c9ca 100644 --- a/Foundation/GTMObjC2Runtime.m +++ b/Foundation/GTMObjC2Runtime.m @@ -44,7 +44,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *protocol) { struct objc_protocol_list *protos; for (protos = cls->protocols; protos != NULL; protos = protos->next) { - for (int i = 0; i < protos->count; i++) { + for (long i = 0; i < protos->count; i++) { if ([protos->list[i] conformsTo:protocol]) { return YES; } diff --git a/Foundation/GTMObjectSingleton.h b/Foundation/GTMObjectSingleton.h index 116d232..4763b68 100644 --- a/Foundation/GTMObjectSingleton.h +++ b/Foundation/GTMObjectSingleton.h @@ -26,7 +26,7 @@ /// /// Sample usage: /// -/// SINGLETON_BOILERPLATE(GMSomeUsefulManager, sharedSomeUsefulManager) +/// GTMOBJECT_SINGLETON_BOILERPLATE(SomeUsefulManager, sharedSomeUsefulManager) /// (with no trailing semicolon) /// #define GTMOBJECT_SINGLETON_BOILERPLATE(_object_name_, _shared_obj_name_) \ @@ -57,8 +57,8 @@ static _object_name_ *z##_shared_obj_name_ = nil; \ - (id)retain { \ return self; \ } \ -- (unsigned int)retainCount { \ - return UINT_MAX; \ +- (NSUInteger)retainCount { \ + return NSUIntegerMax; \ } \ - (void)release { \ } \ diff --git a/Foundation/GTMProgressMonitorInputStream.h b/Foundation/GTMProgressMonitorInputStream.h new file mode 100644 index 0000000..d3da5cd --- /dev/null +++ b/Foundation/GTMProgressMonitorInputStream.h @@ -0,0 +1,73 @@ +// +// GTMProgressMonitorInputStream.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <Foundation/Foundation.h> + +// The monitored input stream calls back into the monitor delegate +// with the number of bytes and total size +// +// - (void)inputStream:(GTMProgressMonitorInputStream *)stream +// hasDeliveredByteCount:(unsigned long long)numberOfBytesRead +// ofTotalByteCount:(unsigned long long)dataLength; + +@interface GTMProgressMonitorInputStream : NSInputStream { + + NSInputStream *inputStream_; // encapsulated stream that does the work + + unsigned long long dataSize_; // size of data in the source + unsigned long long numBytesRead_; // bytes read from the input stream so far + + __weak id monitorDelegate_; // WEAK, not retained + SEL monitorSelector_; + + __weak id monitorSource_; // WEAK, not retained +} + +// Length is passed to the progress callback; it may be zero if the progress +// callback can handle that (mainly meant so the monitor delegate can update the +// bounds/position for a progress indicator. ++ (id)inputStreamWithStream:(NSInputStream *)input + length:(unsigned long long)length; + +- (id)initWithStream:(NSInputStream *)input + length:(unsigned long long)length; + +// The monitor is called when bytes have been read +// +// monitorDelegate should respond to a selector with a signature matching: +// +// - (void)inputStream:(GTMProgressMonitorInputStream *)stream +// hasDeliveredBytes:(unsigned long long)numReadSoFar +// ofTotalBytes:(unsigned long long)total +// +// |total| will be the length passed when this GTMProgressMonitorInputStream was +// created. + +- (void)setMonitorDelegate:(id)monitorDelegate // not retained + selector:(SEL)monitorSelector; +- (id)monitorDelegate; +- (SEL)monitorSelector; + +// The source argument lets the delegate know the source of this input stream. +// this class does nothing w/ this, it's just here to provide context to your +// monitorDelegate. +- (void)setMonitorSource:(id)source; // not retained +- (id)monitorSource; + +@end + diff --git a/Foundation/GTMProgressMonitorInputStream.m b/Foundation/GTMProgressMonitorInputStream.m new file mode 100644 index 0000000..2336268 --- /dev/null +++ b/Foundation/GTMProgressMonitorInputStream.m @@ -0,0 +1,187 @@ +// +// GTMProgressMonitorInputStream.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMProgressMonitorInputStream.h" +#import "GTMDefines.h" +#import "GTMDebugSelectorValidation.h" + +@implementation GTMProgressMonitorInputStream + +// we'll forward all unhandled messages to the NSInputStream class +// or to the encapsulated input stream. This is needed +// for all messages sent to NSInputStream which aren't +// handled by our superclass; that includes various private run +// loop calls. ++ (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { + return [NSInputStream methodSignatureForSelector:selector]; +} + ++ (void)forwardInvocation:(NSInvocation*)invocation { + [invocation invokeWithTarget:[NSInputStream class]]; +} + +- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { + return [inputStream_ methodSignatureForSelector:selector]; +} + +- (void)forwardInvocation:(NSInvocation*)invocation { + [invocation invokeWithTarget:inputStream_]; +} + +#pragma mark - + ++ (id)inputStreamWithStream:(NSInputStream *)input + length:(unsigned long long)length { + + return [[[self alloc] initWithStream:input + length:length] autorelease]; +} + +- (id)initWithStream:(NSInputStream *)input + length:(unsigned long long)length { + + if ((self = [super init]) != nil) { + + inputStream_ = [input retain]; + dataSize_ = length; + } + return self; +} + +- (id)init { + _GTMDevAssert(NO, @"should call initWithStream:length:"); + return nil; +} + +- (void)dealloc { + [inputStream_ release]; + [super dealloc]; +} + +#pragma mark - + + +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { + + NSInteger numRead = [inputStream_ read:buffer maxLength:len]; + + if (numRead > 0) { + + numBytesRead_ += numRead; + + if (monitorDelegate_ && monitorSelector_) { + + // call the monitor delegate with the number of bytes read and the + // total bytes read + + NSMethodSignature *signature = [monitorDelegate_ methodSignatureForSelector:monitorSelector_]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setSelector:monitorSelector_]; + [invocation setTarget:monitorDelegate_]; + [invocation setArgument:&self atIndex:2]; + [invocation setArgument:&numBytesRead_ atIndex:3]; + [invocation setArgument:&dataSize_ atIndex:4]; + [invocation invoke]; + } + } + return numRead; +} + +- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { + return [inputStream_ getBuffer:buffer length:len]; +} + +- (BOOL)hasBytesAvailable { + return [inputStream_ hasBytesAvailable]; +} + +#pragma mark Standard messages + +// Pass expected messages to our encapsulated stream. +// +// We want our encapsulated NSInputStream to handle the standard messages; +// we don't want the superclass to handle them. +- (void)open { + [inputStream_ open]; +} + +- (void)close { + [inputStream_ close]; +} + +- (id)delegate { + return [inputStream_ delegate]; +} + +- (void)setDelegate:(id)delegate { + [inputStream_ setDelegate:delegate]; +} + +- (id)propertyForKey:(NSString *)key { + return [inputStream_ propertyForKey:key]; +} +- (BOOL)setProperty:(id)property forKey:(NSString *)key { + return [inputStream_ setProperty:property forKey:key]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { + [inputStream_ scheduleInRunLoop:aRunLoop forMode:mode]; +} +- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { + [inputStream_ removeFromRunLoop:aRunLoop forMode:mode]; +} + +- (NSStreamStatus)streamStatus { + return [inputStream_ streamStatus]; +} + +- (NSError *)streamError { + return [inputStream_ streamError]; +} + +#pragma mark Setters and getters + +- (void)setMonitorDelegate:(id)monitorDelegate + selector:(SEL)monitorSelector { + monitorDelegate_ = monitorDelegate; // non-retained + monitorSelector_ = monitorSelector; + GTMAssertSelectorNilOrImplementedWithArguments(monitorDelegate, + monitorSelector, + @encode(GTMProgressMonitorInputStream *), + @encode(unsigned long long), + @encode(unsigned long long), + NULL); +} + +- (id)monitorDelegate { + return monitorDelegate_; +} + +- (SEL)monitorSelector { + return monitorSelector_; +} + +- (void)setMonitorSource:(id)source { + monitorSource_ = source; // non-retained +} + +- (id)monitorSource { + return monitorSource_; +} + +@end diff --git a/Foundation/GTMRegex.h b/Foundation/GTMRegex.h index ee56b98..75cffe2 100644 --- a/Foundation/GTMRegex.h +++ b/Foundation/GTMRegex.h @@ -18,9 +18,10 @@ #import <Foundation/Foundation.h> #import <regex.h> +#import "GTMDefines.h" /// Options for controlling the behavior of the matches -typedef enum { +enum { kGTMRegexOptionIgnoreCase = 0x01, // Ignore case in matching, ie: 'a' matches 'a' or 'A' @@ -48,7 +49,8 @@ typedef enum { // and would also match // fooAAA\nbar -} GTMRegexOptions; +}; +typedef NSUInteger GTMRegexOptions; /// Global contants needed for errors from consuming patterns @@ -148,7 +150,7 @@ _EXTERN NSString* kGTMRegexPatternErrorErrorString _INITIALIZE_AS(@"patternError // Sub Patterns are basically the number of parenthesis blocks w/in the pattern. // ie: The pattern "foo((bar)|(baz))" has 3 sub patterns. // -- (int)subPatternCount; +- (NSUInteger)subPatternCount; /// Returns YES if the whole string |str| matches the pattern. - (BOOL)matchesString:(NSString *)str; @@ -266,7 +268,7 @@ _EXTERN NSString* kGTMRegexPatternErrorErrorString _INITIALIZE_AS(@"patternError @private NSData *utf8StrBuf_; regmatch_t *regMatches_; // STRONG: ie-we call free - int numRegMatches_; + NSUInteger numRegMatches_; BOOL isMatch_; } @@ -296,7 +298,7 @@ _EXTERN NSString* kGTMRegexPatternErrorErrorString _INITIALIZE_AS(@"patternError // 4: nil // 5: "baz" // -- (NSString *)subPatternString:(int)index; +- (NSString *)subPatternString:(NSUInteger)index; @end diff --git a/Foundation/GTMRegex.m b/Foundation/GTMRegex.m index c50ff2b..92eb576 100644 --- a/Foundation/GTMRegex.m +++ b/Foundation/GTMRegex.m @@ -47,7 +47,7 @@ static NSString *const kReplacementPattern = BOOL allSegments_; BOOL treatStartOfNewSegmentAsBeginningOfString_; regoff_t curParseIndex_; - regmatch_t *savedRegMatches_; + __strong regmatch_t *savedRegMatches_; } - (id)initWithRegex:(GTMRegex *)regex processString:(NSString *)str @@ -58,7 +58,7 @@ static NSString *const kReplacementPattern = @interface GTMRegexStringSegment (PrivateMethods) - (id)initWithUTF8StrBuf:(NSData *)utf8StrBuf regMatches:(regmatch_t *)regMatches - numRegMatches:(int)numRegMatches + numRegMatches:(NSUInteger)numRegMatches isMatch:(BOOL)isMatch; @end @@ -89,10 +89,10 @@ static NSString *const kReplacementPattern = // a unichar buffer and scanning that, along w/ pushing the data over in // chunks (when possible). - unsigned int len = [str length]; + NSUInteger len = [str length]; NSMutableString *result = [NSMutableString stringWithCapacity:len]; - for (unsigned int x = 0; x < len; ++x) { + for (NSUInteger x = 0; x < len; ++x) { unichar ch = [str characterAtIndex:x]; switch (ch) { case '^': @@ -190,6 +190,17 @@ static NSString *const kReplacementPattern = return self; } +- (void)finalize { + // we used pattern_ as our flag that we initialized the regex_t + if (pattern_) { + regfree(®exData_); + [pattern_ release]; + // play it safe and clear it since we use it as a flag for regexData_ + pattern_ = nil; + } + [super finalize]; +} + - (void)dealloc { // we used pattern_ as our flag that we initialized the regex_t if (pattern_) { @@ -201,7 +212,7 @@ static NSString *const kReplacementPattern = [super dealloc]; } -- (int)subPatternCount { +- (NSUInteger)subPatternCount { return regexData_.re_nsub; } @@ -223,7 +234,7 @@ static NSString *const kReplacementPattern = - (NSArray *)subPatternsOfString:(NSString *)str { NSArray *result = nil; - int count = regexData_.re_nsub + 1; + NSUInteger count = regexData_.re_nsub + 1; regmatch_t *regMatches = malloc(sizeof(regmatch_t) * count); if (!regMatches) return nil; // COV_NF_LINE - no real way to force this in a unittest @@ -243,22 +254,22 @@ static NSString *const kReplacementPattern = if ((regMatches[0].rm_so != 0) || (regMatches[0].rm_eo != [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding])) { // only matched a sub part of the string - return NO; + return nil; } NSMutableArray *buildResult = [NSMutableArray arrayWithCapacity:count]; - for (int x = 0 ; x < count ; ++x) { + for (NSUInteger x = 0 ; x < count ; ++x) { if ((regMatches[x].rm_so == -1) && (regMatches[x].rm_eo == -1)) { // add NSNull since it wasn't used [buildResult addObject:[NSNull null]]; } else { // fetch the string const char *base = utf8Str + regMatches[x].rm_so; - unsigned len = regMatches[x].rm_eo - regMatches[x].rm_so; + regoff_t len = regMatches[x].rm_eo - regMatches[x].rm_so; NSString *sub = [[[NSString alloc] initWithBytes:base - length:len + length:(NSUInteger)len encoding:NSUTF8StringEncoding] autorelease]; [buildResult addObject:sub]; } @@ -284,10 +295,10 @@ static NSString *const kReplacementPattern = flags:0]) { // fetch the string const char *base = utf8Str + regMatch.rm_so; - unsigned len = regMatch.rm_eo - regMatch.rm_so; + regoff_t len = regMatch.rm_eo - regMatch.rm_so; result = [[[NSString alloc] initWithBytes:base - length:len + length:(NSUInteger)len encoding:NSUTF8StringEncoding] autorelease]; } return result; @@ -497,6 +508,7 @@ static NSString *const kReplacementPattern = return self; } +// Don't need a finalize because savedRegMatches_ is marked __strong - (void)dealloc { if (savedRegMatches_) { free(savedRegMatches_); @@ -584,7 +596,7 @@ static NSString *const kReplacementPattern = isMatch = NO; // mark everything but the zero slot w/ not used - for (int x = [regex_ subPatternCount]; x > 0; --x) { + for (NSUInteger x = [regex_ subPatternCount]; x > 0; --x) { nextMatches[x].rm_so = nextMatches[x].rm_eo = -1; } nextMatches[0].rm_so = curParseIndex_; @@ -611,7 +623,7 @@ static NSString *const kReplacementPattern = if (allSegments_) { isMatch = NO; // mark everything but the zero slot w/ not used - for (int x = [regex_ subPatternCount]; x > 0; --x) { + for (NSUInteger x = [regex_ subPatternCount]; x > 0; --x) { nextMatches[x].rm_so = nextMatches[x].rm_eo = -1; } nextMatches[0].rm_so = curParseIndex_; @@ -685,7 +697,7 @@ static NSString *const kReplacementPattern = return [self subPatternString:0]; } -- (NSString *)subPatternString:(int)index { +- (NSString *)subPatternString:(NSUInteger)index { if ((index < 0) || (index > numRegMatches_)) return nil; @@ -695,9 +707,9 @@ static NSString *const kReplacementPattern = // fetch the string const char *base = (const char*)[utf8StrBuf_ bytes] + regMatches_[index].rm_so; - unsigned len = regMatches_[index].rm_eo - regMatches_[index].rm_so; + regoff_t len = regMatches_[index].rm_eo - regMatches_[index].rm_so; return [[[NSString alloc] initWithBytes:base - length:len + length:(NSUInteger)len encoding:NSUTF8StringEncoding] autorelease]; } @@ -705,7 +717,7 @@ static NSString *const kReplacementPattern = NSMutableString *result = [NSMutableString stringWithFormat:@"%@<%p> { isMatch=\"%s\", subPatterns=(", [self class], self, (isMatch_ ? "YES" : "NO")]; - for (int x = 0; x <= numRegMatches_; ++x) { + for (NSUInteger x = 0; x <= numRegMatches_; ++x) { NSString *format = @", \"%.*s\""; if (x == 0) format = @" \"%.*s\""; @@ -725,7 +737,7 @@ static NSString *const kReplacementPattern = - (id)initWithUTF8StrBuf:(NSData *)utf8StrBuf regMatches:(regmatch_t *)regMatches - numRegMatches:(int)numRegMatches + numRegMatches:(NSUInteger)numRegMatches isMatch:(BOOL)isMatch { self = [super init]; if (!self) return nil; diff --git a/Foundation/GTMRegexTest.m b/Foundation/GTMRegexTest.m index 22c571e..033b560 100644 --- a/Foundation/GTMRegexTest.m +++ b/Foundation/GTMRegexTest.m @@ -71,7 +71,7 @@ withError:&error] autorelease], nil); STAssertNotNil(error, nil); STAssertEqualObjects([error domain], kGTMRegexErrorDomain, nil); - STAssertEquals([error code], kGTMRegexPatternParseFailedError, nil); + STAssertEquals([error code], (NSInteger)kGTMRegexPatternParseFailedError, nil); NSDictionary *userInfo = [error userInfo]; STAssertNotNil(userInfo, @"failed to get userInfo from error"); STAssertEqualObjects([userInfo objectForKey:kGTMRegexPatternErrorPattern], @"(.", nil); @@ -106,7 +106,7 @@ withError:&error], nil); STAssertNotNil(error, nil); STAssertEqualObjects([error domain], kGTMRegexErrorDomain, nil); - STAssertEquals([error code], kGTMRegexPatternParseFailedError, nil); + STAssertEquals([error code], (NSInteger)kGTMRegexPatternParseFailedError, nil); userInfo = [error userInfo]; STAssertNotNil(userInfo, @"failed to get userInfo from error"); STAssertEqualObjects([userInfo objectForKey:kGTMRegexPatternErrorPattern], @"(.", nil); @@ -385,11 +385,11 @@ } - (void)testSubPatternCount { - STAssertEquals(0, [[GTMRegex regexWithPattern:@".*"] subPatternCount], nil); - STAssertEquals(1, [[GTMRegex regexWithPattern:@"(.*)"] subPatternCount], nil); - STAssertEquals(1, [[GTMRegex regexWithPattern:@"[fo]*(.*)[bar]*"] subPatternCount], nil); - STAssertEquals(3, [[GTMRegex regexWithPattern:@"([fo]*)(.*)([bar]*)"] subPatternCount], nil); - STAssertEquals(7, [[GTMRegex regexWithPattern:@"(([bar]*)|([fo]*))(.*)(([bar]*)|([fo]*))"] subPatternCount], nil); + STAssertEquals((NSUInteger)0, [[GTMRegex regexWithPattern:@".*"] subPatternCount], nil); + STAssertEquals((NSUInteger)1, [[GTMRegex regexWithPattern:@"(.*)"] subPatternCount], nil); + STAssertEquals((NSUInteger)1, [[GTMRegex regexWithPattern:@"[fo]*(.*)[bar]*"] subPatternCount], nil); + STAssertEquals((NSUInteger)3, [[GTMRegex regexWithPattern:@"([fo]*)(.*)([bar]*)"] subPatternCount], nil); + STAssertEquals((NSUInteger)7, [[GTMRegex regexWithPattern:@"(([bar]*)|([fo]*))(.*)(([bar]*)|([fo]*))"] subPatternCount], nil); } - (void)testMatchesString { @@ -418,15 +418,15 @@ - (void)testSubPatternsOfString { GTMRegex *regex = [GTMRegex regexWithPattern:@"(fo(o+))((bar)|(baz))"]; STAssertNotNil(regex, nil); - STAssertEquals(5, [regex subPatternCount], nil); + STAssertEquals((NSUInteger)5, [regex subPatternCount], nil); NSArray *subPatterns = [regex subPatternsOfString:@"foooooobaz"]; STAssertNotNil(subPatterns, nil); - STAssertEquals(6U, [subPatterns count], nil); + STAssertEquals((NSUInteger)6, [subPatterns count], nil); STAssertEqualStrings(@"foooooobaz", [subPatterns objectAtIndex:0], nil); STAssertEqualStrings(@"foooooo", [subPatterns objectAtIndex:1], nil); STAssertEqualStrings(@"ooooo", [subPatterns objectAtIndex:2], nil); STAssertEqualStrings(@"baz", [subPatterns objectAtIndex:3], nil); - STAssertTrue(([NSNull null] == [subPatterns objectAtIndex:4]), nil); + STAssertEqualObjects([NSNull null], [subPatterns objectAtIndex:4], nil); STAssertEqualStrings(@"baz", [subPatterns objectAtIndex:5], nil); // not there @@ -562,7 +562,7 @@ // now test the saved sub segments regex = [GTMRegex regexWithPattern:@"(foo)((bar)|(baz))"]; STAssertNotNil(regex, nil); - STAssertEquals(4, [regex subPatternCount], nil); + STAssertEquals((NSUInteger)4, [regex subPatternCount], nil); enumerator = [regex segmentEnumeratorForString:@"foobarxxfoobaz"]; STAssertNotNil(enumerator, nil); // "foobar" @@ -605,7 +605,7 @@ STAssertNotNil(enumerator, nil); NSArray *allSegments = [enumerator allObjects]; STAssertNotNil(allSegments, nil); - STAssertEquals(6U, [allSegments count], nil); + STAssertEquals((NSUInteger)6, [allSegments count], nil); // test we are getting the flags right for newline regex = [GTMRegex regexWithPattern:@"^a"]; @@ -737,7 +737,7 @@ // now test the saved sub segments regex = [GTMRegex regexWithPattern:@"(foo)((bar)|(baz))"]; STAssertNotNil(regex, nil); - STAssertEquals(4, [regex subPatternCount], nil); + STAssertEquals((NSUInteger)4, [regex subPatternCount], nil); enumerator = [regex matchSegmentEnumeratorForString:@"foobarxxfoobaz"]; STAssertNotNil(enumerator, nil); // "foobar" @@ -774,7 +774,7 @@ STAssertNotNil(enumerator, nil); NSArray *allSegments = [enumerator allObjects]; STAssertNotNil(allSegments, nil); - STAssertEquals(3U, [allSegments count], nil); + STAssertEquals((NSUInteger)3, [allSegments count], nil); // test we are getting the flags right for newline regex = [GTMRegex regexWithPattern:@"^a"]; @@ -880,23 +880,23 @@ // default options GTMRegex *regex = [GTMRegex regexWithPattern:@"a+"]; STAssertNotNil(regex, nil); - STAssertGreaterThan([[regex description] length], 10U, + STAssertGreaterThan([[regex description] length], (NSUInteger)10, @"failed to get a reasonable description for regex"); // enumerator NSEnumerator *enumerator = [regex segmentEnumeratorForString:@"aaabbbccc"]; STAssertNotNil(enumerator, nil); - STAssertGreaterThan([[enumerator description] length], 10U, + STAssertGreaterThan([[enumerator description] length], (NSUInteger)10, @"failed to get a reasonable description for regex enumerator"); // string segment GTMRegexStringSegment *seg = [enumerator nextObject]; STAssertNotNil(seg, nil); - STAssertGreaterThan([[seg description] length], 10U, + STAssertGreaterThan([[seg description] length], (NSUInteger)10, @"failed to get a reasonable description for regex string segment"); // regex w/ other options regex = [GTMRegex regexWithPattern:@"a+" options:(kGTMRegexOptionIgnoreCase | kGTMRegexOptionSupressNewlineSupport)]; STAssertNotNil(regex, nil); - STAssertGreaterThan([[regex description] length], 10U, + STAssertGreaterThan([[regex description] length], (NSUInteger)10, @"failed to get a reasonable description for regex w/ options"); } @@ -926,12 +926,12 @@ - (void)testSubPatternsOfPattern { NSArray *subPatterns = [@"foooooobaz" gtm_subPatternsOfPattern:@"(fo(o+))((bar)|(baz))"]; STAssertNotNil(subPatterns, nil); - STAssertEquals(6U, [subPatterns count], nil); + STAssertEquals((NSUInteger)6, [subPatterns count], nil); STAssertEqualStrings(@"foooooobaz", [subPatterns objectAtIndex:0], nil); STAssertEqualStrings(@"foooooo", [subPatterns objectAtIndex:1], nil); STAssertEqualStrings(@"ooooo", [subPatterns objectAtIndex:2], nil); STAssertEqualStrings(@"baz", [subPatterns objectAtIndex:3], nil); - STAssertTrue(([NSNull null] == [subPatterns objectAtIndex:4]), nil); + STAssertEqualObjects([NSNull null], [subPatterns objectAtIndex:4], nil); STAssertEqualStrings(@"baz", [subPatterns objectAtIndex:5], nil); // not there @@ -1089,7 +1089,7 @@ STAssertNotNil(enumerator, nil); NSArray *allSegments = [enumerator allObjects]; STAssertNotNil(allSegments, nil); - STAssertEquals(6U, [allSegments count], nil); + STAssertEquals((NSUInteger)6, [allSegments count], nil); } - (void)testMatchSegmentEnumeratorForPattern { @@ -1170,14 +1170,14 @@ STAssertNotNil(enumerator, nil); NSArray *allSegments = [enumerator allObjects]; STAssertNotNil(allSegments, nil); - STAssertEquals(3U, [allSegments count], nil); + STAssertEquals((NSUInteger)3, [allSegments count], nil); } - (void)testAllSubstringsMatchedByPattern { NSArray *segments = [@"afoobarbfooobaarfoobarzz" gtm_allSubstringsMatchedByPattern:@"foo+ba+r"]; STAssertNotNil(segments, nil); - STAssertEquals(3U, [segments count], nil); + STAssertEquals((NSUInteger)3, [segments count], nil); STAssertEqualStrings([segments objectAtIndex:0], @"foobar", nil); STAssertEqualStrings([segments objectAtIndex:1], @"fooobaar", nil); STAssertEqualStrings([segments objectAtIndex:2], @"foobar", nil); @@ -1185,12 +1185,12 @@ // test no match segments = [@"aaa" gtm_allSubstringsMatchedByPattern:@"foo+ba+r"]; STAssertNotNil(segments, nil); - STAssertEquals(0U, [segments count], nil); + STAssertEquals((NSUInteger)0, [segments count], nil); // test only match segments = [@"foobar" gtm_allSubstringsMatchedByPattern:@"foo+ba+r"]; STAssertNotNil(segments, nil); - STAssertEquals(1U, [segments count], nil); + STAssertEquals((NSUInteger)1, [segments count], nil); STAssertEqualStrings([segments objectAtIndex:0], @"foobar", nil); } diff --git a/Foundation/GTMScriptRunnerTest.m b/Foundation/GTMScriptRunnerTest.m index a4497b6..5229800 100644 --- a/Foundation/GTMScriptRunnerTest.m +++ b/Foundation/GTMScriptRunnerTest.m @@ -215,7 +215,7 @@ // make sure description doesn't choke GTMScriptRunner *sr = [GTMScriptRunner runner]; STAssertNotNil(sr, @"Script runner must not be nil"); - STAssertGreaterThan([[sr description] length], 10U, + STAssertGreaterThan([[sr description] length], (NSUInteger)10, @"expected a description of at least 10 chars"); } diff --git a/Foundation/GTMSystemVersion.h b/Foundation/GTMSystemVersion.h index 13c3c19..0f19596 100644 --- a/Foundation/GTMSystemVersion.h +++ b/Foundation/GTMSystemVersion.h @@ -43,6 +43,6 @@ // Returns a YES/NO if the system is 10.5 or better + (BOOL)isLeopardOrGreater; -#endif // GTM_IPHONE_SDK +#endif // GTM_MACOS_SDK @end diff --git a/Foundation/GTMSystemVersion.m b/Foundation/GTMSystemVersion.m index a2e4d7b..da767ae 100644 --- a/Foundation/GTMSystemVersion.m +++ b/Foundation/GTMSystemVersion.m @@ -17,20 +17,55 @@ // #import "GTMSystemVersion.h" +#if GTM_MACOS_SDK +#import <Carbon/Carbon.h> +#endif -static int sGTMSystemVersionMajor = 0; -static int sGTMSystemVersionMinor = 0; -static int sGTMSystemVersionBugFix = 0; +static SInt32 sGTMSystemVersionMajor = 0; +static SInt32 sGTMSystemVersionMinor = 0; +static SInt32 sGTMSystemVersionBugFix = 0; @implementation GTMSystemVersion + (void)initialize { if (self == [GTMSystemVersion class]) { + // Gestalt is the recommended way of getting the OS version (despite a + // comment to the contrary in the 10.4 headers and docs; see + // <http://lists.apple.com/archives/carbon-dev/2007/Aug/msg00089.html>). + // The iPhone doesn't have Gestalt though, so use the plist there. +#if GTM_MACOS_SDK + require_noerr(Gestalt(gestaltSystemVersionMajor, &sGTMSystemVersionMajor), failedGestalt); + require_noerr(Gestalt(gestaltSystemVersionMinor, &sGTMSystemVersionMinor), failedGestalt); + require_noerr(Gestalt(gestaltSystemVersionBugFix, &sGTMSystemVersionBugFix), failedGestalt); + + return; + + failedGestalt: + ; +#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_3 + // gestaltSystemVersionMajor et al are only on 10.4 and above, so they + // could fail when running on 10.3. + SInt32 binaryCodedDec; + OSStatus err = err = Gestalt(gestaltSystemVersion, &binaryCodedDec); + _GTMDevAssert(!err, @"Unable to get version from Gestalt"); + + // Note that this code will return x.9.9 for any system rev parts that are + // greater than 9 (i.e., 10.10.10 will be 10.9.9). This shouldn't ever be a + // problem as the code above takes care of 10.4+. + int msb = (binaryCodedDec & 0x0000F000L) >> 12; + msb *= 10; + int lsb = (binaryCodedDec & 0x00000F00L) >> 8; + sGTMSystemVersionMajor = msb + lsb; + sGTMSystemVersionMinor = (binaryCodedDec & 0x000000F0L) >> 4; + sGTMSystemVersionBugFix = (binaryCodedDec & 0x0000000FL); +#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_3 + +#else // GTM_MACOS_SDK NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSDictionary *systemVersionPlist = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; NSString *version = [systemVersionPlist objectForKey:@"ProductVersion"]; _GTMDevAssert(version, @"Unable to get version"); NSArray *versionInfo = [version componentsSeparatedByString:@"."]; - int length = [versionInfo count]; + NSUInteger length = [versionInfo count]; _GTMDevAssert(length > 1 && length < 4, @"Unparseable version %@", version); sGTMSystemVersionMajor = [[versionInfo objectAtIndex:0] intValue]; _GTMDevAssert(sGTMSystemVersionMajor != 0, @"Unknown version for %@", version); @@ -39,6 +74,7 @@ static int sGTMSystemVersionBugFix = 0; sGTMSystemVersionBugFix = [[versionInfo objectAtIndex:2] intValue]; } [pool release]; +#endif // GTM_MACOS_SDK } } @@ -82,6 +118,6 @@ static int sGTMSystemVersionBugFix = 0; (sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor >= 5); } -#endif // GTM_IPHONE_SDK +#endif // GTM_MACOS_SDK @end diff --git a/Foundation/TestData/GTMHTTPFetcherTestPage.html b/Foundation/TestData/GTMHTTPFetcherTestPage.html new file mode 100644 index 0000000..1f44469 --- /dev/null +++ b/Foundation/TestData/GTMHTTPFetcherTestPage.html @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test Page</title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +</head> +<body> +This is a simple test page +</body> +</html> diff --git a/Foundation/TestData/GTMHTTPFetcherTestServer b/Foundation/TestData/GTMHTTPFetcherTestServer new file mode 100755 index 0000000..838c110 --- /dev/null +++ b/Foundation/TestData/GTMHTTPFetcherTestServer @@ -0,0 +1,274 @@ +#!/usr/bin/python +# +# Copyright 2006-2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# A simple server for testing the http calls + +"""A simple BaseHTTPRequestHandler for unit testing GTM Network code + +This http server is for use by GTMHTTPFetcherTest.m in testing +both authentication and object retrieval. + +Requests to the path /accounts/ClientLogin are assumed to be +for login; other requests are for object retrieval +""" + +__author__ = 'Google Inc.' + +import string +import cgi +import time +import os +import sys +import re +import mimetypes +import socket +from BaseHTTPServer import BaseHTTPRequestHandler +from BaseHTTPServer import HTTPServer +from optparse import OptionParser + +class ServerTimeoutException(Exception): + pass + + +class TimeoutServer(HTTPServer): + + """HTTP server for testing GTM network requests. + + This server will throw an exception if it receives no connections for + several minutes. We use this to ensure that the server will be cleaned + up if something goes wrong during the unit testing. + """ + + def get_request(self): + self.socket.settimeout(120.0) + result = None + while result is None: + try: + result = self.socket.accept() + except socket.timeout: + raise ServerTimeoutException + result[0].settimeout(None) + return result + + +class SimpleServer(BaseHTTPRequestHandler): + + """HTTP request handler for testing GTM network requests. + + This is an implementation of a request handler for BaseHTTPServer, + specifically designed for GTM network code usage. + + Normal requests for GET/POST/PUT simply retrieve the file from the + supplied path, starting in the current directory. A cookie called + TestCookie is set by the response header, with the value of the filename + requested. + + DELETE requests always succeed. + + Appending ?status=n results in a failure with status value n. + + Paths ending in .auth have the .auth extension stripped, and must have + an authorization header of "GoogleLogin auth=GoodAuthToken" to succeed. + + Successful results have a Last-Modified header set; if that header's value + ("thursday") is supplied in a request's "If-Modified-Since" header, the + result is 304 (Not Modified). + + Requests to /accounts/ClientLogin will fail if supplied with a body + containing Passwd=bad. If they contain logintoken and logincaptcha values, + those must be logintoken=CapToken&logincaptch=good to succeed. + """ + + def do_GET(self): + self.doAllRequests() + + def do_POST(self): + self.doAllRequests() + + def do_PUT(self): + self.doAllRequests() + + def do_DELETE(self): + self.doAllRequests() + + def doAllRequests(self): + # This method handles all expected incoming requests + # + # Requests to path /accounts/ClientLogin are assumed to be for signing in + # + # Other paths are for retrieving a local file. An .auth appended + # to a file path will require authentication (meaning the Authorization + # header must be present with the value "GoogleLogin auth=GoodAuthToken".) + # If the token is present, the file (without uthe .auth at the end) will + # be returned. + # + # HTTP Delete commands succeed but return no data. + # + # GData override headers (putting the verb in X-HTTP-Method-Override) + # are supported. + # + # Any auth password is valid except "bad", which will fail, and "captcha", + # which will fail unless the authentication request's post string includes + # "logintoken=CapToken&logincaptcha=good" + + # We will use a readable default result string since it should never show up + # in output + resultString = "default GTMHTTPFetcherTestServer result\n"; + resultStatus = 0 + headerType = "text/plain" + postString = "" + modifiedDate = "thursday" # clients should treat dates as opaque, generally + + # auth queries and some others may include post data + postLength = int(self.headers.getheader("Content-Length", "0")); + if postLength > 0: + postString = self.rfile.read(postLength) + + # auth queries and some GData queries include post data + ifModifiedSince = self.headers.getheader("If-Modified-Since", ""); + + # retrieve the auth header; require it if the file path ends + # with the string ".auth" + authorization = self.headers.getheader("Authorization", "") + if self.path.endswith(".auth"): + if authorization != "GoogleLogin auth=GoodAuthToken": + self.send_error(401,"Unauthorized: %s" % self.path) + return + self.path = self.path[:-5] # remove the .auth at the end + + overrideHeader = self.headers.getheader("X-HTTP-Method-Override", "") + httpCommand = self.command + if httpCommand == "POST" and len(overrideHeader) > 0: + httpCommand = overrideHeader + + try: + if self.path.endswith("/accounts/ClientLogin"): + # + # it's a sign-in attempt; it's good unless the password is "bad" or + # "captcha" + # + + # use regular expression to find the password + password = "" + searchResult = re.search("(Passwd=)([^&\n]*)", postString) + if searchResult: + password = searchResult.group(2) + + if password == "bad": + resultString = "Error=BadAuthentication\n" + resultStatus = 403 + + elif password == "captcha": + logintoken = "" + logincaptcha = "" + + # use regular expressions to find the captcha token and answer + searchResult = re.search("(logintoken=)([^&\n]*)", postString); + if searchResult: + logintoken = searchResult.group(2) + + searchResult = re.search("(logincaptcha=)([^&\n]*)", postString); + if searchResult: + logincaptcha = searchResult.group(2) + + # if the captcha token is "CapToken" and the answer is "good" + # then it's a valid sign in + if (logintoken == "CapToken") and (logincaptcha == "good"): + resultString = "SID=GoodSID\nLSID=GoodLSID\nAuth=GoodAuthToken\n" + resultStatus = 200 + else: + # incorrect captcha token or answer provided + resultString = ("Error=CaptchaRequired\nCaptchaToken=CapToken" + "\nCaptchaUrl=CapUrl\n") + resultStatus = 403 + + else: + # valid username/password + resultString = "SID=GoodSID\nLSID=GoodLSID\nAuth=GoodAuthToken\n" + resultStatus = 200 + + elif httpCommand == "DELETE": + # + # it's an object delete; read and return empty data + # + resultString = "" + resultStatus = 200 + headerType = "text/plain" + + else: + # queries that have something like "?status=456" should fail with the + # status code + searchResult = re.search("(status=)([0-9]+)", self.path) + if searchResult: + status = searchResult.group(2) + self.send_error(int(status), + "Test HTTP server status parameter: %s" % self.path) + return + + # if the client gave us back our not modified date, then say there's no + # change in the response + if ifModifiedSince == modifiedDate: + self.send_response(304) # Not Modified + return + + else: + # + # it's a file fetch; read and return the data file + # + f = open("." + self.path) + resultString = f.read() + f.close() + resultStatus = 200 + fileTypeInfo = mimetypes.guess_type("." + self.path) + headerType = fileTypeInfo[0] # first part of the tuple is mime type + + self.send_response(resultStatus) + self.send_header("Content-type", headerType) + self.send_header("Last-Modified", modifiedDate) + + cookieValue = os.path.basename("." + self.path) + self.send_header('Set-Cookie', 'TestCookie=%s' % cookieValue) + self.end_headers() + self.wfile.write(resultString) + + except IOError: + self.send_error(404,"File Not Found: %s" % self.path) + + +def main(): + try: + parser = OptionParser() + parser.add_option("-p", "--port", dest="port", help="Port to run server on", + type="int", default="80") + parser.add_option("-r", "--root", dest="root", help="Where to root server", + default=".") + (options, args) = parser.parse_args() + os.chdir(options.root) + server = TimeoutServer(("127.0.0.1", options.port), SimpleServer) + sys.stdout.write("started GTMHTTPFetcherTestServer," + " serving files from root directory %s..." % os.getcwd()); + sys.stdout.flush(); + server.serve_forever() + except KeyboardInterrupt: + print "^C received, shutting down server" + server.socket.close() + except ServerTimeoutException: + print "Too long since the last request, shutting down server" + server.socket.close() + + +if __name__ == "__main__": + main() diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj index 21685f0..d279b99 100644 --- a/GTM.xcodeproj/project.pbxproj +++ b/GTM.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 42; + objectVersion = 44; objects = { /* Begin PBXAggregateTarget section */ @@ -24,18 +24,15 @@ /* Begin PBXBuildFile section */ 8B2A9B200D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B2A9B1D0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m */; }; - 8B2A9B220D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B2A9B1F0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h */; }; - 8B2A9B230D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B2A9B1D0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m */; }; + 8B2A9B220D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B2A9B1F0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8B2A9B240D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B2A9B1E0D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m */; }; 8B2A9BEC0D82714A00599386 /* ScreenSaver.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2A9BEB0D82714A00599386 /* ScreenSaver.framework */; }; - 8B2A9BED0D82714A00599386 /* ScreenSaver.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2A9BEB0D82714A00599386 /* ScreenSaver.framework */; }; 8B45A03A0DA46A2A001148C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; 8B45A0B80DA46A2F001148C5 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; }; 8B45A0D50DA46A57001148C5 /* GTMNSObject+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */; }; 8B45A0D60DA46A57001148C5 /* GTMNSObject+BindingUnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1A14E90D900BC800CA1E8E /* GTMNSObject+BindingUnitTesting.m */; }; 8B45A19A0DA46AAA001148C5 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B45A1990DA46AAA001148C5 /* QuartzCore.framework */; }; 8B45A2040DA46DF6001148C5 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; - 8B45A2070DA46DFC001148C5 /* ScreenSaver.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2A9BEB0D82714A00599386 /* ScreenSaver.framework */; }; 8B45A21A0DA46E1D001148C5 /* GTMGeometryUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */; }; 8B45A21D0DA46E2C001148C5 /* GTMObjC2Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F32040DA34A1B0052CA40 /* GTMObjC2Runtime.m */; }; 8B45A21E0DA46E34001148C5 /* GTMObjC2Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F32040DA34A1B0052CA40 /* GTMObjC2Runtime.m */; }; @@ -59,23 +56,24 @@ 8B7AD4AE0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7AD4AD0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m */; }; 8BC045C20DAE899100C2D1CA /* GTMGeometryUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */; }; 8BC046B90DAE8C4B00C2D1CA /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */; }; - 8BC04CD30DB003BE00C2D1CA /* GTMNSString+Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BC04CD10DB003BE00C2D1CA /* GTMNSString+Utilities.h */; }; - 8BC04CD40DB003BE00C2D1CA /* GTMNSString+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04CD20DB003BE00C2D1CA /* GTMNSString+Utilities.m */; }; - 8BC04CD60DB003C300C2D1CA /* GTMNSString+UtilitiesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04CD50DB003C300C2D1CA /* GTMNSString+UtilitiesTest.m */; }; 8BC04CD70DB003CD00C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; - 8BC04CD80DB003D800C2D1CA /* GTMMethodCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F31F40DA3489B0052CA40 /* GTMMethodCheck.h */; }; + 8BC04CD80DB003D800C2D1CA /* GTMMethodCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F31F40DA3489B0052CA40 /* GTMMethodCheck.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BC04CDD0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; 8BC04CDE0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; 8BC04CDF0DB004A100C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; + 8BE869730DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BE869720DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff */; }; 8BEEA90D0DA7446300894774 /* GTMUnitTestingImage.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */; }; 8BEEA90E0DA7446300894774 /* GTMUnitTestingWindow.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */; }; 8BEEA90F0DA7446300894774 /* GTMUnitTestingView.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90C0DA7446300894774 /* GTMUnitTestingView.tiff */; }; - F413908F0D75F63C00F72B31 /* GTMNSFileManager+Path.h in Headers */ = {isa = PBXBuildFile; fileRef = F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */; }; + F413908F0D75F63C00F72B31 /* GTMNSFileManager+Path.h in Headers */ = {isa = PBXBuildFile; fileRef = F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */; settings = {ATTRIBUTES = (Public, ); }; }; F41390900D75F63C00F72B31 /* GTMNSFileManager+Path.m in Sources */ = {isa = PBXBuildFile; fileRef = F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */; }; F41390920D75F64D00F72B31 /* GTMNSFileManager+PathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */; }; + F41D258B0DBD21A300774EEB /* GTMBase64.h in Headers */ = {isa = PBXBuildFile; fileRef = F41D25880DBD21A300774EEB /* GTMBase64.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F41D258C0DBD21A300774EEB /* GTMBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = F41D25890DBD21A300774EEB /* GTMBase64.m */; }; + F41D258F0DBD21B900774EEB /* GTMBase64Test.m in Sources */ = {isa = PBXBuildFile; fileRef = F41D258A0DBD21A300774EEB /* GTMBase64Test.m */; }; F424F7010D9AA02B000B87EF /* GTMNSData+zlibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */; }; - F424F75F0D9AF019000B87EF /* GTMDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B1A16050D90344B00CA1E8E /* GTMDefines.h */; }; - F428FF030D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */; }; + F424F75F0D9AF019000B87EF /* GTMDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B1A16050D90344B00CA1E8E /* GTMDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F428FF030D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */; settings = {ATTRIBUTES = (Public, ); }; }; F428FF040D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */; }; F428FF090D48E57300382ED1 /* GTMNSBezierPath+CGPathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */; }; F42E082F0D19991400D5DDE0 /* GTMNSBezierPath+RoundRectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2830D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.m */; }; @@ -88,56 +86,60 @@ F42E089C0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; }; F42E089D0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; }; F42E09450D199BA400D5DDE0 /* GTMNSObject+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */; }; - F42E09490D199BBF00D5DDE0 /* GTMDelegatingTableColumn.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */; }; + F42E09490D199BBF00D5DDE0 /* GTMDelegatingTableColumn.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */; settings = {ATTRIBUTES = (Public, ); }; }; F42E094A0D199BBF00D5DDE0 /* GTMDelegatingTableColumn.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */; }; - F42E094B0D199BBF00D5DDE0 /* GTMGarbageCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE28D0D198D24009257D2 /* GTMGarbageCollection.h */; }; - F42E094C0D199BBF00D5DDE0 /* GTMGeometryUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */; }; + F42E094B0D199BBF00D5DDE0 /* GTMGarbageCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE28D0D198D24009257D2 /* GTMGarbageCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F42E094C0D199BBF00D5DDE0 /* GTMGeometryUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; F42E094D0D199BBF00D5DDE0 /* GTMGeometryUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */; }; - F42E094F0D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2810D198D0E009257D2 /* GTMNSBezierPath+RoundRect.h */; }; + F42E094F0D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2810D198D0E009257D2 /* GTMNSBezierPath+RoundRect.h */; settings = {ATTRIBUTES = (Public, ); }; }; F42E09500D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2820D198D0E009257D2 /* GTMNSBezierPath+RoundRect.m */; }; - F42E09510D199BBF00D5DDE0 /* GTMNSString+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */; }; + F42E09510D199BBF00D5DDE0 /* GTMNSString+HTML.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */; settings = {ATTRIBUTES = (Public, ); }; }; F42E09520D199BBF00D5DDE0 /* GTMNSString+HTML.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE28F0D198D24009257D2 /* GTMNSString+HTML.m */; }; - F42E09530D199BBF00D5DDE0 /* GTMObjectSingleton.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */; }; - F42E09540D199BBF00D5DDE0 /* GTMSystemVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2920D198D24009257D2 /* GTMSystemVersion.h */; }; + F42E09530D199BBF00D5DDE0 /* GTMObjectSingleton.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F42E09540D199BBF00D5DDE0 /* GTMSystemVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = F48FE2920D198D24009257D2 /* GTMSystemVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; F42E09550D199BBF00D5DDE0 /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2930D198D24009257D2 /* GTMSystemVersion.m */; }; F42E095E0D199BD600D5DDE0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; - F42E09AA0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h in Headers */ = {isa = PBXBuildFile; fileRef = F42E09A80D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h */; }; - F42E09AB0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m in Sources */ = {isa = PBXBuildFile; fileRef = F42E09A90D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m */; }; F42E09AE0D19A62F00D5DDE0 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */; }; - F42E0B0A0D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F42E0B090D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m */; }; - F437F55D0D50BC0A00F5C3A4 /* GTMRegex.h in Headers */ = {isa = PBXBuildFile; fileRef = F437F55A0D50BC0A00F5C3A4 /* GTMRegex.h */; }; + F435DE7C0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = F435DE7A0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff */; }; + F435DE7D0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = F435DE7B0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff */; }; + F435DE8B0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = F435DE8A0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff */; }; + F435E0890DC63F6D0069CDE8 /* GTMHTTPFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = F435E0870DC63F6D0069CDE8 /* GTMHTTPFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F435E08A0DC63F6D0069CDE8 /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E0880DC63F6D0069CDE8 /* GTMHTTPFetcher.m */; }; + F435E27F0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = F435E27D0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F435E2800DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E27E0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m */; }; + F435E3940DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */; }; + F435E3BB0DC8D1980069CDE8 /* GTMHTTPFetcherTestPage.html in Resources */ = {isa = PBXBuildFile; fileRef = F435E3B90DC8D1720069CDE8 /* GTMHTTPFetcherTestPage.html */; }; + F435E3BC0DC8D1980069CDE8 /* GTMHTTPFetcherTestServer in Resources */ = {isa = PBXBuildFile; fileRef = F435E3BA0DC8D1720069CDE8 /* GTMHTTPFetcherTestServer */; }; + F437F55D0D50BC0A00F5C3A4 /* GTMRegex.h in Headers */ = {isa = PBXBuildFile; fileRef = F437F55A0D50BC0A00F5C3A4 /* GTMRegex.h */; settings = {ATTRIBUTES = (Public, ); }; }; F437F55E0D50BC0A00F5C3A4 /* GTMRegex.m in Sources */ = {isa = PBXBuildFile; fileRef = F437F55B0D50BC0A00F5C3A4 /* GTMRegex.m */; }; F437F5620D50BC1D00F5C3A4 /* GTMRegexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F437F55C0D50BC0A00F5C3A4 /* GTMRegexTest.m */; }; - F43DCDCD0D4796C600959A62 /* GTMLoginItems.h in Headers */ = {isa = PBXBuildFile; fileRef = F43DCDCB0D4796C600959A62 /* GTMLoginItems.h */; }; + F43DCDCD0D4796C600959A62 /* GTMLoginItems.h in Headers */ = {isa = PBXBuildFile; fileRef = F43DCDCB0D4796C600959A62 /* GTMLoginItems.h */; settings = {ATTRIBUTES = (Public, ); }; }; F43DCDCE0D4796C600959A62 /* GTMLoginItems.m in Sources */ = {isa = PBXBuildFile; fileRef = F43DCDCC0D4796C600959A62 /* GTMLoginItems.m */; }; F43DCEC70D47BEA000959A62 /* GTMLoginItemsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43DCEC60D47BEA000959A62 /* GTMLoginItemsTest.m */; }; - F43E447A0D4918B20041161F /* GTMLinearRGBShading.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E44770D4918B20041161F /* GTMLinearRGBShading.h */; }; + F43E447A0D4918B20041161F /* GTMLinearRGBShading.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E44770D4918B20041161F /* GTMLinearRGBShading.h */; settings = {ATTRIBUTES = (Public, ); }; }; F43E447B0D4918B20041161F /* GTMLinearRGBShading.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E44780D4918B20041161F /* GTMLinearRGBShading.m */; }; F43E447F0D4918BC0041161F /* GTMLinearRGBShadingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E44790D4918B20041161F /* GTMLinearRGBShadingTest.m */; }; - F43E4C280D4E361D0041161F /* GTMNSString+XML.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4C250D4E361D0041161F /* GTMNSString+XML.h */; }; + F43E4C280D4E361D0041161F /* GTMNSString+XML.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4C250D4E361D0041161F /* GTMNSString+XML.h */; settings = {ATTRIBUTES = (Public, ); }; }; F43E4C290D4E361D0041161F /* GTMNSString+XML.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4C260D4E361D0041161F /* GTMNSString+XML.m */; }; F43E4C2D0D4E36230041161F /* GTMNSString+XMLTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4C270D4E361D0041161F /* GTMNSString+XMLTest.m */; }; - F43E4DD90D4E56320041161F /* GTMNSEnumerator+Filter.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */; }; + F43E4DD90D4E56320041161F /* GTMNSEnumerator+Filter.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */; settings = {ATTRIBUTES = (Public, ); }; }; F43E4DDA0D4E56320041161F /* GTMNSEnumerator+Filter.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4DD70D4E56320041161F /* GTMNSEnumerator+Filter.m */; }; F43E4DDE0D4E56380041161F /* GTMNSEnumerator+FilterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4DD80D4E56320041161F /* GTMNSEnumerator+FilterTest.m */; }; - F43E4E610D4E5EC90041161F /* GTMNSData+zlib.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4E5E0D4E5EC90041161F /* GTMNSData+zlib.h */; }; + F43E4E610D4E5EC90041161F /* GTMNSData+zlib.h in Headers */ = {isa = PBXBuildFile; fileRef = F43E4E5E0D4E5EC90041161F /* GTMNSData+zlib.h */; settings = {ATTRIBUTES = (Public, ); }; }; F43E4E620D4E5EC90041161F /* GTMNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4E5F0D4E5EC90041161F /* GTMNSData+zlib.m */; }; F43E4F6D0D4E60C50041161F /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F43E4F6C0D4E60C50041161F /* libz.dylib */; }; - F47A79880D746EE9002302AB /* GTMScriptRunner.h in Headers */ = {isa = PBXBuildFile; fileRef = F47A79850D746EE9002302AB /* GTMScriptRunner.h */; }; + F47A79880D746EE9002302AB /* GTMScriptRunner.h in Headers */ = {isa = PBXBuildFile; fileRef = F47A79850D746EE9002302AB /* GTMScriptRunner.h */; settings = {ATTRIBUTES = (Public, ); }; }; F47A79890D746EE9002302AB /* GTMScriptRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = F47A79860D746EE9002302AB /* GTMScriptRunner.m */; }; F47A798B0D746EFC002302AB /* GTMScriptRunnerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47A79870D746EE9002302AB /* GTMScriptRunnerTest.m */; }; - F47F1C120D490BC000925B8F /* GTMNSBezierPath+Shading.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */; }; + F47F1C120D490BC000925B8F /* GTMNSBezierPath+Shading.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */; settings = {ATTRIBUTES = (Public, ); }; }; F47F1C130D490BC000925B8F /* GTMNSBezierPath+Shading.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */; }; F47F1C1B0D490BD200925B8F /* GTMNSBezierPath+ShadingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */; }; - F47F1C750D490E5C00925B8F /* GTMShading.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1C740D490E5C00925B8F /* GTMShading.h */; }; - F47F1CAF0D4910FD00925B8F /* GTMNSColor+Theme.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1CAC0D4910FD00925B8F /* GTMNSColor+Theme.h */; }; - F47F1CB00D4910FD00925B8F /* GTMNSColor+Theme.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1CAD0D4910FD00925B8F /* GTMNSColor+Theme.m */; }; - F47F1CB60D49110900925B8F /* GTMNSColor+ThemeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1CAE0D4910FD00925B8F /* GTMNSColor+ThemeTest.m */; }; - F47F1D300D4914AD00925B8F /* GTMCalculatedRange.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */; }; + F47F1C750D490E5C00925B8F /* GTMShading.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1C740D490E5C00925B8F /* GTMShading.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F47F1D300D4914AD00925B8F /* GTMCalculatedRange.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */; settings = {ATTRIBUTES = (Public, ); }; }; F47F1D310D4914AD00925B8F /* GTMCalculatedRange.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */; }; F47F1D350D4914B600925B8F /* GTMCalculatedRangeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */; }; - F4CA854F0DAFAAF600B4AB10 /* GTMObjC2Runtime.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F32060DA34A1B0052CA40 /* GTMObjC2Runtime.h */; }; - F4FF22780D9D4835003880AC /* GTMDebugSelectorValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = F4FF22770D9D4835003880AC /* GTMDebugSelectorValidation.h */; }; + F4CA854F0DAFAAF600B4AB10 /* GTMObjC2Runtime.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F32060DA34A1B0052CA40 /* GTMObjC2Runtime.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F4FF22780D9D4835003880AC /* GTMDebugSelectorValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = F4FF22770D9D4835003880AC /* GTMDebugSelectorValidation.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -155,6 +157,20 @@ remoteGlobalIDString = 8B45A2890DA49B99001148C5; remoteInfo = UIUnitTestingHarness; }; + F41D254E0DB9067C00774EEB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8B45A0270DA4696C001148C5; + remoteInfo = "UnitTest - UnitTesting"; + }; + F41D25510DB9068700774EEB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8B45A0270DA4696C001148C5; + remoteInfo = "UnitTest - UnitTesting"; + }; F42E08760D199A9B00D5DDE0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; @@ -226,27 +242,35 @@ 8B7AD4990DABBB5800B84F4A /* GTMNSBezierPath+RoundRectTest.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.tiff"; sourceTree = "<group>"; }; 8B7AD4AD0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestingBindingTest.m; sourceTree = "<group>"; }; 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = /System/Library/Frameworks/ApplicationServices.framework; sourceTree = "<absolute>"; }; - 8BC04CD10DB003BE00C2D1CA /* GTMNSString+Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+Utilities.h"; sourceTree = "<group>"; }; - 8BC04CD20DB003BE00C2D1CA /* GTMNSString+Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+Utilities.m"; sourceTree = "<group>"; }; - 8BC04CD50DB003C300C2D1CA /* GTMNSString+UtilitiesTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+UtilitiesTest.m"; sourceTree = "<group>"; }; 8BC04D140DB0061300C2D1CA /* RunMacOSUnitTests.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = RunMacOSUnitTests.sh; sourceTree = "<group>"; }; + 8BE869720DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.x86_64.tiff"; sourceTree = "<group>"; }; 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingImage.tiff; sourceTree = "<group>"; }; 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingWindow.tiff; sourceTree = "<group>"; }; 8BEEA90C0DA7446300894774 /* GTMUnitTestingView.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingView.tiff; sourceTree = "<group>"; }; F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSFileManager+Path.h"; sourceTree = "<group>"; }; F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+Path.m"; sourceTree = "<group>"; }; F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+PathTest.m"; sourceTree = "<group>"; }; + F41D25880DBD21A300774EEB /* GTMBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMBase64.h; sourceTree = "<group>"; }; + F41D25890DBD21A300774EEB /* GTMBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64.m; sourceTree = "<group>"; }; + F41D258A0DBD21A300774EEB /* GTMBase64Test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64Test.m; sourceTree = "<group>"; }; F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+CGPath.h"; sourceTree = "<group>"; }; F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPath.m"; sourceTree = "<group>"; }; F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPathTest.m"; sourceTree = "<group>"; }; F42E08210D19987200D5DDE0 /* UnitTest - Foundation.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - Foundation.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; F42E086D0D199A5B00D5DDE0 /* GoogleToolboxForMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GoogleToolboxForMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F42E086E0D199A5B00D5DDE0 /* GTM-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "GTM-Info.plist"; sourceTree = "<group>"; }; - F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = /System/Library/Frameworks/SenTestingKit.framework; sourceTree = "<absolute>"; }; - F42E09A80D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSWorkspace+Theme.h"; sourceTree = "<group>"; }; - F42E09A90D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSWorkspace+Theme.m"; sourceTree = "<group>"; }; + F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = "<absolute>"; }; - F42E0B090D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSWorkspace+ThemeTest.m"; sourceTree = "<group>"; }; + F435DE7A0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+CGPathTest.ppc64.tiff"; sourceTree = "<group>"; }; + F435DE7B0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+CGPathTest.x86_64.tiff"; sourceTree = "<group>"; }; + F435DE8A0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.ppc64.tiff"; sourceTree = "<group>"; }; + F435E0870DC63F6D0069CDE8 /* GTMHTTPFetcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcher.h; sourceTree = "<group>"; }; + F435E0880DC63F6D0069CDE8 /* GTMHTTPFetcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcher.m; sourceTree = "<group>"; }; + F435E27D0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMProgressMonitorInputStream.h; sourceTree = "<group>"; }; + F435E27E0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMProgressMonitorInputStream.m; sourceTree = "<group>"; }; + F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherTest.m; sourceTree = "<group>"; }; + F435E3B90DC8D1720069CDE8 /* GTMHTTPFetcherTestPage.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = GTMHTTPFetcherTestPage.html; sourceTree = "<group>"; }; + F435E3BA0DC8D1720069CDE8 /* GTMHTTPFetcherTestServer */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = GTMHTTPFetcherTestServer; sourceTree = "<group>"; }; F437F55A0D50BC0A00F5C3A4 /* GTMRegex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMRegex.h; sourceTree = "<group>"; }; F437F55B0D50BC0A00F5C3A4 /* GTMRegex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMRegex.m; sourceTree = "<group>"; }; F437F55C0D50BC0A00F5C3A4 /* GTMRegexTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMRegexTest.m; sourceTree = "<group>"; }; @@ -273,9 +297,6 @@ F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+Shading.m"; sourceTree = "<group>"; }; F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+ShadingTest.m"; sourceTree = "<group>"; }; F47F1C740D490E5C00925B8F /* GTMShading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMShading.h; sourceTree = "<group>"; }; - F47F1CAC0D4910FD00925B8F /* GTMNSColor+Theme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSColor+Theme.h"; sourceTree = "<group>"; }; - F47F1CAD0D4910FD00925B8F /* GTMNSColor+Theme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+Theme.m"; sourceTree = "<group>"; }; - F47F1CAE0D4910FD00925B8F /* GTMNSColor+ThemeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+ThemeTest.m"; sourceTree = "<group>"; }; F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCalculatedRange.h; sourceTree = "<group>"; }; F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRange.m; sourceTree = "<group>"; }; F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRangeTest.m; sourceTree = "<group>"; }; @@ -310,6 +331,11 @@ F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersionTest.m; sourceTree = "<group>"; }; F4C978090D5B79C7001C29A6 /* ReleaseNotes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ReleaseNotes.txt; sourceTree = "<group>"; }; F4CA854E0DAFAAB600B4AB10 /* xcconfigs-readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "xcconfigs-readme.txt"; sourceTree = "<group>"; }; + F4CA864A0DB3ACB200B4AB10 /* DebugLeopardOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DebugLeopardOrLater.xcconfig; sourceTree = "<group>"; }; + F4CA864B0DB3ACB200B4AB10 /* ReleaseLeopardOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReleaseLeopardOrLater.xcconfig; sourceTree = "<group>"; }; + F4CA864C0DB3ACD200B4AB10 /* LoadableBundleGCSupported.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = LoadableBundleGCSupported.xcconfig; sourceTree = "<group>"; }; + F4CA864D0DB3ACD200B4AB10 /* SharedLibraryGCSupported.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SharedLibraryGCSupported.xcconfig; sourceTree = "<group>"; }; + F4CA864E0DB3ACD200B4AB10 /* StaticLibraryGCSupported.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = StaticLibraryGCSupported.xcconfig; sourceTree = "<group>"; }; F4FF22770D9D4835003880AC /* GTMDebugSelectorValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDebugSelectorValidation.h; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -318,7 +344,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8B45A2070DA46DFC001148C5 /* ScreenSaver.framework in Frameworks */, 8B45A2040DA46DF6001148C5 /* Cocoa.framework in Frameworks */, 8B45A0B80DA46A2F001148C5 /* SenTestingKit.framework in Frameworks */, 8B45A03A0DA46A2A001148C5 /* Foundation.framework in Frameworks */, @@ -363,7 +388,6 @@ F42E08610D199A2B00D5DDE0 /* Cocoa.framework in Frameworks */, F42E087F0D199AB400D5DDE0 /* GoogleToolboxForMac.framework in Frameworks */, F42E089C0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */, - 8B2A9BED0D82714A00599386 /* ScreenSaver.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -428,6 +452,43 @@ path = GTMUIUnitTestingHarness; sourceTree = "<group>"; }; + F435E46C0DC8F23A0069CDE8 /* TestData */ = { + isa = PBXGroup; + children = ( + 8B45A2DE0DA51A7E001148C5 /* GTMUnitTestingTest.nib */, + 8B45A6B90DA67DD5001148C5 /* GTMUnitTestingImage.gtmUTState */, + 8B45A5F50DA5EB9F001148C5 /* GTMUnitTestingWindow.gtmUTState */, + 8B45A5F60DA5EB9F001148C5 /* GTMUnitTestingTestApp.gtmUTState */, + 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */, + 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */, + 8BEEA90C0DA7446300894774 /* GTMUnitTestingView.tiff */, + ); + path = TestData; + sourceTree = "<group>"; + }; + F435E4840DC8F3DC0069CDE8 /* TestData */ = { + isa = PBXGroup; + children = ( + 8B7AD4980DABBB5800B84F4A /* GTMNSBezierPath+CGPathTest.tiff */, + F435DE7A0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff */, + F435DE7B0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff */, + 8B7AD4990DABBB5800B84F4A /* GTMNSBezierPath+RoundRectTest.tiff */, + F435DE8A0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff */, + 8BE869720DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff */, + 8B7AD4970DABBB5800B84F4A /* GTMNSBezierPath+ShadingTest.10.5.tiff */, + ); + path = TestData; + sourceTree = "<group>"; + }; + F435E4B50DC903E20069CDE8 /* TestData */ = { + isa = PBXGroup; + children = ( + F435E3B90DC8D1720069CDE8 /* GTMHTTPFetcherTestPage.html */, + F435E3BA0DC8D1720069CDE8 /* GTMHTTPFetcherTestServer */, + ); + path = TestData; + sourceTree = "<group>"; + }; F48B91030D94485500D45044 /* TigerGcov */ = { isa = PBXGroup; children = ( @@ -460,25 +521,17 @@ F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */, F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */, F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */, - 8B7AD4980DABBB5800B84F4A /* GTMNSBezierPath+CGPathTest.tiff */, F48FE2810D198D0E009257D2 /* GTMNSBezierPath+RoundRect.h */, F48FE2820D198D0E009257D2 /* GTMNSBezierPath+RoundRect.m */, F48FE2830D198D0E009257D2 /* GTMNSBezierPath+RoundRectTest.m */, - 8B7AD4990DABBB5800B84F4A /* GTMNSBezierPath+RoundRectTest.tiff */, F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */, F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */, - 8B7AD4970DABBB5800B84F4A /* GTMNSBezierPath+ShadingTest.10.5.tiff */, F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */, - F47F1CAC0D4910FD00925B8F /* GTMNSColor+Theme.h */, - F47F1CAD0D4910FD00925B8F /* GTMNSColor+Theme.m */, - F47F1CAE0D4910FD00925B8F /* GTMNSColor+ThemeTest.m */, F47F1C740D490E5C00925B8F /* GTMShading.h */, 8B2A9B1D0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m */, 8B2A9B1E0D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m */, 8B2A9B1F0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h */, - F42E09A80D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h */, - F42E09A90D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m */, - F42E0B090D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m */, + F435E4840DC8F3DC0069CDE8 /* TestData */, ); path = AppKit; sourceTree = "<group>"; @@ -486,6 +539,9 @@ F48FE2720D198CCE009257D2 /* Foundation */ = { isa = PBXGroup; children = ( + F41D25880DBD21A300774EEB /* GTMBase64.h */, + F41D25890DBD21A300774EEB /* GTMBase64.m */, + F41D258A0DBD21A300774EEB /* GTMBase64Test.m */, F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */, F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */, F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */, @@ -493,9 +549,9 @@ F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */, F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */, F48FE28D0D198D24009257D2 /* GTMGarbageCollection.h */, - 8B6F32040DA34A1B0052CA40 /* GTMObjC2Runtime.m */, - 8B6F32050DA34A1B0052CA40 /* GTMObjC2RuntimeTest.m */, - 8B6F32060DA34A1B0052CA40 /* GTMObjC2Runtime.h */, + F435E0870DC63F6D0069CDE8 /* GTMHTTPFetcher.h */, + F435E0880DC63F6D0069CDE8 /* GTMHTTPFetcher.m */, + F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */, F43E4DD60D4E56320041161F /* GTMNSEnumerator+Filter.h */, F43E4DD70D4E56320041161F /* GTMNSEnumerator+Filter.m */, F43E4DD80D4E56320041161F /* GTMNSEnumerator+FilterTest.m */, @@ -508,13 +564,15 @@ F43E4C250D4E361D0041161F /* GTMNSString+XML.h */, F43E4C260D4E361D0041161F /* GTMNSString+XML.m */, F43E4C270D4E361D0041161F /* GTMNSString+XMLTest.m */, - 8BC04CD10DB003BE00C2D1CA /* GTMNSString+Utilities.h */, - 8BC04CD20DB003BE00C2D1CA /* GTMNSString+Utilities.m */, - 8BC04CD50DB003C300C2D1CA /* GTMNSString+UtilitiesTest.m */, F43E4E5E0D4E5EC90041161F /* GTMNSData+zlib.h */, F43E4E5F0D4E5EC90041161F /* GTMNSData+zlib.m */, F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */, + 8B6F32040DA34A1B0052CA40 /* GTMObjC2Runtime.m */, + 8B6F32050DA34A1B0052CA40 /* GTMObjC2RuntimeTest.m */, + 8B6F32060DA34A1B0052CA40 /* GTMObjC2Runtime.h */, F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */, + F435E27D0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h */, + F435E27E0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m */, F437F55A0D50BC0A00F5C3A4 /* GTMRegex.h */, F437F55B0D50BC0A00F5C3A4 /* GTMRegex.m */, F437F55C0D50BC0A00F5C3A4 /* GTMRegexTest.m */, @@ -524,6 +582,7 @@ F48FE2920D198D24009257D2 /* GTMSystemVersion.h */, F48FE2930D198D24009257D2 /* GTMSystemVersion.m */, F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */, + F435E4B50DC903E20069CDE8 /* TestData */, ); path = Foundation; sourceTree = "<group>"; @@ -546,14 +605,8 @@ 8B45A2E00DA51ABC001148C5 /* GTMUnitTestingTest.h */, 8B45A2E10DA51ABC001148C5 /* GTMUnitTestingTest.m */, 8B7AD4AD0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m */, - 8B45A2DE0DA51A7E001148C5 /* GTMUnitTestingTest.nib */, - 8B45A6B90DA67DD5001148C5 /* GTMUnitTestingImage.gtmUTState */, - 8B45A5F50DA5EB9F001148C5 /* GTMUnitTestingWindow.gtmUTState */, - 8B45A5F60DA5EB9F001148C5 /* GTMUnitTestingTestApp.gtmUTState */, - 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */, - 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */, - 8BEEA90C0DA7446300894774 /* GTMUnitTestingView.tiff */, 8B45A2A20DA49C47001148C5 /* GTMUIUnitTestingHarness */, + F435E46C0DC8F23A0069CDE8 /* TestData */, ); path = UnitTesting; sourceTree = "<group>"; @@ -561,7 +614,9 @@ F4CA852B0DAFA92A00B4AB10 /* Project */ = { isa = PBXGroup; children = ( + F4CA864A0DB3ACB200B4AB10 /* DebugLeopardOrLater.xcconfig */, F48FE2410D197F9A009257D2 /* DebugTigerOrLater.xcconfig */, + F4CA864B0DB3ACB200B4AB10 /* ReleaseLeopardOrLater.xcconfig */, F48FE2440D197F9A009257D2 /* ReleaseTigerOrLater.xcconfig */, ); path = Project; @@ -571,10 +626,13 @@ isa = PBXGroup; children = ( F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */, + F4CA864C0DB3ACD200B4AB10 /* LoadableBundleGCSupported.xcconfig */, F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */, F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */, F48FE2460D197F9A009257D2 /* SharedLibrary.xcconfig */, + F4CA864D0DB3ACD200B4AB10 /* SharedLibraryGCSupported.xcconfig */, F48FE2470D197F9A009257D2 /* StaticLibrary.xcconfig */, + F4CA864E0DB3ACD200B4AB10 /* StaticLibraryGCSupported.xcconfig */, ); path = Target; sourceTree = "<group>"; @@ -604,12 +662,10 @@ F42E09510D199BBF00D5DDE0 /* GTMNSString+HTML.h in Headers */, F42E09530D199BBF00D5DDE0 /* GTMObjectSingleton.h in Headers */, F42E09540D199BBF00D5DDE0 /* GTMSystemVersion.h in Headers */, - F42E09AA0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.h in Headers */, F43DCDCD0D4796C600959A62 /* GTMLoginItems.h in Headers */, F428FF030D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h in Headers */, F47F1C120D490BC000925B8F /* GTMNSBezierPath+Shading.h in Headers */, F47F1C750D490E5C00925B8F /* GTMShading.h in Headers */, - F47F1CAF0D4910FD00925B8F /* GTMNSColor+Theme.h in Headers */, F47F1D300D4914AD00925B8F /* GTMCalculatedRange.h in Headers */, F43E447A0D4918B20041161F /* GTMLinearRGBShading.h in Headers */, F43E4C280D4E361D0041161F /* GTMNSString+XML.h in Headers */, @@ -622,8 +678,10 @@ F424F75F0D9AF019000B87EF /* GTMDefines.h in Headers */, F4FF22780D9D4835003880AC /* GTMDebugSelectorValidation.h in Headers */, F4CA854F0DAFAAF600B4AB10 /* GTMObjC2Runtime.h in Headers */, - 8BC04CD30DB003BE00C2D1CA /* GTMNSString+Utilities.h in Headers */, 8BC04CD80DB003D800C2D1CA /* GTMMethodCheck.h in Headers */, + F41D258B0DBD21A300774EEB /* GTMBase64.h in Headers */, + F435E0890DC63F6D0069CDE8 /* GTMHTTPFetcher.h in Headers */, + F435E27F0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -679,6 +737,7 @@ ); dependencies = ( F42E08790D199AA600D5DDE0 /* PBXTargetDependency */, + F41D25520DB9068700774EEB /* PBXTargetDependency */, ); name = "UnitTest - Foundation"; productName = "UnitTest - AppKit"; @@ -716,6 +775,7 @@ ); dependencies = ( F42E08770D199A9B00D5DDE0 /* PBXTargetDependency */, + F41D254F0DB9067C00774EEB /* PBXTargetDependency */, ); name = "UnitTest - AppKit"; productName = "UnitTest - AppKit"; @@ -727,8 +787,11 @@ /* Begin PBXProject section */ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = NO; + }; buildConfigurationList = 1DEB918108733D990010E9CD /* Build configuration list for PBXProject "GTM" */; - compatibilityVersion = "Xcode 2.4"; + compatibilityVersion = "Xcode 3.0"; hasScannedForEncodings = 1; mainGroup = 0867D691FE84028FC02AAC07 /* GTM */; productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; @@ -772,6 +835,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F435E3BB0DC8D1980069CDE8 /* GTMHTTPFetcherTestPage.html in Resources */, + F435E3BC0DC8D1980069CDE8 /* GTMHTTPFetcherTestServer in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -789,6 +854,10 @@ 8B7AD49A0DABBB5800B84F4A /* GTMNSBezierPath+ShadingTest.10.5.tiff in Resources */, 8B7AD49B0DABBB5800B84F4A /* GTMNSBezierPath+CGPathTest.tiff in Resources */, 8B7AD49C0DABBB5800B84F4A /* GTMNSBezierPath+RoundRectTest.tiff in Resources */, + 8BE869730DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff in Resources */, + F435DE7C0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff in Resources */, + F435DE7D0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff in Resources */, + F435DE8B0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -878,8 +947,9 @@ 8B6F32080DA34A1B0052CA40 /* GTMObjC2RuntimeTest.m in Sources */, 8B6F32160DA34C830052CA40 /* GTMMethodCheckTest.m in Sources */, 8BC045C20DAE899100C2D1CA /* GTMGeometryUtilsTest.m in Sources */, - 8BC04CD60DB003C300C2D1CA /* GTMNSString+UtilitiesTest.m in Sources */, 8BC04CDE0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */, + F41D258F0DBD21B900774EEB /* GTMBase64Test.m in Sources */, + F435E3940DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -892,11 +962,9 @@ F42E09500D199BBF00D5DDE0 /* GTMNSBezierPath+RoundRect.m in Sources */, F42E09520D199BBF00D5DDE0 /* GTMNSString+HTML.m in Sources */, F42E09550D199BBF00D5DDE0 /* GTMSystemVersion.m in Sources */, - F42E09AB0D19A5E300D5DDE0 /* GTMNSWorkspace+Theme.m in Sources */, F43DCDCE0D4796C600959A62 /* GTMLoginItems.m in Sources */, F428FF040D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m in Sources */, F47F1C130D490BC000925B8F /* GTMNSBezierPath+Shading.m in Sources */, - F47F1CB00D4910FD00925B8F /* GTMNSColor+Theme.m in Sources */, F47F1D310D4914AD00925B8F /* GTMCalculatedRange.m in Sources */, F43E447B0D4918B20041161F /* GTMLinearRGBShading.m in Sources */, F43E4C290D4E361D0041161F /* GTMNSString+XML.m in Sources */, @@ -907,8 +975,10 @@ F41390900D75F63C00F72B31 /* GTMNSFileManager+Path.m in Sources */, 8B2A9B200D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m in Sources */, 8B45A21E0DA46E34001148C5 /* GTMObjC2Runtime.m in Sources */, - 8BC04CD40DB003BE00C2D1CA /* GTMNSString+Utilities.m in Sources */, 8BC04CD70DB003CD00C2D1CA /* GTMMethodCheck.m in Sources */, + F41D258C0DBD21A300774EEB /* GTMBase64.m in Sources */, + F435E08A0DC63F6D0069CDE8 /* GTMHTTPFetcher.m in Sources */, + F435E2800DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -918,13 +988,10 @@ files = ( F42E082F0D19991400D5DDE0 /* GTMNSBezierPath+RoundRectTest.m in Sources */, F42E09450D199BA400D5DDE0 /* GTMNSObject+UnitTesting.m in Sources */, - F42E0B0A0D19A6FB00D5DDE0 /* GTMNSWorkspace+ThemeTest.m in Sources */, F43DCEC70D47BEA000959A62 /* GTMLoginItemsTest.m in Sources */, F428FF090D48E57300382ED1 /* GTMNSBezierPath+CGPathTest.m in Sources */, F47F1C1B0D490BD200925B8F /* GTMNSBezierPath+ShadingTest.m in Sources */, - F47F1CB60D49110900925B8F /* GTMNSColor+ThemeTest.m in Sources */, F43E447F0D4918BC0041161F /* GTMLinearRGBShadingTest.m in Sources */, - 8B2A9B230D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m in Sources */, 8B2A9B240D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m in Sources */, 8BC04CDD0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */, 8B5547B90DB3BB220014CC1C /* GTMAppKit+UnitTesting.m in Sources */, @@ -944,6 +1011,16 @@ target = 8B45A2890DA49B99001148C5 /* UIUnitTestingHarness */; targetProxy = 8B45A2D30DA51A0E001148C5 /* PBXContainerItemProxy */; }; + F41D254F0DB9067C00774EEB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8B45A0270DA4696C001148C5 /* UnitTest - UnitTesting */; + targetProxy = F41D254E0DB9067C00774EEB /* PBXContainerItemProxy */; + }; + F41D25520DB9068700774EEB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8B45A0270DA4696C001148C5 /* UnitTest - UnitTesting */; + targetProxy = F41D25510DB9068700774EEB /* PBXContainerItemProxy */; + }; F42E08770D199A9B00D5DDE0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F42E086C0D199A5B00D5DDE0 /* GTM */; @@ -978,25 +1055,25 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 1DEB918208733D990010E9CD /* Debug */ = { + 1DEB918208733D990010E9CD /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2410D197F9A009257D2 /* DebugTigerOrLater.xcconfig */; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = GTM_Prefix.pch; }; - name = Debug; + name = "TigerOrLater-Debug"; }; - 1DEB918308733D990010E9CD /* Release */ = { + 1DEB918308733D990010E9CD /* TigerOrLater-Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2440D197F9A009257D2 /* ReleaseTigerOrLater.xcconfig */; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = GTM_Prefix.pch; }; - name = Release; + name = "TigerOrLater-Release"; }; - 8B45A02A0DA4696D001148C5 /* Debug */ = { + 8B45A02A0DA4696D001148C5 /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; buildSettings = { @@ -1006,11 +1083,11 @@ ); INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - UnitTesting"; - TEST_HOST = "$(ACTION)/$(CONFIGURATION)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; }; - name = Debug; + name = "TigerOrLater-Debug"; }; - 8B45A02B0DA4696D001148C5 /* Debug-gcov */ = { + 8B45A02B0DA4696D001148C5 /* TigerOrLater-Debug-gcov */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; buildSettings = { @@ -1020,11 +1097,11 @@ ); INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - UnitTesting"; - TEST_HOST = "$(ACTION)/$(CONFIGURATION)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; }; - name = "Debug-gcov"; + name = "TigerOrLater-Debug-gcov"; }; - 8B45A02C0DA4696D001148C5 /* Release */ = { + 8B45A02C0DA4696D001148C5 /* TigerOrLater-Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; buildSettings = { @@ -1034,35 +1111,35 @@ ); INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - UnitTesting"; - TEST_HOST = "$(ACTION)/$(CONFIGURATION)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; }; - name = Release; + name = "TigerOrLater-Release"; }; - 8B45A28E0DA49B99001148C5 /* Debug */ = { + 8B45A28E0DA49B99001148C5 /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = UnitTesting/GTMUIUnitTestingHarness/Info.plist; PRODUCT_NAME = GTMUIUnitTestingHarness; }; - name = Debug; + name = "TigerOrLater-Debug"; }; - 8B45A28F0DA49B99001148C5 /* Debug-gcov */ = { + 8B45A28F0DA49B99001148C5 /* TigerOrLater-Debug-gcov */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = UnitTesting/GTMUIUnitTestingHarness/Info.plist; PRODUCT_NAME = GTMUIUnitTestingHarness; }; - name = "Debug-gcov"; + name = "TigerOrLater-Debug-gcov"; }; - 8B45A2900DA49B99001148C5 /* Release */ = { + 8B45A2900DA49B99001148C5 /* TigerOrLater-Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = UnitTesting/GTMUIUnitTestingHarness/Info.plist; PRODUCT_NAME = GTMUIUnitTestingHarness; }; - name = Release; + name = "TigerOrLater-Release"; }; - F42E081F0D19987200D5DDE0 /* Debug */ = { + F42E081F0D19987200D5DDE0 /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; buildSettings = { @@ -1073,9 +1150,9 @@ INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; - name = Debug; + name = "TigerOrLater-Debug"; }; - F42E08200D19987200D5DDE0 /* Release */ = { + F42E08200D19987200D5DDE0 /* TigerOrLater-Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; buildSettings = { @@ -1086,9 +1163,9 @@ INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; - name = Release; + name = "TigerOrLater-Release"; }; - F42E08700D199A5C00D5DDE0 /* Debug */ = { + F42E08700D199A5C00D5DDE0 /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */; buildSettings = { @@ -1099,9 +1176,9 @@ INSTALL_PATH = "@loader_path/../Frameworks"; PRODUCT_NAME = GoogleToolboxForMac; }; - name = Debug; + name = "TigerOrLater-Debug"; }; - F42E08710D199A5C00D5DDE0 /* Release */ = { + F42E08710D199A5C00D5DDE0 /* TigerOrLater-Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */; buildSettings = { @@ -1112,31 +1189,23 @@ INSTALL_PATH = "@loader_path/../Frameworks"; PRODUCT_NAME = GoogleToolboxForMac; }; - name = Release; + name = "TigerOrLater-Release"; }; - F47204350D33BEDF00E9F378 /* Debug */ = { + F47204350D33BEDF00E9F378 /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_OPTIMIZATION_LEVEL = 0; PRODUCT_NAME = "All UnitTests"; }; - name = Debug; + name = "TigerOrLater-Debug"; }; - F47204360D33BEDF00E9F378 /* Release */ = { + F47204360D33BEDF00E9F378 /* TigerOrLater-Release */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = YES; - GCC_ENABLE_FIX_AND_CONTINUE = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; PRODUCT_NAME = "All UnitTests"; - ZERO_LINK = NO; }; - name = Release; + name = "TigerOrLater-Release"; }; - F48B91060D94489300D45044 /* Debug-gcov */ = { + F48B91060D94489300D45044 /* TigerOrLater-Debug-gcov */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2410D197F9A009257D2 /* DebugTigerOrLater.xcconfig */; buildSettings = { @@ -1150,9 +1219,9 @@ ); OTHER_LDFLAGS = "-lgcov"; }; - name = "Debug-gcov"; + name = "TigerOrLater-Debug-gcov"; }; - F48B91070D94489300D45044 /* Debug-gcov */ = { + F48B91070D94489300D45044 /* TigerOrLater-Debug-gcov */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2430D197F9A009257D2 /* LoadableBundle.xcconfig */; buildSettings = { @@ -1163,20 +1232,16 @@ INSTALL_PATH = "@loader_path/../Frameworks"; PRODUCT_NAME = GoogleToolboxForMac; }; - name = "Debug-gcov"; + name = "TigerOrLater-Debug-gcov"; }; - F48B91080D94489300D45044 /* Debug-gcov */ = { + F48B91080D94489300D45044 /* TigerOrLater-Debug-gcov */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_OPTIMIZATION_LEVEL = 0; PRODUCT_NAME = "All UnitTests"; }; - name = "Debug-gcov"; + name = "TigerOrLater-Debug-gcov"; }; - F48B91090D94489300D45044 /* Debug-gcov */ = { + F48B91090D94489300D45044 /* TigerOrLater-Debug-gcov */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; buildSettings = { @@ -1187,9 +1252,9 @@ INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - AppKit"; }; - name = "Debug-gcov"; + name = "TigerOrLater-Debug-gcov"; }; - F48B910A0D94489300D45044 /* Debug-gcov */ = { + F48B910A0D94489300D45044 /* TigerOrLater-Debug-gcov */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; buildSettings = { @@ -1200,9 +1265,9 @@ INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; - name = "Debug-gcov"; + name = "TigerOrLater-Debug-gcov"; }; - F48FE2670D198C1F009257D2 /* Debug */ = { + F48FE2670D198C1F009257D2 /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; buildSettings = { @@ -1213,9 +1278,9 @@ INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - AppKit"; }; - name = Debug; + name = "TigerOrLater-Debug"; }; - F48FE2680D198C1F009257D2 /* Release */ = { + F48FE2680D198C1F009257D2 /* TigerOrLater-Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; buildSettings = { @@ -1226,7 +1291,241 @@ INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - AppKit"; }; - name = Release; + name = "TigerOrLater-Release"; + }; + F4CA864F0DB3ACE500B4AB10 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4CA864A0DB3ACB200B4AB10 /* DebugLeopardOrLater.xcconfig */; + buildSettings = { + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = GTM_Prefix.pch; + }; + name = "LeopardOrLater-Debug"; + }; + F4CA86500DB3ACE500B4AB10 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4CA864C0DB3ACD200B4AB10 /* LoadableBundleGCSupported.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "GTM-Info.plist"; + INSTALL_PATH = "@loader_path/../Frameworks"; + PRODUCT_NAME = GoogleToolboxForMac; + }; + name = "LeopardOrLater-Debug"; + }; + F4CA86510DB3ACE500B4AB10 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests"; + }; + name = "LeopardOrLater-Debug"; + }; + F4CA86520DB3ACE500B4AB10 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AppKit"; + }; + name = "LeopardOrLater-Debug"; + }; + F4CA86530DB3ACE500B4AB10 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - Foundation"; + }; + name = "LeopardOrLater-Debug"; + }; + F4CA86540DB3ACE500B4AB10 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - UnitTesting"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; + }; + name = "LeopardOrLater-Debug"; + }; + F4CA86550DB3ACE500B4AB10 /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = UnitTesting/GTMUIUnitTestingHarness/Info.plist; + PRODUCT_NAME = GTMUIUnitTestingHarness; + }; + name = "LeopardOrLater-Debug"; + }; + F4CA865A0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4CA864A0DB3ACB200B4AB10 /* DebugLeopardOrLater.xcconfig */; + buildSettings = { + GCC_GENERATE_TEST_COVERAGE_FILES = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = GTM_Prefix.pch; + OTHER_LDFLAGS = "-lgcov"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F4CA865B0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4CA864C0DB3ACD200B4AB10 /* LoadableBundleGCSupported.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "GTM-Info.plist"; + INSTALL_PATH = "@loader_path/../Frameworks"; + PRODUCT_NAME = GoogleToolboxForMac; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F4CA865C0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F4CA865D0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AppKit"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F4CA865E0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - Foundation"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F4CA865F0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - UnitTesting"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F4CA86600DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = UnitTesting/GTMUIUnitTestingHarness/Info.plist; + PRODUCT_NAME = GTMUIUnitTestingHarness; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F4CA86610DB3AD0D00B4AB10 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4CA864B0DB3ACB200B4AB10 /* ReleaseLeopardOrLater.xcconfig */; + buildSettings = { + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = GTM_Prefix.pch; + }; + name = "LeopardOrLater-Release"; + }; + F4CA86620DB3AD0D00B4AB10 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4CA864C0DB3ACD200B4AB10 /* LoadableBundleGCSupported.xcconfig */; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "GTM-Info.plist"; + INSTALL_PATH = "@loader_path/../Frameworks"; + PRODUCT_NAME = GoogleToolboxForMac; + }; + name = "LeopardOrLater-Release"; + }; + F4CA86630DB3AD0D00B4AB10 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests"; + }; + name = "LeopardOrLater-Release"; + }; + F4CA86640DB3AD0D00B4AB10 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - AppKit"; + }; + name = "LeopardOrLater-Release"; + }; + F4CA86650DB3AD0D00B4AB10 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - Foundation"; + }; + name = "LeopardOrLater-Release"; + }; + F4CA86660DB3AD0D00B4AB10 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F48FE2450D197F9A009257D2 /* ReleaseUnittest.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(value)", + "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", + ); + INFOPLIST_FILE = "UnitTest-Info.plist"; + PRODUCT_NAME = "UnitTest - UnitTesting"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMUIUnitTestingHarness.app/Contents/MacOS/GTMUIUnitTestingHarness"; + }; + name = "LeopardOrLater-Release"; + }; + F4CA86670DB3AD0D00B4AB10 /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = UnitTesting/GTMUIUnitTestingHarness/Info.plist; + PRODUCT_NAME = GTMUIUnitTestingHarness; + }; + name = "LeopardOrLater-Release"; }; /* End XCBuildConfiguration section */ @@ -1234,72 +1533,93 @@ 1DEB918108733D990010E9CD /* Build configuration list for PBXProject "GTM" */ = { isa = XCConfigurationList; buildConfigurations = ( - 1DEB918208733D990010E9CD /* Debug */, - F48B91060D94489300D45044 /* Debug-gcov */, - 1DEB918308733D990010E9CD /* Release */, + 1DEB918208733D990010E9CD /* TigerOrLater-Debug */, + F48B91060D94489300D45044 /* TigerOrLater-Debug-gcov */, + 1DEB918308733D990010E9CD /* TigerOrLater-Release */, + F4CA864F0DB3ACE500B4AB10 /* LeopardOrLater-Debug */, + F4CA865A0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */, + F4CA86610DB3AD0D00B4AB10 /* LeopardOrLater-Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "TigerOrLater-Release"; }; 8B45A02D0DA4696D001148C5 /* Build configuration list for PBXNativeTarget "UnitTest - UnitTesting" */ = { isa = XCConfigurationList; buildConfigurations = ( - 8B45A02A0DA4696D001148C5 /* Debug */, - 8B45A02B0DA4696D001148C5 /* Debug-gcov */, - 8B45A02C0DA4696D001148C5 /* Release */, + 8B45A02A0DA4696D001148C5 /* TigerOrLater-Debug */, + 8B45A02B0DA4696D001148C5 /* TigerOrLater-Debug-gcov */, + 8B45A02C0DA4696D001148C5 /* TigerOrLater-Release */, + F4CA86540DB3ACE500B4AB10 /* LeopardOrLater-Debug */, + F4CA865F0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */, + F4CA86660DB3AD0D00B4AB10 /* LeopardOrLater-Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "TigerOrLater-Release"; }; 8B45A2910DA49B9A001148C5 /* Build configuration list for PBXNativeTarget "UIUnitTestingHarness" */ = { isa = XCConfigurationList; buildConfigurations = ( - 8B45A28E0DA49B99001148C5 /* Debug */, - 8B45A28F0DA49B99001148C5 /* Debug-gcov */, - 8B45A2900DA49B99001148C5 /* Release */, + 8B45A28E0DA49B99001148C5 /* TigerOrLater-Debug */, + 8B45A28F0DA49B99001148C5 /* TigerOrLater-Debug-gcov */, + 8B45A2900DA49B99001148C5 /* TigerOrLater-Release */, + F4CA86550DB3ACE500B4AB10 /* LeopardOrLater-Debug */, + F4CA86600DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */, + F4CA86670DB3AD0D00B4AB10 /* LeopardOrLater-Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "TigerOrLater-Release"; }; F42E081E0D19987200D5DDE0 /* Build configuration list for PBXNativeTarget "UnitTest - Foundation" */ = { isa = XCConfigurationList; buildConfigurations = ( - F42E081F0D19987200D5DDE0 /* Debug */, - F48B910A0D94489300D45044 /* Debug-gcov */, - F42E08200D19987200D5DDE0 /* Release */, + F42E081F0D19987200D5DDE0 /* TigerOrLater-Debug */, + F48B910A0D94489300D45044 /* TigerOrLater-Debug-gcov */, + F42E08200D19987200D5DDE0 /* TigerOrLater-Release */, + F4CA86530DB3ACE500B4AB10 /* LeopardOrLater-Debug */, + F4CA865E0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */, + F4CA86650DB3AD0D00B4AB10 /* LeopardOrLater-Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "TigerOrLater-Release"; }; F42E086F0D199A5C00D5DDE0 /* Build configuration list for PBXNativeTarget "GTM" */ = { isa = XCConfigurationList; buildConfigurations = ( - F42E08700D199A5C00D5DDE0 /* Debug */, - F48B91070D94489300D45044 /* Debug-gcov */, - F42E08710D199A5C00D5DDE0 /* Release */, + F42E08700D199A5C00D5DDE0 /* TigerOrLater-Debug */, + F48B91070D94489300D45044 /* TigerOrLater-Debug-gcov */, + F42E08710D199A5C00D5DDE0 /* TigerOrLater-Release */, + F4CA86500DB3ACE500B4AB10 /* LeopardOrLater-Debug */, + F4CA865B0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */, + F4CA86620DB3AD0D00B4AB10 /* LeopardOrLater-Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "TigerOrLater-Release"; }; F47204340D33BEDF00E9F378 /* Build configuration list for PBXAggregateTarget "All UnitTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - F47204350D33BEDF00E9F378 /* Debug */, - F48B91080D94489300D45044 /* Debug-gcov */, - F47204360D33BEDF00E9F378 /* Release */, + F47204350D33BEDF00E9F378 /* TigerOrLater-Debug */, + F48B91080D94489300D45044 /* TigerOrLater-Debug-gcov */, + F47204360D33BEDF00E9F378 /* TigerOrLater-Release */, + F4CA86510DB3ACE500B4AB10 /* LeopardOrLater-Debug */, + F4CA865C0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */, + F4CA86630DB3AD0D00B4AB10 /* LeopardOrLater-Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "TigerOrLater-Release"; }; F48FE2660D198C1F009257D2 /* Build configuration list for PBXNativeTarget "UnitTest - AppKit" */ = { isa = XCConfigurationList; buildConfigurations = ( - F48FE2670D198C1F009257D2 /* Debug */, - F48B91090D94489300D45044 /* Debug-gcov */, - F48FE2680D198C1F009257D2 /* Release */, + F48FE2670D198C1F009257D2 /* TigerOrLater-Debug */, + F48B91090D94489300D45044 /* TigerOrLater-Debug-gcov */, + F48FE2680D198C1F009257D2 /* TigerOrLater-Release */, + F4CA86520DB3ACE500B4AB10 /* LeopardOrLater-Debug */, + F4CA865D0DB3AD0300B4AB10 /* LeopardOrLater-Debug-gcov */, + F4CA86640DB3AD0D00B4AB10 /* LeopardOrLater-Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "TigerOrLater-Release"; }; /* End XCConfigurationList section */ }; diff --git a/GTMDefines.h b/GTMDefines.h index b8cd1ec..8bc1385 100644 --- a/GTMDefines.h +++ b/GTMDefines.h @@ -23,6 +23,19 @@ // is compiled. // ---------------------------------------------------------------------------- + +// GTMHTTPFetcher will support logging by default but only hook its input +// stream support for logging when requested. You can control the inclusion of +// the code by providing your own definitions for these w/in a prefix header. +// +#ifndef GTM_HTTPFETCHER_ENABLE_LOGGING +# define GTM_HTTPFETCHER_ENABLE_LOGGING 1 +#endif // GTM_HTTPFETCHER_ENABLE_LOGGING +#ifndef GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING +# define GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING 0 +#endif // GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING + + // _GTMDevLog & _GTMDevAssert // // _GTMDevLog & _GTMDevAssert are meant to be a very lightweight shell for @@ -89,3 +102,37 @@ #define GTM_MACOS_SDK 1 #endif +// To simplify support for 64bit (and Leopard in general), we provide the type +// defines for non Leopard SDKs +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 + // NSInteger/NSUInteger and Max/Mins + #ifndef NSINTEGER_DEFINED + #if __LP64__ || NS_BUILD_32_LIKE_64 + typedef long NSInteger; + typedef unsigned long NSUInteger; + #else + typedef int NSInteger; + typedef unsigned int NSUInteger; + #endif + #define NSIntegerMax LONG_MAX + #define NSIntegerMin LONG_MIN + #define NSUIntegerMax ULONG_MAX + #define NSINTEGER_DEFINED 1 + #endif // NSINTEGER_DEFINED + // CGFloat + #ifndef CGFLOAT_DEFINED + #if defined(__LP64__) && __LP64__ + // This really is an untested path (64bit on Tiger?) + typedef double CGFloat; + #define CGFLOAT_MIN DBL_MIN + #define CGFLOAT_MAX DBL_MAX + #define CGFLOAT_IS_DOUBLE 1 + #else /* !defined(__LP64__) || !__LP64__ */ + typedef float CGFloat; + #define CGFLOAT_MIN FLT_MIN + #define CGFLOAT_MAX FLT_MAX + #define CGFLOAT_IS_DOUBLE 0 + #endif /* !defined(__LP64__) || !__LP64__ */ + #define CGFLOAT_DEFINED 1 + #endif // CGFLOAT_DEFINED +#endif // MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj index aa2f713..0baf1b8 100644 --- a/GTMiPhone.xcodeproj/project.pbxproj +++ b/GTMiPhone.xcodeproj/project.pbxproj @@ -6,6 +6,20 @@ objectVersion = 45; objects = { +/* Begin PBXAggregateTarget section */ + F4C7F9BF0DC62EC8009BEE5B /* All UnitTests */ = { + isa = PBXAggregateTarget; + buildConfigurationList = F4C7F9C50DC62F0C009BEE5B /* Build configuration list for PBXAggregateTarget "All UnitTests" */; + buildPhases = ( + ); + dependencies = ( + F4C7F9C40DC62ECD009BEE5B /* PBXTargetDependency */, + ); + name = "All UnitTests"; + productName = "All UnitTests"; + }; +/* End PBXAggregateTarget section */ + /* Begin PBXBuildFile section */ 1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; @@ -37,12 +51,24 @@ 8BC0486C0DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BC048010DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.png */; }; 8BC04A720DAF144700C2D1CA /* GTMSystemVersionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04A710DAF144700C2D1CA /* GTMSystemVersionTest.m */; }; 8BC04A750DAF145200C2D1CA /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04A740DAF145200C2D1CA /* GTMSystemVersion.m */; }; - 8BC04C470DAFFCF300C2D1CA /* GTMNSString+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04C450DAFFCF300C2D1CA /* GTMNSString+Utilities.m */; }; - 8BC04C480DAFFCF300C2D1CA /* GTMNSString+UtilitiesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04C460DAFFCF300C2D1CA /* GTMNSString+UtilitiesTest.m */; }; 8BC04D480DB0088500C2D1CA /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC04D470DB0088500C2D1CA /* libz.dylib */; }; 8BC04DE80DB023D400C2D1CA /* ReleaseNotes.txt in Resources */ = {isa = PBXBuildFile; fileRef = 8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */; }; + F439ADEB0DBD3C0000BE9B91 /* GTMBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = F439ADE90DBD3C0000BE9B91 /* GTMBase64.m */; }; + F439ADEC0DBD3C0000BE9B91 /* GTMBase64Test.m in Sources */ = {isa = PBXBuildFile; fileRef = F439ADEA0DBD3C0000BE9B91 /* GTMBase64Test.m */; }; + F439ADF00DBD3C4000BE9B91 /* GTMGeometryUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = F439ADEE0DBD3C4000BE9B91 /* GTMGeometryUtils.m */; }; + F439ADF10DBD3C4000BE9B91 /* GTMGeometryUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F439ADEF0DBD3C4000BE9B91 /* GTMGeometryUtilsTest.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + F4C7F9C30DC62ECD009BEE5B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1D6058900D05DD3D006BFB54 /* GTMiPhoneUnitTesting */; + remoteInfo = GTMiPhoneUnitTesting; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -99,11 +125,14 @@ 8BC04A6F0DAF144200C2D1CA /* GTMSystemVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSystemVersion.h; sourceTree = "<group>"; }; 8BC04A710DAF144700C2D1CA /* GTMSystemVersionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersionTest.m; sourceTree = "<group>"; }; 8BC04A740DAF145200C2D1CA /* GTMSystemVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersion.m; sourceTree = "<group>"; }; - 8BC04C440DAFFCF300C2D1CA /* GTMNSString+Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+Utilities.h"; sourceTree = "<group>"; }; - 8BC04C450DAFFCF300C2D1CA /* GTMNSString+Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+Utilities.m"; sourceTree = "<group>"; }; - 8BC04C460DAFFCF300C2D1CA /* GTMNSString+UtilitiesTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+UtilitiesTest.m"; sourceTree = "<group>"; }; 8BC04D470DB0088500C2D1CA /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ReleaseNotes.txt; sourceTree = "<group>"; }; + F439ADE80DBD3C0000BE9B91 /* GTMBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMBase64.h; sourceTree = "<group>"; }; + F439ADE90DBD3C0000BE9B91 /* GTMBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64.m; sourceTree = "<group>"; }; + F439ADEA0DBD3C0000BE9B91 /* GTMBase64Test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64Test.m; sourceTree = "<group>"; }; + F439ADED0DBD3C4000BE9B91 /* GTMGeometryUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGeometryUtils.h; sourceTree = "<group>"; }; + F439ADEE0DBD3C4000BE9B91 /* GTMGeometryUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMGeometryUtils.m; sourceTree = "<group>"; }; + F439ADEF0DBD3C4000BE9B91 /* GTMGeometryUtilsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMGeometryUtilsTest.m; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -134,13 +163,13 @@ isa = PBXGroup; children = ( 8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */, - 8BC049840DAEC59100C2D1CA /* XcodeConfig */, - 8B308AF40DAD070C00183556 /* RunIPhoneUnitTest.sh */, 8BC047750DAE926E00C2D1CA /* GTMDefines.h */, 8BC047760DAE928A00C2D1CA /* Foundation */, 8BC0479A0DAE928A00C2D1CA /* DebugUtils */, 8BC0479F0DAE928A00C2D1CA /* UnitTesting */, + 8BC049840DAEC59100C2D1CA /* XcodeConfig */, 32CA4F630368D1EE00C91783 /* GTMiPhone_Prefix.pch */, + 8B308AF40DAD070C00183556 /* RunIPhoneUnitTest.sh */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); @@ -162,16 +191,19 @@ 8BC047760DAE928A00C2D1CA /* Foundation */ = { isa = PBXGroup; children = ( + F439ADE80DBD3C0000BE9B91 /* GTMBase64.h */, + F439ADE90DBD3C0000BE9B91 /* GTMBase64.m */, + F439ADEA0DBD3C0000BE9B91 /* GTMBase64Test.m */, 8BC047770DAE928A00C2D1CA /* GTMCalculatedRange.h */, 8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */, 8BC047790DAE928A00C2D1CA /* GTMCalculatedRangeTest.m */, 8BC0477A0DAE928A00C2D1CA /* GTMGarbageCollection.h */, + F439ADED0DBD3C4000BE9B91 /* GTMGeometryUtils.h */, + F439ADEE0DBD3C4000BE9B91 /* GTMGeometryUtils.m */, + F439ADEF0DBD3C4000BE9B91 /* GTMGeometryUtilsTest.m */, 8BC0477E0DAE928A00C2D1CA /* GTMNSData+zlib.h */, 8BC0477F0DAE928A00C2D1CA /* GTMNSData+zlib.m */, 8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */, - 8BC04C440DAFFCF300C2D1CA /* GTMNSString+Utilities.h */, - 8BC04C450DAFFCF300C2D1CA /* GTMNSString+Utilities.m */, - 8BC04C460DAFFCF300C2D1CA /* GTMNSString+UtilitiesTest.m */, 8BC047810DAE928A00C2D1CA /* GTMNSEnumerator+Filter.h */, 8BC047820DAE928A00C2D1CA /* GTMNSEnumerator+Filter.m */, 8BC047830DAE928A00C2D1CA /* GTMNSEnumerator+FilterTest.m */, @@ -220,9 +252,9 @@ 8BC047ED0DAE928A00C2D1CA /* GTMNSObject+UnitTesting.m */, 8BC047F60DAE928A00C2D1CA /* GTMSenTestCase.h */, 8BC047F70DAE928A00C2D1CA /* GTMSenTestCase.m */, - 8BC048000DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.gtmUTState */, 8BC048010DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.png */, 8BC0480E0DAE928A00C2D1CA /* RunIPhoneUnitTest.sh */, + F435E49F0DC8F5290069CDE8 /* TestData */, ); path = UnitTesting; sourceTree = "<group>"; @@ -253,6 +285,14 @@ path = Project; sourceTree = "<group>"; }; + F435E49F0DC8F5290069CDE8 /* TestData */ = { + isa = PBXGroup; + children = ( + 8BC048000DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.gtmUTState */, + ); + path = TestData; + sourceTree = "<group>"; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -286,6 +326,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( + F4C7F9BF0DC62EC8009BEE5B /* All UnitTests */, 1D6058900D05DD3D006BFB54 /* GTMiPhoneUnitTesting */, ); }; @@ -347,15 +388,25 @@ 8BC048650DAE928A00C2D1CA /* GTMSenTestCase.m in Sources */, 8BC04A720DAF144700C2D1CA /* GTMSystemVersionTest.m in Sources */, 8BC04A750DAF145200C2D1CA /* GTMSystemVersion.m in Sources */, - 8BC04C470DAFFCF300C2D1CA /* GTMNSString+Utilities.m in Sources */, - 8BC04C480DAFFCF300C2D1CA /* GTMNSString+UtilitiesTest.m in Sources */, 8B5547CA0DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m in Sources */, 8B5547CB0DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m in Sources */, + F439ADEB0DBD3C0000BE9B91 /* GTMBase64.m in Sources */, + F439ADEC0DBD3C0000BE9B91 /* GTMBase64Test.m in Sources */, + F439ADF00DBD3C4000BE9B91 /* GTMGeometryUtils.m in Sources */, + F439ADF10DBD3C4000BE9B91 /* GTMGeometryUtilsTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + F4C7F9C40DC62ECD009BEE5B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1D6058900D05DD3D006BFB54 /* GTMiPhoneUnitTesting */; + targetProxy = F4C7F9C30DC62ECD009BEE5B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 1D6058940D05DD3E006BFB54 /* Debug */ = { isa = XCBuildConfiguration; @@ -407,6 +458,27 @@ }; name = Release; }; + F4C7F9C00DC62EC8009BEE5B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests"; + }; + name = Debug; + }; + F4C7F9C10DC62EC8009BEE5B /* Debug-gcov */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests"; + }; + name = "Debug-gcov"; + }; + F4C7F9C20DC62EC8009BEE5B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -430,6 +502,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F4C7F9C50DC62F0C009BEE5B /* Build configuration list for PBXAggregateTarget "All UnitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F4C7F9C00DC62EC8009BEE5B /* Debug */, + F4C7F9C10DC62EC8009BEE5B /* Debug-gcov */, + F4C7F9C20DC62EC8009BEE5B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index f86bfe6..29cf35a 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -7,7 +7,10 @@ Discussion group: http://groups.google.com/group/google-toolbox-for-mac Release ?.?.? Changes since 1.0.0 -- Updated the project so Xcode 3 is also happy. +- Updated the project to Xcode 3. This is the only supported Xcode version + for the project. The code can build against the Tiger or Leopard SDKs, and + developers can pull individual files into a Xcode 2.x project and things + should work just fine. - Fixed up the prefix header of the project and prefix handing in the Unittest Xcode Config. (thanks schafdog) @@ -84,6 +87,21 @@ Changes since 1.0.0 https://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?bundleID=19915 +- Remove NSColor+Theme and NSWorkspace+Theme as they are no longer needed for + testing things for unittests, instead GTMUnitTestingUtilities.m(Lines 64-79) + force the user settable things to ensure tests are consistent. + +- Added GTMBase64. + +- Added GTMHTTPFetcher and GTMProgressMonitorInputStream. + +- Moved the data files for unittests into subdirectories call TestData to + help make it a little easier to find files w/in the main directories. + +- GTMDelegatingTableColumn get an overhaul to match the 10.5 sdk so it's closer + to a dropin for previous sdks. + + Release 1.0.0 14-January-2008 diff --git a/UnitTesting/GTMAppKit+UnitTesting.m b/UnitTesting/GTMAppKit+UnitTesting.m index 85f03a2..6cb5a8b 100644 --- a/UnitTesting/GTMAppKit+UnitTesting.m +++ b/UnitTesting/GTMAppKit+UnitTesting.m @@ -23,21 +23,34 @@ #import "GTMGeometryUtils.h" #import "GTMMethodCheck.h" +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 + #define ENCODE_NSINTEGER(coder, i, key) [(coder) encodeInt:(i) forKey:(key)] +#else + #define ENCODE_NSINTEGER(coder, i, key) [(coder) encodeInteger:(i) forKey:(key)] +#endif + @implementation NSApplication (GMUnitTestingAdditions) GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:); // COV_NF_LINE - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { [super gtm_unitTestEncodeState:inCoder]; - [inCoder encodeInt:[[self mainWindow] windowNumber] - forKey:@"ApplicationMainWindow"]; - + ENCODE_NSINTEGER(inCoder, [[self mainWindow] windowNumber], @"ApplicationMainWindow"); + // Descend down into the windows allowing them to store their state NSEnumerator *windowEnum = [[self windows] objectEnumerator]; NSWindow *window = nil; int i = 0; while ((window = [windowEnum nextObject])) { - [inCoder encodeObject:window forKey:[NSString stringWithFormat:@"Window %d", i]]; - i = i + 1; + if ([window isVisible]) { + // Only record visible windows because invisible windows may be closing on us + // This appears to happen differently in 64 bit vs 32 bit, and items + // in the window may hold an extra retain count for a while until the + // event loop is spun. To avoid all this, we just don't record non + // visible windows. + // See rdar://5851458 for details. + [inCoder encodeObject:window forKey:[NSString stringWithFormat:@"Window %d", i]]; + i = i + 1; + } } // and encode the menu bar @@ -81,7 +94,7 @@ GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:); // COV_NF_LINE [inCoder encodeObject:[self class] forKey:@"ControlType"]; [inCoder encodeObject:[self objectValue] forKey:@"ControlValue"]; [inCoder encodeObject:[self selectedCell] forKey:@"ControlSelectedCell"]; - [inCoder encodeInt:[self tag] forKey:@"ControlTag"]; + ENCODE_NSINTEGER(inCoder, [self tag], @"ControlTag"); [inCoder encodeBool:[self isEnabled] forKey:@"ControlIsEnabled"]; } @@ -117,8 +130,8 @@ GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:); // COV_NF_LINE // is going to be in the CellValue encoding. [inCoder encodeObject:[self title] forKey:@"CellTitle"]; } - [inCoder encodeInt:[self state] forKey:@"CellState"]; - [inCoder encodeInt:[self tag] forKey:@"CellTag"]; + ENCODE_NSINTEGER(inCoder, [self state], @"CellState"); + ENCODE_NSINTEGER(inCoder, [self tag], @"CellTag"); } @end @@ -163,8 +176,19 @@ GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:); // COV_NF_LINE // inCoder - the coder to encode our state into - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { [super gtm_unitTestEncodeState:inCoder]; - [inCoder encodeObject:[self title] forKey:@"MenuTitle"]; - + // Hack here to work around + // rdar://5881796 Application menu item title wrong when accessed programatically + // which causes us to have different results on x86_64 vs x386. + // Hack is braced intentionally. We don't record the title of the + // "application" menu or it's menu title because they are wrong on 32 bit. + // They appear to work right on 64bit. + { + NSMenu *mainMenu = [NSApp mainMenu]; + NSMenu *appleMenu = [[mainMenu itemAtIndex:0] submenu]; + if (![self isEqual:appleMenu]) { + [inCoder encodeObject:[self title] forKey:@"MenuTitle"]; + } + } // Descend down into the menuitems allowing them to store their state NSEnumerator *menuItemEnum = [[self itemArray] objectEnumerator]; NSMenuItem *menuItem = nil; @@ -179,15 +203,25 @@ GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:); // COV_NF_LINE - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder { [super gtm_unitTestEncodeState:inCoder]; - [inCoder encodeObject:[self title] forKey:@"MenuItemTitle"]; + // Hack here to work around + // rdar://5881796 Application menu item title wrong when accessed programatically + // which causes us to have different results on x86_64 vs x386. + // See comment above. + { + NSMenu *mainMenu = [NSApp mainMenu]; + NSMenuItem *appleMenuItem = [mainMenu itemAtIndex:0]; + if (![self isEqual:appleMenuItem]) { + [inCoder encodeObject:[self title] forKey:@"MenuItemTitle"]; + } + } [inCoder encodeObject:[self keyEquivalent] forKey:@"MenuItemKeyEquivalent"]; [inCoder encodeBool:[self isSeparatorItem] forKey:@"MenuItemIsSeparator"]; - [inCoder encodeInt:[self state] forKey:@"MenuItemState"]; + ENCODE_NSINTEGER(inCoder, [self state], @"MenuItemState"); [inCoder encodeBool:[self isEnabled] forKey:@"MenuItemIsEnabled"]; [inCoder encodeBool:[self isAlternate] forKey:@"MenuItemIsAlternate"]; [inCoder encodeObject:[self toolTip] forKey:@"MenuItemTooltip"]; - [inCoder encodeInt:[self tag] forKey:@"MenuItemTag"]; - [inCoder encodeInt:[self indentationLevel] forKey:@"MenuItemIndentationLevel"]; + ENCODE_NSINTEGER(inCoder, [self tag], @"MenuItemTag"); + ENCODE_NSINTEGER(inCoder, [self indentationLevel], @"MenuItemIndentationLevel"); // Do our submenu if neccessary if ([self hasSubmenu]) { @@ -221,7 +255,7 @@ GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:); // COV_NF_LINE return self; } -- (void) dealloc { +- (void)dealloc { [drawer_ release]; [super dealloc]; } diff --git a/UnitTesting/GTMIPhoneUnitTestMain.m b/UnitTesting/GTMIPhoneUnitTestMain.m index 2af060b..16a3907 100644 --- a/UnitTesting/GTMIPhoneUnitTestMain.m +++ b/UnitTesting/GTMIPhoneUnitTestMain.m @@ -43,11 +43,11 @@ static int MethodSort(const void *a, const void *b) { @implementation GTMIPhoneUnitTestDelegate // Return YES if class is subclass (1 or more generations) of SenTestCase -- (BOOL)isTestFixture:(Class)class { +- (BOOL)isTestFixture:(Class)aClass { BOOL iscase = NO; Class testCaseClass = [SenTestCase class]; Class superclass; - for (superclass = class; + for (superclass = aClass; !iscase && superclass; superclass = class_getSuperclass(superclass)) { iscase = superclass == testCaseClass ? YES : NO; diff --git a/UnitTesting/GTMNSObject+BindingUnitTesting.m b/UnitTesting/GTMNSObject+BindingUnitTesting.m index 03d723d..7773542 100644 --- a/UnitTesting/GTMNSObject+BindingUnitTesting.m +++ b/UnitTesting/GTMNSObject+BindingUnitTesting.m @@ -306,11 +306,14 @@ BOOL GTMDoExposedBindingsFunctionCorrectly(NSObject *object, - (NSMutableDictionary*)gtm_unitTestExposedBindingsTestValues:(NSString*)binding { NSMutableDictionary *dict = [super gtm_unitTestExposedBindingsTestValues:binding]; +#if !__LP64__ if ([binding isEqualToString:NSAlignmentBinding]) { // rdar://5851491 - Setting NSAlignmentBinding of search field to NSCenterTextAlignment broken + // This appears to not be a bug in 64 bit. Strange. [dict setObject:[NSNumber numberWithInt:NSNaturalTextAlignment] forKey:[NSNumber numberWithInt:NSCenterTextAlignment]]; } +#endif return dict; } diff --git a/UnitTesting/GTMNSObject+UnitTesting.m b/UnitTesting/GTMNSObject+UnitTesting.m index 6aff70c..34aeb58 100644 --- a/UnitTesting/GTMNSObject+UnitTesting.m +++ b/UnitTesting/GTMNSObject+UnitTesting.m @@ -202,6 +202,8 @@ BOOL GTMIsObjectStateEqualToStateNamed(id object, - (NSString *)gtm_saveToPathForFileNamed:(NSString*)name extension:(NSString*)extension; - (CGImageRef)gtm_createUnitTestImage; +// Returns nil if there is no override +- (NSString *)gtm_getOverrideDefaultUnitTestSaveToDirectory; @end // This is a keyed coder for storing unit test state data. It is used only by @@ -388,12 +390,17 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Desktop"]; -#else +#else NSArray *desktopDirs = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); gGTMUnitTestSaveToDirectory = [desktopDirs objectAtIndex:0]; #endif + // Did we get overridden? + NSString *override = [self gtm_getOverrideDefaultUnitTestSaveToDirectory]; + if (override) { + gGTMUnitTestSaveToDirectory = override; + } [gGTMUnitTestSaveToDirectory retain]; } result = gGTMUnitTestSaveToDirectory; @@ -402,6 +409,29 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; return result; } +// Return nil if there is no override +- (NSString *)gtm_getOverrideDefaultUnitTestSaveToDirectory { + NSString *result = nil; + + // If we have an environment variable that ends in "BUILD_NUMBER" odds are + // we're on an automated build system, so use the build products dir as an + // override instead of writing on the desktop. + NSDictionary *env = [[NSProcessInfo processInfo] environment]; + NSEnumerator *enumerator = [env keyEnumerator]; + NSString *key = nil; + while (((key = [enumerator nextObject]) != nil) && + ![key hasSuffix:@"BUILD_NUMBER"]) + ; + if (key) { + result = [env objectForKey:@"BUILT_PRODUCTS_DIR"]; + } + + if (result && [result length] == 0) { + result = nil; + } + return result; +} + /// Find the path for a file named name.extension in your bundle. // Searches for the following: // "name.extension", @@ -443,15 +473,33 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; systemVersions[2] = [NSString stringWithFormat:@".%d", major]; systemVersions[3] = @""; NSString *extensions[2]; -#if GTM_IPHONE_SDK +#if GTM_IPHONE_SDK extensions[0] = @".iPhone"; -#else - const NXArchInfo *localInfo = NXGetLocalArchInfo(); - _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); - const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0); - _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo"); - extensions[0] = [NSString stringWithFormat:@".%s", genericInfo->name]; -#endif +#else // !GTM_IPHONE_SDK + // In reading arch(3) you'd thing this would work: + // + // const NXArchInfo *localInfo = NXGetLocalArchInfo(); + // _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); + // const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0); + // _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo"); + // extensions[0] = [NSString stringWithFormat:@".%s", genericInfo->name]; + // + // but on 64bit it returns the same things as on 32bit, so... +#if __POWERPC__ + #if __LP64__ + extensions[0] = @".ppc64"; + #else // !__LP64__ + extensions[0] = @".ppc"; + #endif // __LP64__ +#else // !__POWERPC__ + #if __LP64__ + extensions[0] = @".x86_64"; + #else // !__LP64__ + extensions[0] = @".i386"; + #endif // __LP64__ +#endif // !__POWERPC__ + +#endif // GTM_IPHONE_SDK extensions[1] = @""; int i,j; @@ -481,11 +529,29 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; #if GTM_IPHONE_SDK system = "iPhone"; #else - const NXArchInfo *localInfo = NXGetLocalArchInfo(); - _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); - const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0); - _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo"); - system = genericInfo->name; + // In reading arch(3) you'd thing this would work: + // + // const NXArchInfo *localInfo = NXGetLocalArchInfo(); + // _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo"); + // const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0); + // _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo"); + // system = genericInfo->name; + // + // but on 64bit it returns the same things as on 32bit, so... +#if __POWERPC__ + #if __LP64__ + system = "ppc64"; + #else // !__LP64__ + system = "ppc"; + #endif // __LP64__ +#else // !__POWERPC__ + #if __LP64__ + system = "x86_64"; + #else // !__LP64__ + system = "i386"; + #endif // __LP64__ +#endif // !__POWERPC__ + #endif long major, minor, bugFix; [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; @@ -530,7 +596,7 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; _GTMDevAssert(cs, @"Couldn't create colorspace"); CGBitmapInfo info = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault; if (data) { - *data = calloc(bytesPerRow, height); + *data = (unsigned char*)calloc(bytesPerRow, height); _GTMDevAssert(*data, @"Couldn't create bitmap"); } context = CGBitmapContextCreate(data ? *data : NULL, width, height, @@ -618,7 +684,7 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; NSDictionary *tiffDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:NSTIFFCompressionLZW] forKey:(NSString*)kCGImagePropertyTIFFCompression]; NSDictionary *destProps = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithFloat:1.0], + [NSNumber numberWithFloat:1.0f], (NSString*)kCGImageDestinationLossyCompressionQuality, tiffDict, (NSString*)kCGImagePropertyTIFFDictionary, @@ -756,12 +822,12 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; _GTMDevAssert(diffContext, @"Can't make diff context"); size_t diffRowBytes = CGBitmapContextGetBytesPerRow(diffContext); for (row = 0; row < imageHeight; row++) { - unsigned long *imageRow = (unsigned long*)(imageData + imageBytesPerRow * row); - unsigned long *fileRow = (unsigned long*)(fileData + fileBytesPerRow * row); - unsigned long* diffRow = (unsigned long*)(diffData + diffRowBytes * row); + uint32_t *imageRow = (uint32_t*)(imageData + imageBytesPerRow * row); + uint32_t *fileRow = (uint32_t*)(fileData + fileBytesPerRow * row); + uint32_t* diffRow = (uint32_t*)(diffData + diffRowBytes * row); for (col = 0; col < imageWidth; col++) { - unsigned long imageColor = imageRow[col]; - unsigned long fileColor = fileRow[col]; + uint32_t imageColor = imageRow[col]; + uint32_t fileColor = fileRow[col]; unsigned char imageAlpha = imageColor & 0xF; unsigned char imageBlue = imageColor >> 8 & 0xF; @@ -783,12 +849,12 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; almostEqual(imageAlpha, fileAlpha); answer &= equal; if (diff) { - unsigned long newColor; + uint32_t newColor; if (equal) { - newColor = (((unsigned long)imageRed) << 24) + - (((unsigned long)imageGreen) << 16) + - (((unsigned long)imageBlue) << 8) + - (((unsigned long)imageAlpha) / 2); + newColor = (((uint32_t)imageRed) << 24) + + (((uint32_t)imageGreen) << 16) + + (((uint32_t)imageBlue) << 8) + + (((uint32_t)imageAlpha) / 2); } else { newColor = 0xFF0000FF; } diff --git a/UnitTesting/GTMUnitTestingBindingTest.m b/UnitTesting/GTMUnitTestingBindingTest.m index c0ef93c..d27e8e7 100644 --- a/UnitTesting/GTMUnitTestingBindingTest.m +++ b/UnitTesting/GTMUnitTestingBindingTest.m @@ -48,6 +48,7 @@ NSWindow *window = [testWindowController window]; GTMTestExposedBindings(window, @"Window failed binding test"); [self doSubviewBindingTest:[window contentView]]; + [window close]; [testWindowController release]; // Run a test against something with no bindings. diff --git a/UnitTesting/GTMUnitTestingTest.m b/UnitTesting/GTMUnitTestingTest.m index f152428..cc28a1a 100644 --- a/UnitTesting/GTMUnitTestingTest.m +++ b/UnitTesting/GTMUnitTestingTest.m @@ -204,18 +204,6 @@ NSString *const kGTMWindowSaveFileName = @"GTMUnitTestingWindow"; return field_; } -- (void)dealloc { - NSWindow *window = [self window]; - int count = [window retainCount]; - - // Spinning the run loop here to get rid of the window. Stupid issue - // where there's a delayed selector holding a retain count on our window - // rdar://5851458 - Closing a window with a NSTextView in it should get rid of it immediately - while (count == [window retainCount]) { - [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; - } - [super dealloc]; -} @end @implementation GTMUnitTestingDelegate diff --git a/UnitTesting/GTMUnitTestingUtilities.m b/UnitTesting/GTMUnitTestingUtilities.m index 7c1b915..a37b7dd 100644 --- a/UnitTesting/GTMUnitTestingUtilities.m +++ b/UnitTesting/GTMUnitTestingUtilities.m @@ -54,8 +54,8 @@ static void RestoreColorProfile(void); // Sets up the user interface so that we can run consistent UI unittests on it. + (void)setUpForUIUnitTests { // Give some names to undocumented defaults values - static const int MediumFontSmoothing = 2; - static const int BlueTintedAppearance = 1; + const NSInteger MediumFontSmoothing = 2; + const NSInteger BlueTintedAppearance = 1; // This sets up some basic values that we want as our defaults for doing pixel // based user interface tests. These defaults only apply to the unit test app, @@ -78,7 +78,7 @@ static void RestoreColorProfile(void); // Use english plz [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"]; // How fast should we draw sheets. This speeds up the sheet tests considerably - [defaults setFloat:.001 forKey:@"NSWindowResizeTime"]; + [defaults setFloat:.001f forKey:@"NSWindowResizeTime"]; // Switch over the screen profile to "generic rgb". This installs an // atexit handler to return our profile back when we are done. SetColorProfileToGenericRGB(); @@ -111,7 +111,7 @@ BOOL AreCMProfilesEqual(CMProfileRef a, CMProfileRef b) { static void RestoreColorProfile(void) { if (gCurrentColorProfile) { CGDirectDisplayID displayID = CGMainDisplayID(); - int error = CMSetProfileByAVID((UInt32)displayID, gCurrentColorProfile); + CMError error = CMSetProfileByAVID((UInt32)displayID, gCurrentColorProfile); if (error) { // COV_NF_START // No way to force this case in a unittest. diff --git a/UnitTesting/GTMUIViewUnitTestingTest.gtmUTState b/UnitTesting/TestData/GTMUIViewUnitTestingTest.gtmUTState index 87ae9e5..87ae9e5 100644 --- a/UnitTesting/GTMUIViewUnitTestingTest.gtmUTState +++ b/UnitTesting/TestData/GTMUIViewUnitTestingTest.gtmUTState diff --git a/UnitTesting/GTMUIViewUnitTestingTest.png b/UnitTesting/TestData/GTMUIViewUnitTestingTest.png Binary files differindex 03fd9f0..03fd9f0 100644 --- a/UnitTesting/GTMUIViewUnitTestingTest.png +++ b/UnitTesting/TestData/GTMUIViewUnitTestingTest.png diff --git a/UnitTesting/GTMUnitTestingImage.gtmUTState b/UnitTesting/TestData/GTMUnitTestingImage.gtmUTState index 969ddf6..969ddf6 100644 --- a/UnitTesting/GTMUnitTestingImage.gtmUTState +++ b/UnitTesting/TestData/GTMUnitTestingImage.gtmUTState diff --git a/UnitTesting/GTMUnitTestingImage.tiff b/UnitTesting/TestData/GTMUnitTestingImage.tiff Binary files differindex 4d08297..4d08297 100644 --- a/UnitTesting/GTMUnitTestingImage.tiff +++ b/UnitTesting/TestData/GTMUnitTestingImage.tiff diff --git a/UnitTesting/GTMUnitTestingTest.nib/classes.nib b/UnitTesting/TestData/GTMUnitTestingTest.nib/classes.nib index cc51f67..cc51f67 100644 --- a/UnitTesting/GTMUnitTestingTest.nib/classes.nib +++ b/UnitTesting/TestData/GTMUnitTestingTest.nib/classes.nib diff --git a/UnitTesting/GTMUnitTestingTest.nib/info.nib b/UnitTesting/TestData/GTMUnitTestingTest.nib/info.nib index 3fb1618..3fb1618 100644 --- a/UnitTesting/GTMUnitTestingTest.nib/info.nib +++ b/UnitTesting/TestData/GTMUnitTestingTest.nib/info.nib diff --git a/UnitTesting/GTMUnitTestingTest.nib/keyedobjects.nib b/UnitTesting/TestData/GTMUnitTestingTest.nib/keyedobjects.nib Binary files differindex d3104e2..d3104e2 100644 --- a/UnitTesting/GTMUnitTestingTest.nib/keyedobjects.nib +++ b/UnitTesting/TestData/GTMUnitTestingTest.nib/keyedobjects.nib diff --git a/UnitTesting/GTMUnitTestingTestApp.gtmUTState b/UnitTesting/TestData/GTMUnitTestingTestApp.gtmUTState index 1b7c4e5..03e611c 100644 --- a/UnitTesting/GTMUnitTestingTestApp.gtmUTState +++ b/UnitTesting/TestData/GTMUnitTestingTestApp.gtmUTState @@ -256,13 +256,9 @@ <key>MenuItemTitle</key> <string></string> </dict> - <key>MenuTitle</key> - <string>Apple</string> </dict> <key>MenuItemTag</key> <integer>0</integer> - <key>MenuItemTitle</key> - <string></string> </dict> <key>MenuItem 1</key> <dict> @@ -1482,290 +1478,6 @@ <key>CellTag</key> <integer>0</integer> <key>CellTitle</key> - <string>Foo</string> - <key>CellValue</key> - <string>Foo</string> - </dict> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSTextField</string> - <key>ControlValue</key> - <string>Foo</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 5</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlSelectedCell</key> - <dict> - <key>CellState</key> - <integer>1</integer> - <key>CellTag</key> - <integer>0</integer> - <key>CellTitle</key> - <string>CheckMate!</string> - <key>CellValue</key> - <string>1</string> - </dict> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSButton</string> - <key>ControlValue</key> - <string>1</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 6</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlSelectedCell</key> - <dict> - <key>CellState</key> - <integer>1</integer> - <key>CellTag</key> - <integer>0</integer> - <key>CellTitle</key> - <string></string> - <key>CellValue</key> - <string>50</string> - </dict> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSSlider</string> - <key>ControlValue</key> - <string>50</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 7</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlSelectedCell</key> - <dict> - <key>CellState</key> - <integer>0</integer> - <key>CellTag</key> - <integer>0</integer> - <key>CellTitle</key> - <string>Cancel</string> - <key>CellValue</key> - <string>0</string> - </dict> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSButton</string> - <key>ControlValue</key> - <string>0</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 8</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSColorWell</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 9</key> - <dict> - <key>ViewIsHidden</key> - <false/> - </dict> - </dict> - <key>WindowIsMain</key> - <false/> - <key>WindowIsVisible</key> - <false/> - <key>WindowTitle</key> - <string>Window</string> - </dict> - <key>Window 1</key> - <dict> - <key>WindowContent</key> - <dict> - <key>ViewIsHidden</key> - <false/> - <key>ViewSubView 0</key> - <dict> - <key>ViewIsHidden</key> - <false/> - <key>ViewSubView 0</key> - <dict> - <key>ViewIsHidden</key> - <false/> - <key>ViewSubView 0</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSTableView</string> - <key>ViewIsHidden</key> - <false/> - </dict> - </dict> - <key>ViewSubView 1</key> - <dict> - <key>ControlIsEnabled</key> - <false/> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSScroller</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 2</key> - <dict> - <key>ControlIsEnabled</key> - <false/> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSScroller</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 3</key> - <dict> - <key>ViewIsHidden</key> - <false/> - <key>ViewSubView 0</key> - <dict> - <key>ViewIsHidden</key> - <false/> - </dict> - </dict> - <key>ViewSubView 4</key> - <dict> - <key>ViewIsHidden</key> - <false/> - </dict> - </dict> - <key>ViewSubView 1</key> - <dict> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 10</key> - <dict> - <key>ViewIsHidden</key> - <false/> - <key>ViewSubView 0</key> - <dict> - <key>ViewIsHidden</key> - <false/> - <key>ViewSubView 0</key> - <dict> - <key>ViewIsHidden</key> - <false/> - </dict> - </dict> - <key>ViewSubView 1</key> - <dict> - <key>ControlIsEnabled</key> - <false/> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSScroller</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 2</key> - <dict> - <key>ControlIsEnabled</key> - <false/> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSScroller</string> - <key>ViewIsHidden</key> - <false/> - </dict> - </dict> - <key>ViewSubView 11</key> - <dict> - <key>ViewIsHidden</key> - <false/> - <key>ViewSubView 0</key> - <dict> - <key>ViewIsHidden</key> - <false/> - </dict> - </dict> - <key>ViewSubView 2</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlSelectedCell</key> - <dict> - <key>CellState</key> - <integer>0</integer> - <key>CellTag</key> - <integer>0</integer> - <key>CellTitle</key> - <string>HaHa</string> - <key>CellValue</key> - <string>HaHa</string> - </dict> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSTextField</string> - <key>ControlValue</key> - <string>HaHa</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 3</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlSelectedCell</key> - <dict> - <key>CellState</key> - <integer>0</integer> - <key>CellTag</key> - <integer>0</integer> - <key>CellTitle</key> - <string>Still Haven't Found What I'm Searching For</string> - <key>CellValue</key> - <string>Still Haven't Found What I'm Searching For</string> - </dict> - <key>ControlTag</key> - <integer>0</integer> - <key>ControlType</key> - <string>NSSearchField</string> - <key>ControlValue</key> - <string>Still Haven't Found What I'm Searching For</string> - <key>ViewIsHidden</key> - <false/> - </dict> - <key>ViewSubView 4</key> - <dict> - <key>ControlIsEnabled</key> - <true/> - <key>ControlSelectedCell</key> - <dict> - <key>CellState</key> - <integer>1</integer> - <key>CellTag</key> - <integer>0</integer> - <key>CellTitle</key> <string>Once upon a time</string> <key>CellValue</key> <string>Once upon a time</string> diff --git a/UnitTesting/GTMUnitTestingView.tiff b/UnitTesting/TestData/GTMUnitTestingView.tiff Binary files differindex 228df73..228df73 100644 --- a/UnitTesting/GTMUnitTestingView.tiff +++ b/UnitTesting/TestData/GTMUnitTestingView.tiff diff --git a/UnitTesting/GTMUnitTestingWindow.gtmUTState b/UnitTesting/TestData/GTMUnitTestingWindow.gtmUTState index 27dd08e..27dd08e 100644 --- a/UnitTesting/GTMUnitTestingWindow.gtmUTState +++ b/UnitTesting/TestData/GTMUnitTestingWindow.gtmUTState diff --git a/UnitTesting/GTMUnitTestingWindow.tiff b/UnitTesting/TestData/GTMUnitTestingWindow.tiff Binary files differindex 63f5649..63f5649 100644 --- a/UnitTesting/GTMUnitTestingWindow.tiff +++ b/UnitTesting/TestData/GTMUnitTestingWindow.tiff diff --git a/AppKit/GTMNSColor+Theme.h b/XcodeConfig/Target/LoadableBundleGCSupported.xcconfig index f63a143..861845a 100644 --- a/AppKit/GTMNSColor+Theme.h +++ b/XcodeConfig/Target/LoadableBundleGCSupported.xcconfig @@ -1,7 +1,12 @@ // -// GTMNSColor+Theme.m +// LoadableBundleGCSupported.xcconfig // -// Category for working with Themes and NSColor +// Xcode configuration file for a loadable bundle that supports garbage +// collection. Usually a Cocoa plugin or similar. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. // // Copyright 2006-2008 Google Inc. // @@ -18,18 +23,8 @@ // the License. // -#import <Cocoa/Cocoa.h> -#import <Carbon/Carbon.h> - -/// Category for working with Themes and NSColor -@interface NSColor (GTMColorThemeAdditions) - -/// Create up an NSColor based on a Theme Text Color. -/// Colors will be in the CalibratedRGB color space -+ (id)gtm_colorWithThemeTextColor:(ThemeTextColor)textColor; - -/// Create up an NSColor based on a Theme Brush -/// Colors will be in the CalibratedRGB color space -+ (id)gtm_colorWithThemeBrush:(ThemeBrush)brush; +// Include the basic Loadable Bundle config +#include "LoadableBundle.xcconfig" -@end +// Include the GC flag(s) +#include "../subconfig/GCSupported.xcconfig" diff --git a/XcodeConfig/Target/SharedLibraryGCSupported.xcconfig b/XcodeConfig/Target/SharedLibraryGCSupported.xcconfig new file mode 100644 index 0000000..e6b56f7 --- /dev/null +++ b/XcodeConfig/Target/SharedLibraryGCSupported.xcconfig @@ -0,0 +1,30 @@ +// +// SharedLibraryGCSupported.xcconfig +// +// Xcode configuration file for a shared library that support garbage +// collection. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +// Include the basic Shared Library config +#include "SharedLibrary.xcconfig" + +// Include the GC flag(s) +#include "../subconfig/GCSupported.xcconfig" diff --git a/XcodeConfig/Target/StaticLibraryGCSupported.xcconfig b/XcodeConfig/Target/StaticLibraryGCSupported.xcconfig new file mode 100644 index 0000000..6a57a0c --- /dev/null +++ b/XcodeConfig/Target/StaticLibraryGCSupported.xcconfig @@ -0,0 +1,30 @@ +// +// StaticLibraryGCSupported.xcconfig +// +// Xcode configuration file for a static library that supports garbage +// collection. +// +// This is a _Target_ config file, for use in the "Based on" popup of the +// settings dialog for a target. Do not attempt to apply this as the base +// of an Xcode configuration in the project settings dialog. +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +// Include the basic Static Library config +#include "StaticLibrary.xcconfig" + +// Include the GC flag(s) +#include "../subconfig/GCSupported.xcconfig" diff --git a/XcodeConfig/subconfig/64bit.xcconfig b/XcodeConfig/subconfig/64bit.xcconfig new file mode 100644 index 0000000..bb9cae6 --- /dev/null +++ b/XcodeConfig/subconfig/64bit.xcconfig @@ -0,0 +1,26 @@ +// +// 64bit.xcconfig +// +// Xcode configuration file to include for builds wanting 64bit support. +// +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +// Compile all flavors: 32 & 64bit on both PPC and Intel +ARCHS = i386 x86_64 ppc ppc64 + +// Warn on implicit data conversions in 64bit builds +GCC_WARN_64_TO_32_BIT_CONVERSION[arch=ppc64] = YES +GCC_WARN_64_TO_32_BIT_CONVERSION[arch=x86_64] = YES diff --git a/XcodeConfig/subconfig/GCSupported.xcconfig b/XcodeConfig/subconfig/GCSupported.xcconfig new file mode 100644 index 0000000..74bd027 --- /dev/null +++ b/XcodeConfig/subconfig/GCSupported.xcconfig @@ -0,0 +1,23 @@ +// +// GCSupported.xcconfig +// +// Xcode configuration file for making a build Garbage Collection enabled. +// Use the *GCSupported specific configs in the Target folder. +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +// enable garbage collection (but don't require it) +GCC_ENABLE_OBJC_GC = supported diff --git a/XcodeConfig/subconfig/LeopardOrLater.xcconfig b/XcodeConfig/subconfig/LeopardOrLater.xcconfig index b2828d7..6cc1a08 100644 --- a/XcodeConfig/subconfig/LeopardOrLater.xcconfig +++ b/XcodeConfig/subconfig/LeopardOrLater.xcconfig @@ -26,3 +26,7 @@ GCC_VERSION = 4.0 // On Leopard use Obj-C fast dispatch GCC_FAST_OBJC_DISPATCH = YES + +// For Leopard, pull in 64bit support +#include "64bit.xcconfig" + diff --git a/XcodeConfig/subconfig/TigerOrLater.xcconfig b/XcodeConfig/subconfig/TigerOrLater.xcconfig index d8f7a3d..a693a58 100644 --- a/XcodeConfig/subconfig/TigerOrLater.xcconfig +++ b/XcodeConfig/subconfig/TigerOrLater.xcconfig @@ -20,7 +20,7 @@ // // Default SDK and minimum OS version is 10.4 -SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk +SDKROOT = $(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk MACOSX_DEPLOYMENT_TARGET = 10.4 GCC_VERSION = 4.0 |