2
0

Sharing on Facebook, Twitter and Google+

[FIXED]     Font of navbar.
[FIXED]     A few compile fixes.
[IMPROVED]  Made properties nonatomic.
[ADDED]     Support for facebook, twitter and google+ sharing.
This commit is contained in:
Maarten Billemont 2012-08-25 12:38:29 +02:00
parent b9ccee398e
commit 5e7b6ed60e
131 changed files with 24758 additions and 26 deletions

3
.gitmodules vendored
View File

@ -10,3 +10,6 @@
[submodule "External/FontReplacer"] [submodule "External/FontReplacer"]
path = External/FontReplacer path = External/FontReplacer
url = git://github.com/0xced/FontReplacer.git url = git://github.com/0xced/FontReplacer.git
[submodule "External/facebook-ios-sdk"]
path = External/facebook-ios-sdk
url = https://github.com/facebook/facebook-ios-sdk

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit d295f7d413fb2afa9fcdf70079969fd78ac8fb70 Subproject commit 19a7054441049ea1519fe0bb72bc52d4542964cc

1
External/facebook-ios-sdk vendored Submodule

@ -0,0 +1 @@
Subproject commit 682535050207c771706823cbf0fc31a206b12956

View File

@ -0,0 +1,3 @@
2012-06-25 -- v1.0.0
- Google+ sign-in button, share plugin, and Google+ history integration library
with sample app.

View File

@ -0,0 +1,25 @@
/* Copyright (c) 2012 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>
NSData *GTLDecodeBase64(NSString *base64Str);
NSString *GTLEncodeBase64(NSData *data);
// "Web-safe" encoding substitutes - and _ for + and / in the encoding table,
// per http://www.ietf.org/rfc/rfc4648.txt section 5.
NSData *GTLDecodeWebSafeBase64(NSString *base64Str);
NSString *GTLEncodeWebSafeBase64(NSData *data);

View File

@ -0,0 +1,139 @@
/* Copyright (c) 2012 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 "GTLBase64.h"
// Based on Cyrus Najmabadi's elegent little encoder and decoder from
// http://www.cocoadev.com/index.pl?BaseSixtyFour
static char gStandardEncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static char gWebSafeEncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
#pragma mark Encode
static NSString *EncodeBase64StringCommon(NSData *data, const char *table) {
if (data == nil) return nil;
const uint8_t* input = [data bytes];
NSUInteger length = [data length];
NSUInteger bufferSize = ((length + 2) / 3) * 4;
NSMutableData* buffer = [NSMutableData dataWithLength:bufferSize];
uint8_t *output = [buffer mutableBytes];
for (NSUInteger i = 0; i < length; i += 3) {
NSUInteger value = 0;
for (NSUInteger j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger idx = (i / 3) * 4;
output[idx + 0] = table[(value >> 18) & 0x3F];
output[idx + 1] = table[(value >> 12) & 0x3F];
output[idx + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[idx + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
NSString *result = [[[NSString alloc] initWithData:buffer
encoding:NSASCIIStringEncoding] autorelease];
return result;
}
NSString *GTLEncodeBase64(NSData *data) {
return EncodeBase64StringCommon(data, gStandardEncodingTable);
}
NSString *GTLEncodeWebSafeBase64(NSData *data) {
return EncodeBase64StringCommon(data, gWebSafeEncodingTable);
}
#pragma mark Decode
static void CreateDecodingTable(const char *encodingTable,
size_t encodingTableSize, char *decodingTable) {
memset(decodingTable, 0, 128);
for (unsigned int i = 0; i < encodingTableSize; i++) {
decodingTable[(unsigned int) encodingTable[i]] = i;
}
}
static NSData *DecodeBase64StringCommon(NSString *base64Str,
char *decodingTable) {
// The input string should be plain ASCII
const char *cString = [base64Str cStringUsingEncoding:NSASCIIStringEncoding];
if (cString == nil) return nil;
NSUInteger inputLength = strlen(cString);
if (inputLength % 4 != 0) return nil;
if (inputLength == 0) return [NSData data];
while (inputLength > 0 && cString[inputLength - 1] == '=') {
inputLength--;
}
NSInteger outputLength = inputLength * 3 / 4;
NSMutableData* data = [NSMutableData dataWithLength:outputLength];
uint8_t *output = [data mutableBytes];
NSInteger inputPoint = 0;
NSInteger outputPoint = 0;
char *table = decodingTable;
while (inputPoint < inputLength) {
int i0 = cString[inputPoint++];
int i1 = cString[inputPoint++];
int i2 = inputPoint < inputLength ? cString[inputPoint++] : 'A'; // 'A' will decode to \0
int i3 = inputPoint < inputLength ? cString[inputPoint++] : 'A';
output[outputPoint++] = (table[i0] << 2) | (table[i1] >> 4);
if (outputPoint < outputLength) {
output[outputPoint++] = ((table[i1] & 0xF) << 4) | (table[i2] >> 2);
}
if (outputPoint < outputLength) {
output[outputPoint++] = ((table[i2] & 0x3) << 6) | table[i3];
}
}
return data;
}
NSData *GTLDecodeBase64(NSString *base64Str) {
static char decodingTable[128];
static BOOL hasInited = NO;
if (!hasInited) {
CreateDecodingTable(gStandardEncodingTable, sizeof(gStandardEncodingTable),
decodingTable);
hasInited = YES;
}
return DecodeBase64StringCommon(base64Str, decodingTable);
}
NSData *GTLDecodeWebSafeBase64(NSString *base64Str) {
static char decodingTable[128];
static BOOL hasInited = NO;
if (!hasInited) {
CreateDecodingTable(gWebSafeEncodingTable, sizeof(gWebSafeEncodingTable),
decodingTable);
hasInited = YES;
}
return DecodeBase64StringCommon(base64Str, decodingTable);
}

View File

@ -0,0 +1,49 @@
/* Copyright (c) 2011 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.
*/
//
// GTLBatchQuery.h
//
#import "GTLQuery.h"
@interface GTLBatchQuery : NSObject <GTLQueryProtocol> {
@private
NSMutableArray *queries_;
NSMutableDictionary *requestIDMap_;
BOOL skipAuthorization_;
NSDictionary *additionalHTTPHeaders_;
}
// Queries included in this batch. Each query should have a unique requestID.
@property (retain) NSArray *queries;
// Clients may set this to YES to disallow authorization. Defaults to NO.
@property (assign) BOOL shouldSkipAuthorization;
// Any additional HTTP headers for this batch.
//
// These headers override the same keys from the service object's
// additionalHTTPHeaders.
@property (copy) NSDictionary *additionalHTTPHeaders;
+ (id)batchQuery;
+ (id)batchQueryWithQueries:(NSArray *)array;
- (void)addQuery:(GTLQuery *)query;
- (GTLQuery *)queryForRequestID:(NSString *)requestID;
@end

View File

@ -0,0 +1,133 @@
/* Copyright (c) 2011 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.
*/
//
// GTLBatchQuery.m
//
#import "GTLBatchQuery.h"
@implementation GTLBatchQuery
@synthesize shouldSkipAuthorization = skipAuthorization_,
additionalHTTPHeaders = additionalHTTPHeaders_;
+ (id)batchQuery {
GTLBatchQuery *obj = [[[self alloc] init] autorelease];
return obj;
}
+ (id)batchQueryWithQueries:(NSArray *)queries {
GTLBatchQuery *obj = [self batchQuery];
obj.queries = queries;
return obj;
}
- (id)copyWithZone:(NSZone *)zone {
// Deep copy the list of queries
NSArray *copiesOfQueries = [[[NSArray alloc] initWithArray:self.queries
copyItems:YES] autorelease];
GTLBatchQuery *newBatch = [[[self class] allocWithZone:zone] init];
newBatch.queries = copiesOfQueries;
newBatch.shouldSkipAuthorization = self.shouldSkipAuthorization;
newBatch.additionalHTTPHeaders = self.additionalHTTPHeaders;
return newBatch;
}
- (void)dealloc {
[queries_ release];
[additionalHTTPHeaders_ release];
[requestIDMap_ release];
[super dealloc];
}
- (NSString *)description {
NSArray *queries = self.queries;
NSArray *methodNames = [queries valueForKey:@"methodName"];
NSArray *dedupedNames = [[NSSet setWithArray:methodNames] allObjects];
NSString *namesStr = [dedupedNames componentsJoinedByString:@","];
return [NSString stringWithFormat:@"%@ %p (queries:%lu methods:%@)",
[self class], self, (unsigned long) [queries count], namesStr];
}
#pragma mark -
- (BOOL)isBatchQuery {
return YES;
}
- (GTLUploadParameters *)uploadParameters {
// File upload is not supported for batches
return nil;
}
- (void)executionDidStop {
NSArray *queries = self.queries;
[queries makeObjectsPerformSelector:@selector(executionDidStop)];
}
- (GTLQuery *)queryForRequestID:(NSString *)requestID {
GTLQuery *result = [requestIDMap_ objectForKey:requestID];
if (result) return result;
// We've not before tried to look up a query, or the map is stale
[requestIDMap_ release];
requestIDMap_ = [[NSMutableDictionary alloc] init];
for (GTLQuery *query in queries_) {
[requestIDMap_ setObject:query forKey:query.requestID];
}
result = [requestIDMap_ objectForKey:requestID];
return result;
}
#pragma mark -
- (void)setQueries:(NSArray *)array {
#if DEBUG
for (id obj in array) {
GTLQuery *query = obj;
GTL_DEBUG_ASSERT([query isKindOfClass:[GTLQuery class]],
@"unexpected query class: %@", [obj class]);
GTL_DEBUG_ASSERT(query.uploadParameters == nil,
@"batch may not contain upload: %@", query);
}
#endif
[queries_ autorelease];
queries_ = [array mutableCopy];
}
- (NSArray *)queries {
return queries_;
}
- (void)addQuery:(GTLQuery *)query {
GTL_DEBUG_ASSERT([query isKindOfClass:[GTLQuery class]],
@"unexpected query class: %@", [query class]);
GTL_DEBUG_ASSERT(query.uploadParameters == nil,
@"batch may not contain upload: %@", query);
if (queries_ == nil) {
queries_ = [[NSMutableArray alloc] init];
}
[queries_ addObject:query];
}
@end

View File

@ -0,0 +1,58 @@
/* Copyright (c) 2011 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.
*/
//
// GTLBatchResult.h
//
#import "GTLObject.h"
@interface GTLBatchResult : GTLObject <GTLBatchItemCreationProtocol> {
@private
NSMutableDictionary *successes_;
NSMutableDictionary *failures_;
}
// Dictionaries of results for all queries in the batch
//
// Dictionary keys are requestID strings; objects are results or
// GTLErrorObjects.
//
// For successes with no returned object (such as from delete operations),
// the object for the dictionary entry is NSNull.
//
//
// The original query for each result is available from the service ticket,
// for example
//
// NSDictionary *successes = batchResults.successes;
// for (NSString *requestID in successes) {
// GTLObject *obj = [successes objectForKey:requestID];
// GTLQuery *query = [ticket queryForRequestID:requestID];
// NSLog(@"Query %@ returned object %@", query, obj);
// }
//
// NSDictionary *failures = batchResults.failures;
// for (NSString *requestID in failures) {
// GTLErrorObject *errorObj = [failures objectForKey:requestID];
// GTLQuery *query = [ticket queryForRequestID:requestID];
// NSLog(@"Query %@ failed with error %@", query, errorObj);
// }
//
@property (retain) NSMutableDictionary *successes;
@property (retain) NSMutableDictionary *failures;
@end

View File

@ -0,0 +1,92 @@
/* Copyright (c) 2011 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.
*/
//
// GTLBatchResult.m
//
#import "GTLBatchResult.h"
#import "GTLErrorObject.h"
@implementation GTLBatchResult
@synthesize successes = successes_,
failures = failures_;
- (id)copyWithZone:(NSZone *)zone {
GTLBatchResult* newObject = [super copyWithZone:zone];
newObject.successes = [[self.successes mutableCopyWithZone:zone] autorelease];
newObject.failures = [[self.failures mutableCopyWithZone:zone] autorelease];
return newObject;
}
- (void)dealloc {
[successes_ release];
[failures_ release];
[super dealloc];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %p (successes:%lu failures:%lu)",
[self class], self,
(unsigned long) [self.successes count],
(unsigned long) [self.failures count]];
}
#pragma mark -
- (void)createItemsWithClassMap:(NSDictionary *)batchClassMap {
// This is called by GTLObject objectForJSON:defaultClass:
// JSON is defined to be a dictionary, but for batch results, it really
// is any array.
id json = self.JSON;
GTL_DEBUG_ASSERT([json isKindOfClass:[NSArray class]],
@"didn't get an array for the batch results");
NSArray *jsonArray = json;
NSMutableDictionary *successes = [NSMutableDictionary dictionary];
NSMutableDictionary *failures = [NSMutableDictionary dictionary];
for (NSMutableDictionary *rpcResponse in jsonArray) {
NSString *responseID = [rpcResponse objectForKey:@"id"];
NSMutableDictionary *errorJSON = [rpcResponse objectForKey:@"error"];
if (errorJSON) {
GTLErrorObject *errorObject = [GTLErrorObject objectWithJSON:errorJSON];
[failures setValue:errorObject forKey:responseID];
} else {
NSMutableDictionary *resultJSON = [rpcResponse objectForKey:@"result"];
NSDictionary *surrogates = self.surrogates;
Class defaultClass = [batchClassMap objectForKey:responseID];
id resultObject = [[self class] objectForJSON:resultJSON
defaultClass:defaultClass
surrogates:surrogates
batchClassMap:nil];
if (resultObject == nil) {
// methods like delete return no object
resultObject = [NSNull null];
}
[successes setValue:resultObject forKey:responseID];
}
}
self.successes = successes;
self.failures = failures;
}
@end

View File

@ -0,0 +1,56 @@
/* Copyright (c) 2011 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.
*/
//
// GTLDateTime.h
//
#import <Foundation/Foundation.h>
#import "GTLDefines.h"
@interface GTLDateTime : NSObject <NSCopying> {
NSDateComponents *dateComponents_;
NSInteger milliseconds_; // This is only for the fraction of a second 0-999
NSInteger offsetSeconds_; // may be NSUndefinedDateComponent
BOOL isUniversalTime_; // preserves "Z"
NSTimeZone *timeZone_; // specific time zone by name, if known
}
// Note: nil can be passed for time zone arguments when the time zone is not
// known.
+ (GTLDateTime *)dateTimeWithRFC3339String:(NSString *)str;
+ (GTLDateTime *)dateTimeWithDate:(NSDate *)date timeZone:(NSTimeZone *)tz;
- (void)setFromDate:(NSDate *)date timeZone:(NSTimeZone *)tz;
- (void)setFromRFC3339String:(NSString *)str;
@property (nonatomic, readonly) NSDate *date;
@property (nonatomic, readonly) NSCalendar *calendar;
@property (nonatomic, readonly) NSString *RFC3339String;
@property (nonatomic, readonly) NSString *stringValue; // same as RFC3339String
@property (nonatomic, retain) NSTimeZone *timeZone;
@property (nonatomic, copy) NSDateComponents *dateComponents;
@property (nonatomic, assign) NSInteger milliseconds; // This is only for the fraction of a second 0-999
@property (nonatomic, assign) BOOL hasTime;
@property (nonatomic, assign) NSInteger offsetSeconds;
@property (nonatomic, assign, getter=isUniversalTime) BOOL universalTime;
- (void)setTimeZone:(NSTimeZone *)timeZone withOffsetSeconds:(NSInteger)val;
@end

View File

@ -0,0 +1,422 @@
/* Copyright (c) 2011 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.
*/
//
// GTLDateTime.m
//
#import "GTLDateTime.h"
@implementation GTLDateTime
// A note about milliseconds_:
// RFC 3339 has support for fractions of a second. NSDateComponents is all
// NSInteger based, so it can't handle a fraction of a second. NSDate is
// built on NSTimeInterval so it has sub-millisecond precision. GTL takes
// the compromise of supporting the RFC's optional fractional second support
// by maintaining a number of milliseconds past what fits in the
// NSDateComponents. The parsing and string conversions will include
// 3 decimal digits (hence milliseconds). When going to a string, the decimal
// digits are only included if the milliseconds are non zero.
@dynamic date;
@dynamic calendar;
@dynamic RFC3339String;
@dynamic stringValue;
@dynamic timeZone;
@dynamic hasTime;
@synthesize dateComponents = dateComponents_,
milliseconds = milliseconds_,
offsetSeconds = offsetSeconds_,
universalTime = isUniversalTime_;
+ (GTLDateTime *)dateTimeWithRFC3339String:(NSString *)str {
if (str == nil) return nil;
GTLDateTime *result = [[[self alloc] init] autorelease];
[result setFromRFC3339String:str];
return result;
}
+ (GTLDateTime *)dateTimeWithDate:(NSDate *)date timeZone:(NSTimeZone *)tz {
if (date == nil) return nil;
GTLDateTime *result = [[[self alloc] init] autorelease];
[result setFromDate:date timeZone:tz];
return result;
}
- (void)dealloc {
[dateComponents_ release];
[timeZone_ release];
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone {
GTLDateTime *newObj = [[GTLDateTime alloc] init];
newObj.universalTime = self.isUniversalTime;
[newObj setTimeZone:self.timeZone withOffsetSeconds:self.offsetSeconds];
newObj.dateComponents = self.dateComponents;
newObj.milliseconds = self.milliseconds;
return newObj;
}
// until NSDateComponent implements isEqual, we'll use this
- (BOOL)doesDateComponents:(NSDateComponents *)dc1
equalDateComponents:(NSDateComponents *)dc2 {
return [dc1 era] == [dc2 era]
&& [dc1 year] == [dc2 year]
&& [dc1 month] == [dc2 month]
&& [dc1 day] == [dc2 day]
&& [dc1 hour] == [dc2 hour]
&& [dc1 minute] == [dc2 minute]
&& [dc1 second] == [dc2 second]
&& [dc1 week] == [dc2 week]
&& [dc1 weekday] == [dc2 weekday]
&& [dc1 weekdayOrdinal] == [dc2 weekdayOrdinal];
}
- (BOOL)isEqual:(GTLDateTime *)other {
if (self == other) return YES;
if (![other isKindOfClass:[GTLDateTime class]]) return NO;
BOOL areDateComponentsEqual = [self doesDateComponents:self.dateComponents
equalDateComponents:other.dateComponents];
NSTimeZone *tz1 = self.timeZone;
NSTimeZone *tz2 = other.timeZone;
BOOL areTimeZonesEqual = (tz1 == tz2 || (tz2 && [tz1 isEqual:tz2]));
return self.offsetSeconds == other.offsetSeconds
&& self.isUniversalTime == other.isUniversalTime
&& self.milliseconds == other.milliseconds
&& areDateComponentsEqual
&& areTimeZonesEqual;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %p: {%@}",
[self class], self, self.RFC3339String];
}
- (NSTimeZone *)timeZone {
if (timeZone_) {
return timeZone_;
}
if (self.isUniversalTime) {
NSTimeZone *ztz = [NSTimeZone timeZoneWithName:@"Universal"];
return ztz;
}
NSInteger offsetSeconds = self.offsetSeconds;
if (offsetSeconds != NSUndefinedDateComponent) {
NSTimeZone *tz = [NSTimeZone timeZoneForSecondsFromGMT:offsetSeconds];
return tz;
}
return nil;
}
- (void)setTimeZone:(NSTimeZone *)timeZone {
[timeZone_ release];
timeZone_ = [timeZone retain];
if (timeZone) {
NSInteger offsetSeconds = [timeZone secondsFromGMTForDate:self.date];
self.offsetSeconds = offsetSeconds;
} else {
self.offsetSeconds = NSUndefinedDateComponent;
}
}
- (void)setTimeZone:(NSTimeZone *)timeZone withOffsetSeconds:(NSInteger)val {
[timeZone_ release];
timeZone_ = [timeZone retain];
offsetSeconds_ = val;
}
- (NSCalendar *)calendar {
NSCalendar *cal = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSTimeZone *tz = self.timeZone;
if (tz) {
[cal setTimeZone:tz];
}
return cal;
}
- (NSDate *)date {
NSCalendar *cal = self.calendar;
NSDateComponents *dateComponents = self.dateComponents;
NSTimeInterval extraMillisecondsAsSeconds = 0.0;
if (!self.hasTime) {
// we're not keeping track of a time, but NSDate always is based on
// an absolute time. We want to avoid returning an NSDate where the
// calendar date appears different from what was used to create our
// date-time object.
//
// We'll make a copy of the date components, setting the time on our
// copy to noon GMT, since that ensures the date renders correctly for
// any time zone
NSDateComponents *noonDateComponents = [[dateComponents copy] autorelease];
[noonDateComponents setHour:12];
[noonDateComponents setMinute:0];
[noonDateComponents setSecond:0];
dateComponents = noonDateComponents;
NSTimeZone *gmt = [NSTimeZone timeZoneWithName:@"Universal"];
[cal setTimeZone:gmt];
} else {
// Add in the fractional seconds that don't fit into NSDateComponents.
extraMillisecondsAsSeconds = ((NSTimeInterval)self.milliseconds) / 1000.0;
}
NSDate *date = [cal dateFromComponents:dateComponents];
// Add in any milliseconds that didn't fit into the dateComponents.
if (extraMillisecondsAsSeconds > 0.0) {
#if GTL_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5)
date = [date dateByAddingTimeInterval:extraMillisecondsAsSeconds];
#else
date = [date addTimeInterval:extraMillisecondsAsSeconds];
#endif
}
return date;
}
- (NSString *)stringValue {
return self.RFC3339String;
}
- (NSString *)RFC3339String {
NSDateComponents *dateComponents = self.dateComponents;
NSInteger offset = self.offsetSeconds;
NSString *timeString = @""; // timeString like "T15:10:46-08:00"
if (self.hasTime) {
NSString *timeOffsetString; // timeOffsetString like "-08:00"
if (self.isUniversalTime) {
timeOffsetString = @"Z";
} else if (offset == NSUndefinedDateComponent) {
// unknown offset is rendered as -00:00 per
// http://www.ietf.org/rfc/rfc3339.txt section 4.3
timeOffsetString = @"-00:00";
} else {
NSString *sign = @"+";
if (offset < 0) {
sign = @"-";
offset = -offset;
}
timeOffsetString = [NSString stringWithFormat:@"%@%02ld:%02ld",
sign, (long)(offset/(60*60)) % 24, (long)(offset / 60) % 60];
}
NSString *fractionalSecondsString = @"";
if (self.milliseconds > 0.0) {
fractionalSecondsString = [NSString stringWithFormat:@".%03ld", (long)self.milliseconds];
}
timeString = [NSString stringWithFormat:@"T%02ld:%02ld:%02ld%@%@",
(long)[dateComponents hour], (long)[dateComponents minute],
(long)[dateComponents second], fractionalSecondsString, timeOffsetString];
}
// full dateString like "2006-11-17T15:10:46-08:00"
NSString *dateString = [NSString stringWithFormat:@"%04ld-%02ld-%02ld%@",
(long)[dateComponents year], (long)[dateComponents month],
(long)[dateComponents day], timeString];
return dateString;
}
- (void)setFromDate:(NSDate *)date timeZone:(NSTimeZone *)tz {
NSCalendar *cal = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
if (tz) {
[cal setTimeZone:tz];
}
NSUInteger const kComponentBits = (NSYearCalendarUnit | NSMonthCalendarUnit
| NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit
| NSSecondCalendarUnit);
NSDateComponents *components = [cal components:kComponentBits fromDate:date];
self.dateComponents = components;
// Extract the fractional seconds.
NSTimeInterval asTimeInterval = [date timeIntervalSince1970];
NSTimeInterval worker = asTimeInterval - trunc(asTimeInterval);
self.milliseconds = (NSInteger)round(worker * 1000.0);
self.universalTime = NO;
NSInteger offset = NSUndefinedDateComponent;
if (tz) {
offset = [tz secondsFromGMTForDate:date];
if (offset == 0 && [tz isEqualToTimeZone:[NSTimeZone timeZoneWithName:@"Universal"]]) {
self.universalTime = YES;
}
}
self.offsetSeconds = offset;
// though offset seconds are authoritative, we'll retain the time zone
// since we can't regenerate it reliably from just the offset
timeZone_ = [tz retain];
}
- (void)setFromRFC3339String:(NSString *)str {
NSInteger year = NSUndefinedDateComponent;
NSInteger month = NSUndefinedDateComponent;
NSInteger day = NSUndefinedDateComponent;
NSInteger hour = NSUndefinedDateComponent;
NSInteger minute = NSUndefinedDateComponent;
NSInteger sec = NSUndefinedDateComponent;
NSInteger milliseconds = 0;
double secDouble = -1.0;
NSString* sign = nil;
NSInteger offsetHour = 0;
NSInteger offsetMinute = 0;
if ([str length] > 0) {
NSScanner* scanner = [NSScanner scannerWithString:str];
// There should be no whitespace, so no skip characters.
[scanner setCharactersToBeSkipped:nil];
NSCharacterSet* dashSet = [NSCharacterSet characterSetWithCharactersInString:@"-"];
NSCharacterSet* tSet = [NSCharacterSet characterSetWithCharactersInString:@"Tt "];
NSCharacterSet* colonSet = [NSCharacterSet characterSetWithCharactersInString:@":"];
NSCharacterSet* plusMinusZSet = [NSCharacterSet characterSetWithCharactersInString:@"+-zZ"];
// for example, scan 2006-11-17T15:10:46-08:00
// or 2006-11-17T15:10:46Z
if (// yyyy-mm-dd
[scanner scanInteger:&year] &&
[scanner scanCharactersFromSet:dashSet intoString:NULL] &&
[scanner scanInteger:&month] &&
[scanner scanCharactersFromSet:dashSet intoString:NULL] &&
[scanner scanInteger:&day] &&
// Thh:mm:ss
[scanner scanCharactersFromSet:tSet intoString:NULL] &&
[scanner scanInteger:&hour] &&
[scanner scanCharactersFromSet:colonSet intoString:NULL] &&
[scanner scanInteger:&minute] &&
[scanner scanCharactersFromSet:colonSet intoString:NULL] &&
[scanner scanDouble:&secDouble]) {
// At this point we got secDouble, pull it apart.
sec = (NSInteger)secDouble;
double worker = secDouble - ((double)sec);
milliseconds = (NSInteger)round(worker * 1000.0);
// Finish parsing, now the offset info.
if (// Z or +hh:mm
[scanner scanCharactersFromSet:plusMinusZSet intoString:&sign] &&
[scanner scanInteger:&offsetHour] &&
[scanner scanCharactersFromSet:colonSet intoString:NULL] &&
[scanner scanInteger:&offsetMinute]) {
}
}
}
NSDateComponents *dateComponents = [[[NSDateComponents alloc] init] autorelease];
[dateComponents setYear:year];
[dateComponents setMonth:month];
[dateComponents setDay:day];
[dateComponents setHour:hour];
[dateComponents setMinute:minute];
[dateComponents setSecond:sec];
self.dateComponents = dateComponents;
self.milliseconds = milliseconds;
// determine the offset, like from Z, or -08:00:00.0
self.timeZone = nil;
NSInteger totalOffset = NSUndefinedDateComponent;
self.universalTime = NO;
if ([sign caseInsensitiveCompare:@"Z"] == NSOrderedSame) {
self.universalTime = YES;
totalOffset = 0;
} else if (sign != nil) {
totalOffset = (60 * offsetMinute) + (60 * 60 * offsetHour);
if ([sign isEqual:@"-"]) {
if (totalOffset == 0) {
// special case: offset of -0.00 means undefined offset
totalOffset = NSUndefinedDateComponent;
} else {
totalOffset *= -1;
}
}
}
self.offsetSeconds = totalOffset;
}
- (BOOL)hasTime {
NSDateComponents *dateComponents = self.dateComponents;
BOOL hasTime = ([dateComponents hour] != NSUndefinedDateComponent
&& [dateComponents minute] != NSUndefinedDateComponent);
return hasTime;
}
- (void)setHasTime:(BOOL)shouldHaveTime {
// we'll set time values to zero or NSUndefinedDateComponent as appropriate
BOOL hadTime = self.hasTime;
if (shouldHaveTime && !hadTime) {
[dateComponents_ setHour:0];
[dateComponents_ setMinute:0];
[dateComponents_ setSecond:0];
milliseconds_ = 0;
offsetSeconds_ = NSUndefinedDateComponent;
isUniversalTime_ = NO;
} else if (hadTime && !shouldHaveTime) {
[dateComponents_ setHour:NSUndefinedDateComponent];
[dateComponents_ setMinute:NSUndefinedDateComponent];
[dateComponents_ setSecond:NSUndefinedDateComponent];
milliseconds_ = 0;
offsetSeconds_ = NSUndefinedDateComponent;
isUniversalTime_ = NO;
self.timeZone = nil;
}
}
@end

View File

@ -0,0 +1,128 @@
/* Copyright (c) 2011 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.
*/
//
// GTLDefines.h
//
// Ensure Apple's conditionals we depend on are defined.
#import <TargetConditionals.h>
#import <AvailabilityMacros.h>
//
// The developer may choose to define these in the project:
//
// #define GTL_TARGET_NAMESPACE Xxx // preface all GTL class names with Xxx (recommended for building plug-ins)
// #define GTL_FOUNDATION_ONLY 1 // builds without AppKit or Carbon (default for iPhone builds)
// #define STRIP_GTM_FETCH_LOGGING 1 // omit http logging code (default for iPhone release builds)
//
// Mac developers may find GTL_SIMPLE_DESCRIPTIONS and STRIP_GTM_FETCH_LOGGING useful for
// reducing code size.
//
// Define later OS versions when building on earlier versions
#ifdef MAC_OS_X_VERSION_10_0
#ifndef MAC_OS_X_VERSION_10_6
#define MAC_OS_X_VERSION_10_6 1060
#endif
#endif
#ifdef GTL_TARGET_NAMESPACE
// prefix all GTL class names with GTL_TARGET_NAMESPACE for this target
#import "GTLTargetNamespace.h"
#endif
// Provide a common definition for externing constants/functions
#if defined(__cplusplus)
#define GTL_EXTERN extern "C"
#else
#define GTL_EXTERN extern
#endif
#if TARGET_OS_IPHONE // iPhone SDK
#define GTL_IPHONE 1
#endif
#if GTL_IPHONE
#define GTL_FOUNDATION_ONLY 1
#endif
//
// GTL_ASSERT is like NSAssert, but takes a variable number of arguments:
//
// GTL_ASSERT(condition, @"Problem in argument %@", argStr);
//
// GTL_DEBUG_ASSERT is similar, but compiles in only for debug builds
//
#ifndef GTL_ASSERT
// we directly invoke the NSAssert handler so we can pass on the varargs
#if !defined(NS_BLOCK_ASSERTIONS)
#define GTL_ASSERT(condition, ...) \
do { \
if (!(condition)) { \
[[NSAssertionHandler currentHandler] \
handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ \
description:__VA_ARGS__]; \
} \
} while(0)
#else
#define GTL_ASSERT(condition, ...) do { } while (0)
#endif // !defined(NS_BLOCK_ASSERTIONS)
#endif // GTL_ASSERT
#ifndef GTL_DEBUG_ASSERT
#if DEBUG
#define GTL_DEBUG_ASSERT(condition, ...) GTL_ASSERT(condition, __VA_ARGS__)
#else
#define GTL_DEBUG_ASSERT(condition, ...) do { } while (0)
#endif
#endif
#ifndef GTL_DEBUG_LOG
#if DEBUG
#define GTL_DEBUG_LOG(...) NSLog(__VA_ARGS__)
#else
#define GTL_DEBUG_LOG(...) do { } while (0)
#endif
#endif
#ifndef STRIP_GTM_FETCH_LOGGING
#if GTL_IPHONE && !DEBUG
#define STRIP_GTM_FETCH_LOGGING 1
#else
#define STRIP_GTM_FETCH_LOGGING 0
#endif
#endif
// Some support for advanced clang static analysis functionality
// See http://clang-analyzer.llvm.org/annotations.html
#ifndef __has_feature // Optional.
#define __has_feature(x) 0 // Compatibility with non-clang compilers.
#endif
#ifndef NS_RETURNS_NOT_RETAINED
#if __has_feature(attribute_ns_returns_not_retained)
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
#else
#define NS_RETURNS_NOT_RETAINED
#endif
#endif

View File

@ -0,0 +1,45 @@
/* Copyright (c) 2011 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.
*/
//
// GTLErrorObject.h
//
#import "GTLObject.h"
@class GTLErrorObjectData;
@interface GTLErrorObject : GTLObject
@property (retain) NSNumber *code;
@property (retain) NSString *message;
@property (retain) NSArray *data; // of GTLErrorObjectData
// Convenience accessor for creating an NSError from a GTLErrorObject.
@property (readonly) NSError *foundationError;
// Convenience accessor for extracting the GTLErrorObject that was used to
// create an NSError.
//
// Returns nil if the error was not originally from a GTLErrorObject.
+ (GTLErrorObject *)underlyingObjectForError:(NSError *)foundationError;
@end
@interface GTLErrorObjectData : GTLObject
@property (retain) NSString *domain;
@property (retain) NSString *reason;
@property (retain) NSString *message;
@property (retain) NSString *location;
@end

View File

@ -0,0 +1,78 @@
/* Copyright (c) 2011 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.
*/
//
// GTLErrorObject.m
//
#import "GTLErrorObject.h"
#import "GTLService.h"
@implementation GTLErrorObject
@dynamic code;
@dynamic message;
@dynamic data;
+ (NSDictionary *)arrayPropertyToClassMap {
NSDictionary *map = [NSDictionary dictionaryWithObject:[GTLErrorObjectData class]
forKey:@"data"];
return map;
}
- (NSError *)foundationError {
NSMutableDictionary *userInfo;
// This structured GTLErrorObject will be available in the error's userInfo
// dictionary
userInfo = [NSMutableDictionary dictionaryWithObject:self
forKey:kGTLStructuredErrorKey];
NSString *reasonStr = self.message;
if (reasonStr) {
// We always store an error in the userInfo key "error"
[userInfo setObject:reasonStr
forKey:kGTLServerErrorStringKey];
// Store a user-readable "reason" to show up when an error is logged,
// in parentheses like NSError does it
NSString *parenthesized = [NSString stringWithFormat:@"(%@)", reasonStr];
[userInfo setObject:parenthesized
forKey:NSLocalizedFailureReasonErrorKey];
}
NSInteger code = [self.code integerValue];
NSError *error = [NSError errorWithDomain:kGTLJSONRPCErrorDomain
code:code
userInfo:userInfo];
return error;
}
+ (GTLErrorObject *)underlyingObjectForError:(NSError *)foundationError {
NSDictionary *userInfo = [foundationError userInfo];
GTLErrorObject *errorObj = [userInfo objectForKey:kGTLStructuredErrorKey];
return errorObj;
}
@end
@implementation GTLErrorObjectData
@dynamic domain;
@dynamic reason;
@dynamic message;
@dynamic location;
@end

View File

@ -0,0 +1,35 @@
/* Copyright (c) 2011 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.
*/
#ifndef _GTLFRAMEWORK_H_
#define _GTLFRAMEWORK_H_
#import <Foundation/Foundation.h>
#import "GTLDefines.h"
// Returns the version of the framework. Major and minor should
// match the bundle version in the Info.plist file.
//
// Pass NULL to ignore any of the parameters.
void GTLFrameworkVersion(NSUInteger* major, NSUInteger* minor, NSUInteger* release);
// Returns the version in @"a.b" or @"a.b.c" format
NSString *GTLFrameworkVersionString(void);
#endif

View File

@ -0,0 +1,40 @@
/* Copyright (c) 2011 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 "GTLFramework.h"
void GTLFrameworkVersion(NSUInteger* major, NSUInteger* minor, NSUInteger* release) {
// version 2.0.0
if (major) *major = 2;
if (minor) *minor = 0;
if (release) *release = 0;
}
NSString *GTLFrameworkVersionString(void) {
NSUInteger major, minor, release;
NSString *libVersionString;
GTLFrameworkVersion(&major, &minor, &release);
// most library releases will have a release value of zero
if (release != 0) {
libVersionString = [NSString stringWithFormat:@"%d.%d.%d",
(int)major, (int)minor, (int)release];
} else {
libVersionString = [NSString stringWithFormat:@"%d.%d",
(int)major, (int)minor];
}
return libVersionString;
}

View File

@ -0,0 +1,41 @@
/* Copyright (c) 2011 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.
*/
//
// GTLJSONParser.h
//
// This class is a thin wrapper around the JSON parser. It uses
// NSJSONSerialization when available, and SBJSON otherwise.
#import <Foundation/Foundation.h>
#import "GTLDefines.h"
@interface GTLJSONParser : NSObject
+ (NSString*)stringWithObject:(id)value
humanReadable:(BOOL)humanReadable
error:(NSError**)error;
+ (NSData *)dataWithObject:(id)obj
humanReadable:(BOOL)humanReadable
error:(NSError**)error;
+ (id)objectWithString:(NSString *)jsonStr
error:(NSError **)error;
+ (id)objectWithData:(NSData *)jsonData
error:(NSError **)error;
@end

View File

@ -0,0 +1,150 @@
/* Copyright (c) 2011 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.
*/
//
// GTLJSONParser.m
//
#import "GTLJSONParser.h"
// We can assume NSJSONSerialization is present on Mac OS X 10.7 and iOS 5
#if !defined(GTL_REQUIRES_NSJSONSERIALIZATION)
#if (!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1070)) || \
(TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED >= 50000))
#define GTL_REQUIRES_NSJSONSERIALIZATION 1
#endif
#endif
// If GTMNSJSONSerialization is available, it is used for parsing and
// formatting JSON
#if !GTL_REQUIRES_NSJSONSERIALIZATION
@interface GTMNSJSONSerialization : NSObject
+ (NSData *)dataWithJSONObject:(id)obj options:(NSUInteger)opt error:(NSError **)error;
+ (id)JSONObjectWithData:(NSData *)data options:(NSUInteger)opt error:(NSError **)error;
@end
// As a fallback, SBJSON is used for parsing and formatting JSON
@interface GTLSBJSON
- (void)setHumanReadable:(BOOL)flag;
- (NSString*)stringWithObject:(id)value error:(NSError**)error;
- (id)objectWithString:(NSString*)jsonrep error:(NSError**)error;
@end
#endif // !GTL_REQUIRES_NSJSONSERIALIZATION
@implementation GTLJSONParser
#if DEBUG && !GTL_REQUIRES_NSJSONSERIALIZATION
// When compiling for iOS 4 compatibility, SBJSON must be available
+ (void)load {
Class writer = NSClassFromString(@"SBJsonWriter");
Class parser = NSClassFromString(@"SBJsonParser");
Class oldParser = NSClassFromString(@"SBJSON");
GTL_ASSERT((oldParser != Nil)
|| (writer != Nil && parser != Nil),
@"No parsing class found");
}
#endif // DEBUG && !GTL_REQUIRES_NSJSONSERIALIZATION
+ (NSString*)stringWithObject:(id)obj
humanReadable:(BOOL)humanReadable
error:(NSError**)error {
NSData *data = [self dataWithObject:obj
humanReadable:humanReadable
error:error];
if (data) {
NSString *jsonStr = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
return jsonStr;
}
return nil;
}
+ (NSData *)dataWithObject:(id)obj
humanReadable:(BOOL)humanReadable
error:(NSError**)error {
const NSUInteger kOpts = humanReadable ? (1UL << 0) : 0; // NSJSONWritingPrettyPrinted
#if GTL_REQUIRES_NSJSONSERIALIZATION
NSData *data = [NSJSONSerialization dataWithJSONObject:obj
options:kOpts
error:error];
return data;
#else
Class serializer = NSClassFromString(@"NSJSONSerialization");
if (serializer) {
NSData *data = [serializer dataWithJSONObject:obj
options:kOpts
error:error];
return data;
} else {
Class jsonWriteClass = NSClassFromString(@"SBJsonWriter");
if (!jsonWriteClass) {
jsonWriteClass = NSClassFromString(@"SBJSON");
}
if (error) *error = nil;
GTLSBJSON *writer = [[[jsonWriteClass alloc] init] autorelease];
[writer setHumanReadable:humanReadable];
NSString *jsonStr = [writer stringWithObject:obj
error:error];
NSData *data = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
return data;
}
#endif
}
+ (id)objectWithString:(NSString *)jsonStr
error:(NSError **)error {
NSData *data = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
return [self objectWithData:data
error:error];
}
+ (id)objectWithData:(NSData *)jsonData
error:(NSError **)error {
#if GTL_REQUIRES_NSJSONSERIALIZATION
NSMutableDictionary *obj = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:error];
return obj;
#else
Class serializer = NSClassFromString(@"NSJSONSerialization");
if (serializer) {
const NSUInteger kOpts = (1UL << 0); // NSJSONReadingMutableContainers
NSMutableDictionary *obj = [serializer JSONObjectWithData:jsonData
options:kOpts
error:error];
return obj;
} else {
Class jsonParseClass = NSClassFromString(@"SBJsonParser");
if (!jsonParseClass) {
jsonParseClass = NSClassFromString(@"SBJSON");
}
if (error) *error = nil;
GTLSBJSON *parser = [[[jsonParseClass alloc] init] autorelease];
NSString *jsonrep = [[[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding] autorelease];
id obj = [parser objectWithString:jsonrep
error:error];
return obj;
}
#endif
}
@end

View File

@ -0,0 +1,180 @@
/* Copyright (c) 2011 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.
*/
//
// GTLObject.h
//
#import <Foundation/Foundation.h>
#import "GTLDefines.h"
#import "GTLUtilities.h"
#import "GTLDateTime.h"
#undef _EXTERN
#undef _INITIALIZE_AS
#ifdef GTLOBJECT_DEFINE_GLOBALS
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
#define _EXTERN extern
#define _INITIALIZE_AS(x)
#endif
@protocol GTLCollectionProtocol
@optional
@property (retain) NSArray *items;
@end
@protocol GTLBatchItemCreationProtocol
- (void)createItemsWithClassMap:(NSDictionary *)batchClassMap;
@end
@interface GTLObject : NSObject <NSCopying> {
@private
NSMutableDictionary *json_;
// Used when creating the subobjects from this one.
NSDictionary *surrogates_;
// Any complex object hung off this object goes into the cache so the
// next fetch will get the same object back instead of having to recreate
// it.
NSMutableDictionary *childCache_;
// Anything defined by the client; retained but not used internally; not
// copied by copyWithZone:
NSMutableDictionary *userProperties_;
}
@property (nonatomic, retain) NSMutableDictionary *JSON;
@property (nonatomic, retain) NSDictionary *surrogates;
@property (nonatomic, retain) NSMutableDictionary *userProperties;
///////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
// These methods are intended for users of the library
//
+ (id)object;
+ (id)objectWithJSON:(NSMutableDictionary *)dict;
- (id)copyWithZone:(NSZone *)zone;
- (NSString *)JSONString;
// generic access to json; also creates it if necessary
- (void)setJSONValue:(id)obj forKey:(NSString *)key;
- (id)JSONValueForKey:(NSString *)key;
// Returns the list of keys in this object's JSON that aren't listed as
// properties on the object.
- (NSArray *)additionalJSONKeys;
// Any keys in the JSON that aren't listed as @properties on the object
// are counted as "additional properties". These allow you to get/set them.
- (id)additionalPropertyForName:(NSString *)name;
- (void)setAdditionalProperty:(id)obj forName:(NSString *)name;
- (NSDictionary *)additionalProperties;
// User properties are supported for client convenience, but are not copied by
// copyWithZone. User Properties keys beginning with _ are reserved by the library.
- (void)setProperty:(id)obj forKey:(NSString *)key; // pass nil obj to remove property
- (id)propertyForKey:(NSString *)key;
// userData is stored as a property with key "_userData"
- (void)setUserData:(id)obj;
- (id)userData;
// Makes a partial query-compatible string describing the fields present
// in this object. (Note: only the first element of any array is examined.)
//
// http://code.google.com/apis/tasks/v1/performance.html#partial
//
- (NSString *)fieldsDescription;
// Makes an object containing only the changes needed to do a partial update
// (patch), where the patch would be to change an object from the original
// to the receiver, such as
//
// GTLSomeObject *patchObject = [newVersion patchObjectFromOriginal:oldVersion];
//
// http://code.google.com/apis/tasks/v1/performance.html#patch
//
// NOTE: this method returns nil if there are no changes between the original
// and the receiver.
- (id)patchObjectFromOriginal:(GTLObject *)original;
// Method creating a null value to set object properties for patch queries that
// delete fields. Do not use this except when setting an object property for
// a patch query.
+ (id)nullValue;
///////////////////////////////////////////////////////////////////////////////
//
// Protected methods
//
// These methods are intended for subclasses of GTLObject
//
// class registration ("kind" strings) for subclasses
+ (Class)registeredObjectClassForKind:(NSString *)kind;
+ (void)registerObjectClassForKind:(NSString *)kind;
// creation of objects from a JSON dictionary
+ (GTLObject *)objectForJSON:(NSMutableDictionary *)json
defaultClass:(Class)defaultClass
surrogates:(NSDictionary *)surrogates
batchClassMap:(NSDictionary *)batchClassMap;
// property-to-key mapping (for JSON keys which are not used as method names)
+ (NSDictionary *)propertyToJSONKeyMap;
// property-to-Class mapping for array properties (to say what is in the array)
+ (NSDictionary *)arrayPropertyToClassMap;
// The default class for additional JSON keys
+ (Class)classForAdditionalProperties;
@end
// Collection objects with an "items" property should derive from GTLCollection
// object. This provides support for fast object enumeration and the
// itemAtIndex: convenience method.
//
// Subclasses must implement the items method dynamically.
@interface GTLCollectionObject : GTLObject <GTLCollectionProtocol, NSFastEnumeration>
// itemAtIndex: returns nil when the index exceeds the bounds of the items array
- (id)itemAtIndex:(NSUInteger)idx;
@end
@interface GTLCollectionObject (DynamicMethods)
- (NSArray *)items;
@end
// Base object use for when an service method directly returns an array instead
// of an object. Normally methods should return an object with an 'items'
// property, this exists for the methods not up to spec.
@interface GTLResultArray : GTLCollectionObject
// This method should only be called by subclasses.
- (NSArray *)itemsWithItemClass:(Class)itemClass;
@end

View File

@ -0,0 +1,691 @@
/* Copyright (c) 2011 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.
*/
//
// GTLObject.m
//
#define GTLOBJECT_DEFINE_GLOBALS 1
#include <objc/runtime.h>
#import "GTLObject.h"
#import "GTLRuntimeCommon.h"
#import "GTLJSONParser.h"
static NSString *const kUserDataPropertyKey = @"_userData";
@interface GTLObject () <GTLRuntimeCommon>
+ (NSMutableArray *)allDeclaredProperties;
+ (NSArray *)allKnownKeys;
+ (NSArray *)fieldsElementsForJSON:(NSDictionary *)targetJSON;
+ (NSString *)fieldsDescriptionForJSON:(NSDictionary *)targetJSON;
+ (NSMutableDictionary *)patchDictionaryForJSON:(NSDictionary *)newJSON
fromOriginalJSON:(NSDictionary *)originalJSON;
@end
@implementation GTLObject
@synthesize JSON = json_,
surrogates = surrogates_,
userProperties = userProperties_;
+ (id)object {
return [[[self alloc] init] autorelease];
}
+ (id)objectWithJSON:(NSMutableDictionary *)dict {
GTLObject *obj = [self object];
obj.JSON = dict;
return obj;
}
+ (NSDictionary *)propertyToJSONKeyMap {
return nil;
}
+ (NSDictionary *)arrayPropertyToClassMap {
return nil;
}
+ (Class)classForAdditionalProperties {
return Nil;
}
- (BOOL)isEqual:(GTLObject *)other {
if (self == other) return YES;
if (other == nil) return NO;
// The objects should be the same class, or one should be a subclass of the
// other's class
if (![other isKindOfClass:[self class]]
&& ![self isKindOfClass:[other class]]) return NO;
// What we're not comparing here:
// properties
return GTL_AreEqualOrBothNil(json_, [other JSON]);
}
// By definition, for two objects to potentially be considered equal,
// they must have the same hash value. The hash is mostly ignored,
// but removeObjectsInArray: in Leopard does seem to check the hash,
// and NSObject's default hash method just returns the instance pointer.
// We'll define hash here for all of our GTLObjects.
- (NSUInteger)hash {
return (NSUInteger) (void *) [GTLObject class];
}
- (id)copyWithZone:(NSZone *)zone {
GTLObject* newObject = [[[self class] allocWithZone:zone] init];
CFPropertyListRef ref = CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
json_, kCFPropertyListMutableContainers);
GTL_DEBUG_ASSERT(ref != NULL, @"GTLObject: copy failed (probably a non-plist type in the JSON)");
newObject.JSON = [NSMakeCollectable(ref) autorelease];
newObject.surrogates = self.surrogates;
// What we're not copying:
// userProperties
return newObject;
}
- (NSString *)descriptionWithLocale:(id)locale {
return [self description];
}
- (void)dealloc {
[json_ release];
[surrogates_ release];
[childCache_ release];
[userProperties_ release];
[super dealloc];
}
#pragma mark JSON values
- (void)setJSONValue:(id)obj forKey:(NSString *)key {
NSMutableDictionary *dict = self.JSON;
if (dict == nil && obj != nil) {
dict = [NSMutableDictionary dictionaryWithCapacity:1];
self.JSON = dict;
}
[dict setValue:obj forKey:key];
}
- (id)JSONValueForKey:(NSString *)key {
id obj = [self.JSON objectForKey:key];
return obj;
}
- (NSString *)JSONString {
NSError *error = nil;
NSString *str = [GTLJSONParser stringWithObject:[self JSON]
humanReadable:YES
error:&error];
if (error) {
return [error description];
}
return str;
}
- (NSArray *)additionalJSONKeys {
NSArray *knownKeys = [[self class] allKnownKeys];
NSMutableArray *result = [NSMutableArray arrayWithArray:[json_ allKeys]];
[result removeObjectsInArray:knownKeys];
// Return nil instead of an empty array.
if ([result count] == 0) {
result = nil;
}
return result;
}
#pragma mark Partial - Fields
- (NSString *)fieldsDescription {
NSString *str = [GTLObject fieldsDescriptionForJSON:self.JSON];
return str;
}
+ (NSString *)fieldsDescriptionForJSON:(NSDictionary *)targetJSON {
// Internal routine: recursively generate a string field description
// by joining elements
NSArray *array = [self fieldsElementsForJSON:targetJSON];
NSString *str = [array componentsJoinedByString:@","];
return str;
}
+ (NSArray *)fieldsElementsForJSON:(NSDictionary *)targetJSON {
// Internal routine: recursively generate an array of field description
// element strings
NSMutableArray *resultFields = [NSMutableArray array];
// Sorting the dictionary keys gives us deterministic results when iterating
NSArray *sortedKeys = [[targetJSON allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
for (NSString *key in sortedKeys) {
// We'll build a comma-separated list of fields
id value = [targetJSON objectForKey:key];
if ([value isKindOfClass:[NSString class]]
|| [value isKindOfClass:[NSNumber class]]) {
// Basic type (string, number), so the key is what we want
[resultFields addObject:key];
} else if ([value isKindOfClass:[NSDictionary class]]) {
// Object (dictionary): "parent/child1,parent/child2,parent/child3"
NSArray *subElements = [self fieldsElementsForJSON:value];
for (NSString *subElem in subElements) {
NSString *prepended = [NSString stringWithFormat:@"%@/%@",
key, subElem];
[resultFields addObject:prepended];
}
} else if ([value isKindOfClass:[NSArray class]]) {
// Array; we'll generate from the first array entry:
// "parent(child1,child2,child3)"
//
// Open question: should this instead create the union of elements for
// all items in the array, rather than just get fields from the first
// array object?
if ([value count] > 0) {
id firstObj = [value objectAtIndex:0];
if ([firstObj isKindOfClass:[NSDictionary class]]) {
// An array of objects
NSString *contentsStr = [self fieldsDescriptionForJSON:firstObj];
NSString *encapsulated = [NSString stringWithFormat:@"%@(%@)",
key, contentsStr];
[resultFields addObject:encapsulated];
} else {
// An array of some basic type, or of arrays
[resultFields addObject:key];
}
}
} else {
GTL_ASSERT(0, @"GTLObject unknown field element for %@ (%@)",
key, NSStringFromClass([value class]));
}
}
return resultFields;
}
#pragma mark Partial - Patch
- (id)patchObjectFromOriginal:(GTLObject *)original {
id resultObj;
NSMutableDictionary *resultJSON = [GTLObject patchDictionaryForJSON:self.JSON
fromOriginalJSON:original.JSON];
if ([resultJSON count] > 0) {
resultObj = [[self class] objectWithJSON:resultJSON];
} else {
// Client apps should not attempt to patch with an object containing
// empty JSON
resultObj = nil;
}
return resultObj;
}
+ (NSMutableDictionary *)patchDictionaryForJSON:(NSDictionary *)newJSON
fromOriginalJSON:(NSDictionary *)originalJSON {
// Internal recursive routine to create an object suitable for
// our patch semantics
NSMutableDictionary *resultJSON = [NSMutableDictionary dictionary];
// Iterate through keys present in the old object
NSArray *originalKeys = [originalJSON allKeys];
for (NSString *key in originalKeys) {
id originalValue = [originalJSON objectForKey:key];
id newValue = [newJSON valueForKey:key];
if (newValue == nil) {
// There is no new value for this key, so set the value to NSNull
[resultJSON setValue:[NSNull null] forKey:key];
} else if (!GTL_AreEqualOrBothNil(originalValue, newValue)) {
// The values for this key differ
if ([originalValue isKindOfClass:[NSDictionary class]]
&& [newValue isKindOfClass:[NSDictionary class]]) {
// Both are objects; recurse
NSMutableDictionary *subDict = [self patchDictionaryForJSON:newValue
fromOriginalJSON:originalValue];
[resultJSON setValue:subDict forKey:key];
} else {
// They are non-object values; the new replaces the old. Per the
// documentation for patch, this replaces entire arrays.
[resultJSON setValue:newValue forKey:key];
}
} else {
// The values are the same; omit this key-value pair
}
}
// Iterate through keys present only in the new object, and add them to the
// result
NSMutableArray *newKeys = [NSMutableArray arrayWithArray:[newJSON allKeys]];
[newKeys removeObjectsInArray:originalKeys];
for (NSString *key in newKeys) {
id value = [newJSON objectForKey:key];
[resultJSON setValue:value forKey:key];
}
return resultJSON;
}
+ (id)nullValue {
return [NSNull null];
}
#pragma mark Additional Properties
- (id)additionalPropertyForName:(NSString *)name {
// Return the cached object, if any, before creating one.
id result = [self cacheChildForKey:name];
if (result != nil) {
return result;
}
Class defaultClass = [[self class] classForAdditionalProperties];
id jsonObj = [self JSONValueForKey:name];
BOOL shouldCache = NO;
if (jsonObj != nil) {
NSDictionary *surrogates = self.surrogates;
result = [GTLRuntimeCommon objectFromJSON:jsonObj
defaultClass:defaultClass
surrogates:surrogates
isCacheable:&shouldCache];
}
[self setCacheChild:(shouldCache ? result : nil)
forKey:name];
return result;
}
- (void)setAdditionalProperty:(id)obj forName:(NSString *)name {
BOOL shouldCache = NO;
Class defaultClass = [[self class] classForAdditionalProperties];
id json = [GTLRuntimeCommon jsonFromAPIObject:obj
expectedClass:defaultClass
isCacheable:&shouldCache];
[self setJSONValue:json forKey:name];
[self setCacheChild:(shouldCache ? obj : nil)
forKey:name];
}
- (NSDictionary *)additionalProperties {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSArray *propertyNames = [self additionalJSONKeys];
for (NSString *name in propertyNames) {
id obj = [self additionalPropertyForName:name];
[result setObject:obj forKey:name];
}
return result;
}
#pragma mark Child Cache methods
// There is no property for childCache_ as there shouldn't be KVC/KVO
// support for it, it's an implementation detail.
- (void)setCacheChild:(id)obj forKey:(NSString *)key {
if (childCache_ == nil && obj != nil) {
childCache_ = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
obj, key, nil];
} else {
[childCache_ setValue:obj forKey:key];
}
}
- (id)cacheChildForKey:(NSString *)key {
id obj = [childCache_ objectForKey:key];
return obj;
}
#pragma mark userData and user properties
- (void)setUserData:(id)userData {
[self setProperty:userData forKey:kUserDataPropertyKey];
}
- (id)userData {
// be sure the returned pointer has the life of the autorelease pool,
// in case self is released immediately
return [[[self propertyForKey:kUserDataPropertyKey] retain] autorelease];
}
- (void)setProperty:(id)obj forKey:(NSString *)key {
if (obj == nil) {
// user passed in nil, so delete the property
[userProperties_ removeObjectForKey:key];
} else {
// be sure the property dictionary exists
if (userProperties_ == nil) {
self.userProperties = [NSMutableDictionary dictionary];
}
[userProperties_ setObject:obj forKey:key];
}
}
- (id)propertyForKey:(NSString *)key {
id obj = [userProperties_ objectForKey:key];
// be sure the returned pointer has the life of the autorelease pool,
// in case self is released immediately
return [[obj retain] autorelease];
}
#pragma mark Support methods
+ (NSMutableArray *)allDeclaredProperties {
NSMutableArray *array = [NSMutableArray array];
// walk from this class up the hierarchy to GTLObject
Class topClass = class_getSuperclass([GTLObject class]);
for (Class currClass = self;
currClass != topClass;
currClass = class_getSuperclass(currClass)) {
// step through this class's properties, and add the property names to the
// array
objc_property_t *properties = class_copyPropertyList(currClass, NULL);
if (properties) {
for (objc_property_t *prop = properties;
*prop != NULL;
++prop) {
const char *propName = property_getName(*prop);
// We only want dynamic properties; their attributes contain ",D".
const char *attr = property_getAttributes(*prop);
const char *dynamicMarker = strstr(attr, ",D");
if (dynamicMarker &&
(dynamicMarker[2] == 0 || dynamicMarker[2] == ',' )) {
[array addObject:[NSString stringWithUTF8String:propName]];
}
}
free(properties);
}
}
return array;
}
+ (NSArray *)allKnownKeys {
NSArray *allProps = [self allDeclaredProperties];
NSMutableArray *knownKeys = [NSMutableArray arrayWithArray:allProps];
NSDictionary *propMap = [GTLObject propertyToJSONKeyMapForClass:[self class]];
NSUInteger idx = 0;
for (NSString *propName in allProps) {
NSString *jsonKey = [propMap objectForKey:propName];
if (jsonKey) {
[knownKeys replaceObjectAtIndex:idx
withObject:jsonKey];
}
++idx;
}
return knownKeys;
}
- (NSString *)description {
// find the list of declared and otherwise known JSON keys for this class
NSArray *knownKeys = [[self class] allKnownKeys];
NSMutableString *descStr = [NSMutableString string];
NSString *spacer = @"";
for (NSString *key in json_) {
NSString *value = nil;
// show question mark for JSON keys not supported by a declared property:
// foo?:"Hi mom."
NSString *qmark = [knownKeys containsObject:key] ? @"" : @"?";
// determine property value to dislay
id rawValue = [json_ valueForKey:key];
if ([rawValue isKindOfClass:[NSDictionary class]]) {
// for dictionaries, show the list of keys:
// {key1,key2,key3}
NSString *subkeyList = [[rawValue allKeys] componentsJoinedByString:@","];
value = [NSString stringWithFormat:@"{%@}", subkeyList];
} else if ([rawValue isKindOfClass:[NSArray class]]) {
// for arrays, show the number of items in the array:
// [3]
value = [NSString stringWithFormat:@"[%lu]", (unsigned long)[rawValue count]];
} else if ([rawValue isKindOfClass:[NSString class]]) {
// for strings, show the string in quotes:
// "Hi mom."
value = [NSString stringWithFormat:@"\"%@\"", rawValue];
} else {
// for numbers, show just the number
value = [rawValue description];
}
[descStr appendFormat:@"%@%@%@:%@", spacer, key, qmark, value];
spacer = @" ";
}
NSString *str = [NSString stringWithFormat:@"%@ %p: {%@}",
[self class], self, descStr];
return str;
}
#pragma mark Class Registration
static NSMutableDictionary *gKindMap = nil;
+ (Class)registeredObjectClassForKind:(NSString *)kind {
Class resultClass = [gKindMap objectForKey:kind];
return resultClass;
}
+ (void)registerObjectClassForKind:(NSString *)kind {
// there's no autorelease pool in place at +load time, so we'll create our own
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (gKindMap == nil) {
gKindMap = [GTLUtilities newStaticDictionary];
}
Class selfClass = [self class];
#if DEBUG
// ensure this is a unique registration
if ([gKindMap objectForKey:kind] != nil ) {
GTL_DEBUG_LOG(@"%@ (%@) registration conflicts with %@",
selfClass, kind, [gKindMap objectForKey:kind]);
}
if ([[gKindMap allKeysForObject:selfClass] count] != 0) {
GTL_DEBUG_LOG(@"%@ (%@) registration conflicts with %@",
selfClass, kind, [gKindMap allKeysForObject:selfClass]);
}
#endif
[gKindMap setValue:selfClass forKey:kind];
// we drain here to keep the clang static analyzer quiet
[pool drain];
}
#pragma mark Object Instantiation
+ (GTLObject *)objectForJSON:(NSMutableDictionary *)json
defaultClass:(Class)defaultClass
surrogates:(NSDictionary *)surrogates
batchClassMap:(NSDictionary *)batchClassMap {
if ([json isEqual:[NSNull null]]) {
// no actual result, such as the response from a delete
return nil;
}
GTL_ASSERT([json count] != 0, @"Creating object from empty json");
if ([json count] == 0) return nil;
// Determine the class to instantiate, based on the original fetch
// request or by looking up "kind" string from the registration at
// +load time of GTLObject subclasses
//
// We're letting the dynamic kind override the default class so
// feeds of heterogenous entries can use the defaultClass as a
// fallback
Class classToCreate = defaultClass;
NSString *kind = nil;
if ([json isKindOfClass:[NSDictionary class]]) {
kind = [json valueForKey:@"kind"];
if ([kind isKindOfClass:[NSString class]] && [kind length] > 0) {
Class dynamicClass = [GTLObject registeredObjectClassForKind:kind];
if (dynamicClass) {
classToCreate = dynamicClass;
}
}
}
// Warn the developer that no specific class of GTLObject
// was requested with the fetch call, and no class is found
// compiled in to match the "kind" attribute of the JSON
// returned by the server
GTL_ASSERT(classToCreate != nil,
@"Could not find registered GTLObject subclass to "
"match JSON with kind \"%@\"", kind);
if (classToCreate == nil) {
classToCreate = [self class];
}
// See if the top-level class for the JSON is listed in the surrogates;
// if so, instantiate the surrogate class instead
Class baseSurrogate = [surrogates objectForKey:classToCreate];
if (baseSurrogate) {
classToCreate = baseSurrogate;
}
// now instantiate the GTLObject
GTLObject *parsedObject = [classToCreate object];
parsedObject.surrogates = surrogates;
parsedObject.JSON = json;
// it's time to instantiate inner items
if ([parsedObject conformsToProtocol:@protocol(GTLBatchItemCreationProtocol)]) {
id <GTLBatchItemCreationProtocol> batch =
(id <GTLBatchItemCreationProtocol>) parsedObject;
[batch createItemsWithClassMap:batchClassMap];
}
return parsedObject;
}
#pragma mark Runtime Utilities
static NSMutableDictionary *gJSONKeyMapCache = nil;
static NSMutableDictionary *gArrayPropertyToClassMapCache = nil;
+ (void)initialize {
// Note that initialize is guaranteed by the runtime to be called in a
// thread-safe manner
if (gJSONKeyMapCache == nil) {
gJSONKeyMapCache = [GTLUtilities newStaticDictionary];
}
if (gArrayPropertyToClassMapCache == nil) {
gArrayPropertyToClassMapCache = [GTLUtilities newStaticDictionary];
}
}
+ (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRuntimeCommon>)aClass {
NSDictionary *resultMap =
[GTLUtilities mergedClassDictionaryForSelector:@selector(propertyToJSONKeyMap)
startClass:aClass
ancestorClass:[GTLObject class]
cache:gJSONKeyMapCache];
return resultMap;
}
+ (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRuntimeCommon>)aClass {
NSDictionary *resultMap =
[GTLUtilities mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap)
startClass:aClass
ancestorClass:[GTLObject class]
cache:gArrayPropertyToClassMapCache];
return resultMap;
}
#pragma mark Runtime Support
+ (Class<GTLRuntimeCommon>)ancestorClass {
return [GTLObject class];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
BOOL resolved = [GTLRuntimeCommon resolveInstanceMethod:sel onClass:self];
if (resolved)
return YES;
return [super resolveInstanceMethod:sel];
}
@end
@implementation GTLCollectionObject
// Subclasses must implement the items method dynamically.
- (id)itemAtIndex:(NSUInteger)idx {
NSArray *items = [self performSelector:@selector(items)];
if (idx < [items count]) {
return [items objectAtIndex:idx];
}
return nil;
}
// NSFastEnumeration protocol
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id *)stackbuf
count:(NSUInteger)len {
NSArray *items = [self performSelector:@selector(items)];
NSUInteger result = [items countByEnumeratingWithState:state
objects:stackbuf
count:len];
return result;
}
@end
@implementation GTLResultArray
- (NSArray *)itemsWithItemClass:(Class)itemClass {
// Return the cached array before creating on demand.
NSString *cacheKey = @"result_array_items";
NSMutableArray *cachedArray = [self cacheChildForKey:cacheKey];
if (cachedArray != nil) {
return cachedArray;
}
NSArray *result = nil;
NSArray *array = (NSArray *)[self JSON];
if (array != nil) {
if ([array isKindOfClass:[NSArray class]]) {
NSDictionary *surrogates = self.surrogates;
result = [GTLRuntimeCommon objectFromJSON:array
defaultClass:itemClass
surrogates:surrogates
isCacheable:NULL];
} else {
#if DEBUG
if (![array isKindOfClass:[NSNull class]]) {
GTL_DEBUG_LOG(@"GTLObject: unexpected JSON: %@ should be an array, actually is a %@:\n%@",
NSStringFromClass([self class]),
NSStringFromClass([array class]),
array);
}
#endif
result = array;
}
}
[self setCacheChild:result forKey:cacheKey];
return result;
}
@end

View File

@ -0,0 +1,36 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlus.h
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
#import "GTLPlusConstants.h"
#import "GTLPlusItemScope.h"
#import "GTLPlusMoment.h"
#import "GTLPlusPerson.h"
#import "GTLQueryPlus.h"
#import "GTLServicePlus.h"

View File

@ -0,0 +1,46 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusConstants.h
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
#import <Foundation/Foundation.h>
#if GTL_BUILT_AS_FRAMEWORK
#import "GTL/GTLDefines.h"
#else
#import "GTLDefines.h"
#endif
// Authorization scope
// Know who you are on Google
GTL_EXTERN NSString * const kGTLAuthScopePlusMe; // "https://www.googleapis.com/auth/plus.me"
// View and manage user activity information in Google+
GTL_EXTERN NSString * const kGTLAuthScopePlusMomentsWrite; // "https://www.googleapis.com/auth/plus.moments.write"
// View your email address
GTL_EXTERN NSString * const kGTLAuthScopePlusUserinfoEmail; // "https://www.googleapis.com/auth/userinfo.email"
// Collection
GTL_EXTERN NSString * const kGTLPlusCollectionVault; // "vault"

View File

@ -0,0 +1,37 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusConstants.m
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
#import "GTLPlusConstants.h"
// Authorization scope
NSString * const kGTLAuthScopePlusMe = @"https://www.googleapis.com/auth/plus.me";
NSString * const kGTLAuthScopePlusMomentsWrite = @"https://www.googleapis.com/auth/plus.moments.write";
NSString * const kGTLAuthScopePlusUserinfoEmail = @"https://www.googleapis.com/auth/userinfo.email";
// Collection
NSString * const kGTLPlusCollectionVault = @"vault";

View File

@ -0,0 +1,225 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusItemScope.h
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLPlusItemScope (0 custom class methods, 55 custom properties)
#if GTL_BUILT_AS_FRAMEWORK
#import "GTL/GTLObject.h"
#else
#import "GTLObject.h"
#endif
@class GTLPlusItemScope;
// ----------------------------------------------------------------------------
//
// GTLPlusItemScope
//
@interface GTLPlusItemScope : GTLObject
// The subject matter of the content.
@property (retain) GTLPlusItemScope *about;
// An additional name for a Person, can be used for a middle name.
@property (retain) NSArray *additionalName; // of NSString
// Postal address.
@property (retain) GTLPlusItemScope *address;
// Address country.
@property (copy) NSString *addressCountry;
// Address locality.
@property (copy) NSString *addressLocality;
// Address region.
@property (copy) NSString *addressRegion;
// The encoding.
@property (retain) NSArray *associatedMedia; // of GTLPlusItemScope
// Number of attendees.
@property (retain) NSNumber *attendeeCount; // intValue
// A person attending the event.
@property (retain) NSArray *attendees; // of GTLPlusItemScope
// From http://schema.org/MusicRecording, the audio file.
@property (retain) GTLPlusItemScope *audio;
// The person who created this scope.
@property (retain) NSArray *author; // of GTLPlusItemScope
// Best possible rating value.
@property (copy) NSString *bestRating;
// Date of birth.
@property (copy) NSString *birthDate;
// From http://schema.org/MusicRecording, the artist that performed this
// recording.
@property (retain) GTLPlusItemScope *byArtist;
// The caption for this object.
@property (copy) NSString *caption;
// File size in (mega/kilo) bytes.
@property (copy) NSString *contentSize;
// Actual bytes of the media object, for example the image file or video file.
@property (copy) NSString *contentUrl;
// The list of contributors for this scope.
@property (retain) NSArray *contributor; // of GTLPlusItemScope
// The date this scope was created.
@property (copy) NSString *dateCreated;
// The date this scope was last modified.
@property (copy) NSString *dateModified;
// The initial date this scope was published.
@property (copy) NSString *datePublished;
// The string describing the content of this scope.
// Remapped to 'descriptionProperty' to avoid NSObject's 'description'.
@property (copy) NSString *descriptionProperty;
// The duration of the item (movie, audio recording, event, etc.) in ISO 8601
// date format.
@property (copy) NSString *duration;
// A URL pointing to a player for a specific video. In general, this is the
// information in the src element of an embed tag and should not be the same as
// the content of the loc tag.
@property (copy) NSString *embedUrl;
// The end date and time of the event (in ISO 8601 date format).
@property (copy) NSString *endDate;
// Family name. In the U.S., the last name of an Person. This can be used along
// with givenName instead of the Name property.
@property (copy) NSString *familyName;
// Gender of the person.
@property (copy) NSString *gender;
// Geo coordinates.
@property (retain) GTLPlusItemScope *geo;
// Given name. In the U.S., the first name of a Person. This can be used along
// with familyName instead of the Name property.
@property (copy) NSString *givenName;
// The height of the media object.
@property (copy) NSString *height;
// The id for this item scope.
// identifier property maps to 'id' in JSON (to avoid Objective C's 'id').
@property (copy) NSString *identifier;
// A url to the image for this scope.
@property (copy) NSString *image;
// From http://schema.org/MusicRecording, which album a song is in.
@property (retain) GTLPlusItemScope *inAlbum;
// Identifies this resource as an itemScope.
@property (copy) NSString *kind;
// Latitude.
@property (retain) NSNumber *latitude; // doubleValue
// The location of the event or organization.
@property (retain) GTLPlusItemScope *location;
// Longitude.
@property (retain) NSNumber *longitude; // doubleValue
// The name of this scope.
@property (copy) NSString *name;
// Property of http://schema.org/TVEpisode indicating which series the episode
// belongs to.
@property (retain) GTLPlusItemScope *partOfTVSeries;
// The main performer or performers of the event—for example, a presenter,
// musician, or actor.
@property (retain) NSArray *performers; // of GTLPlusItemScope
// Player type required—for example, Flash or Silverlight.
@property (copy) NSString *playerType;
// Postal code.
@property (copy) NSString *postalCode;
// Post office box number.
@property (copy) NSString *postOfficeBoxNumber;
// Rating value.
@property (copy) NSString *ratingValue;
// Review rating.
@property (retain) NSArray *reviewRating; // of GTLPlusItemScope
// The start date and time of the event (in ISO 8601 date format).
@property (copy) NSString *startDate;
// Street address.
@property (copy) NSString *streetAddress;
// Comment text, review text, etc.
@property (copy) NSString *text;
// Thumbnail image for an image or video.
@property (retain) GTLPlusItemScope *thumbnail;
// A url to a thumbnail image for this scope.
@property (copy) NSString *thumbnailUrl;
// The exchange traded instrument associated with a Corporation object. The
// tickerSymbol is expressed as an exchange and an instrument name separated by
// a space character. For the exchange component of the tickerSymbol attribute,
// we reccommend using the controlled vocaulary of Market Identifier Codes (MIC)
// specified in ISO15022.
@property (copy) NSString *tickerSymbol;
// The item type.
@property (copy) NSString *type;
// A url for this scope.
@property (copy) NSString *url;
// The width of the media object.
@property (copy) NSString *width;
// Worst possible rating value.
@property (copy) NSString *worstRating;
@end

View File

@ -0,0 +1,78 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusItemScope.m
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLPlusItemScope (0 custom class methods, 55 custom properties)
#import "GTLPlusItemScope.h"
// ----------------------------------------------------------------------------
//
// GTLPlusItemScope
//
@implementation GTLPlusItemScope
@dynamic about, additionalName, address, addressCountry, addressLocality,
addressRegion, associatedMedia, attendeeCount, attendees, audio,
author, bestRating, birthDate, byArtist, caption, contentSize,
contentUrl, contributor, dateCreated, dateModified, datePublished,
descriptionProperty, duration, embedUrl, endDate, familyName, gender,
geo, givenName, height, identifier, image, inAlbum, kind, latitude,
location, longitude, name, partOfTVSeries, performers, playerType,
postalCode, postOfficeBoxNumber, ratingValue, reviewRating, startDate,
streetAddress, text, thumbnail, thumbnailUrl, tickerSymbol, type, url,
width, worstRating;
+ (NSDictionary *)propertyToJSONKeyMap {
NSDictionary *map =
[NSDictionary dictionaryWithObjectsAndKeys:
@"associated_media", @"associatedMedia",
@"description", @"descriptionProperty",
@"id", @"identifier",
nil];
return map;
}
+ (NSDictionary *)arrayPropertyToClassMap {
NSDictionary *map =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSString class], @"additionalName",
[GTLPlusItemScope class], @"associated_media",
[GTLPlusItemScope class], @"attendees",
[GTLPlusItemScope class], @"author",
[GTLPlusItemScope class], @"contributor",
[GTLPlusItemScope class], @"performers",
[GTLPlusItemScope class], @"reviewRating",
nil];
return map;
}
+ (void)load {
[self registerObjectClassForKind:@"plus#itemScope"];
}
@end

View File

@ -0,0 +1,79 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusMoment.h
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLPlusMoment (0 custom class methods, 6 custom properties)
// GTLPlusMomentVerb (0 custom class methods, 1 custom properties)
#if GTL_BUILT_AS_FRAMEWORK
#import "GTL/GTLObject.h"
#else
#import "GTLObject.h"
#endif
@class GTLPlusItemScope;
@class GTLPlusMomentVerb;
// ----------------------------------------------------------------------------
//
// GTLPlusMoment
//
@interface GTLPlusMoment : GTLObject
// Identifies this resource as a moment.
@property (copy) NSString *kind;
// The object generated by performing the action on the item
@property (retain) GTLPlusItemScope *result;
// Timestamp of the action (when it occured) in RFC3339 format.
@property (retain) GTLDateTime *startDate;
// The object on which the action was performed
@property (retain) GTLPlusItemScope *target;
// The schema.org activity type
@property (copy) NSString *type;
// The action the user performed
@property (retain) GTLPlusMomentVerb *verb;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusMomentVerb
//
@interface GTLPlusMomentVerb : GTLObject
// Url name of the verb
@property (copy) NSString *url;
@end

View File

@ -0,0 +1,58 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusMoment.m
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLPlusMoment (0 custom class methods, 6 custom properties)
// GTLPlusMomentVerb (0 custom class methods, 1 custom properties)
#import "GTLPlusMoment.h"
#import "GTLPlusItemScope.h"
// ----------------------------------------------------------------------------
//
// GTLPlusMoment
//
@implementation GTLPlusMoment
@dynamic kind, result, startDate, target, type, verb;
+ (void)load {
[self registerObjectClassForKind:@"plus#moment"];
}
@end
// ----------------------------------------------------------------------------
//
// GTLPlusMomentVerb
//
@implementation GTLPlusMomentVerb
@dynamic url;
@end

View File

@ -0,0 +1,285 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusPerson.h
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLPlusPerson (0 custom class methods, 21 custom properties)
// GTLPlusPersonEmailsItem (0 custom class methods, 3 custom properties)
// GTLPlusPersonImage (0 custom class methods, 1 custom properties)
// GTLPlusPersonName (0 custom class methods, 6 custom properties)
// GTLPlusPersonOrganizationsItem (0 custom class methods, 9 custom properties)
// GTLPlusPersonPlacesLivedItem (0 custom class methods, 2 custom properties)
// GTLPlusPersonUrlsItem (0 custom class methods, 3 custom properties)
#if GTL_BUILT_AS_FRAMEWORK
#import "GTL/GTLObject.h"
#else
#import "GTLObject.h"
#endif
@class GTLPlusPersonEmailsItem;
@class GTLPlusPersonImage;
@class GTLPlusPersonName;
@class GTLPlusPersonOrganizationsItem;
@class GTLPlusPersonPlacesLivedItem;
@class GTLPlusPersonUrlsItem;
// ----------------------------------------------------------------------------
//
// GTLPlusPerson
//
@interface GTLPlusPerson : GTLObject
// A short biography for this person.
@property (copy) NSString *aboutMe;
// The person's date of birth, represented as YYYY-MM-DD.
@property (copy) NSString *birthday;
// The current location for this person.
@property (copy) NSString *currentLocation;
// The name of this person, suitable for display.
@property (copy) NSString *displayName;
// A list of email addresses for this person.
@property (retain) NSArray *emails; // of GTLPlusPersonEmailsItem
// ETag of this response for caching purposes.
@property (copy) NSString *ETag;
// The person's gender. Possible values are:
// - "male" - Male gender.
// - "female" - Female gender.
// - "other" - Other.
@property (copy) NSString *gender;
// If "true", indicates that the person has installed the app that is making the
// request and has chosen to expose this install state to the caller. A value of
// "false" indicates that the install state cannot be determined (it is either
// not installed or the person has chosen to keep this information private).
@property (retain) NSNumber *hasApp; // boolValue
// The ID of this person.
// identifier property maps to 'id' in JSON (to avoid Objective C's 'id').
@property (copy) NSString *identifier;
// The representation of the person's profile photo.
@property (retain) GTLPlusPersonImage *image;
// Identifies this resource as a person. Value: "plus#person".
@property (copy) NSString *kind;
// The languages spoken by this person.
@property (retain) NSArray *languagesSpoken; // of NSString
// An object representation of the individual components of a person's name.
@property (retain) GTLPlusPersonName *name;
// The nickname of this person.
@property (copy) NSString *nickname;
// Type of person within Google+. Possible values are:
// - "person" - represents an actual person.
// - "page" - represents a page.
@property (copy) NSString *objectType;
// A list of current or past organizations with which this person is associated.
@property (retain) NSArray *organizations; // of GTLPlusPersonOrganizationsItem
// A list of places where this person has lived.
@property (retain) NSArray *placesLived; // of GTLPlusPersonPlacesLivedItem
// The person's relationship status. Possible values are:
// - "single" - Person is single.
// - "in_a_relationship" - Person is in a relationship.
// - "engaged" - Person is engaged.
// - "married" - Person is married.
// - "its_complicated" - The relationship is complicated.
// - "open_relationship" - Person is in an open relationship.
// - "widowed" - Person is widowed.
// - "in_domestic_partnership" - Person is in a domestic partnership.
// - "in_civil_union" - Person is in a civil union.
@property (copy) NSString *relationshipStatus;
// The brief description (tagline) of this person.
@property (copy) NSString *tagline;
// The URL of this person's profile.
@property (copy) NSString *url;
// A list of URLs for this person.
@property (retain) NSArray *urls; // of GTLPlusPersonUrlsItem
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonEmailsItem
//
@interface GTLPlusPersonEmailsItem : GTLObject
// If "true", indicates this email address is the person's primary one.
@property (retain) NSNumber *primary; // boolValue
// The type of address. Possible values are:
// - "home" - Home email address.
// - "work" - Work email address.
// - "other" - Other.
@property (copy) NSString *type;
// The email address.
@property (copy) NSString *value;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonImage
//
@interface GTLPlusPersonImage : GTLObject
// The URL of the person's profile photo. To re-size the image and crop it to a
// square, append the query string ?sz=x, where x is the dimension in pixels of
// each side.
@property (copy) NSString *url;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonName
//
@interface GTLPlusPersonName : GTLObject
// The family name (last name) of this person.
@property (copy) NSString *familyName;
// The full name of this person, including middle names, suffixes, etc.
@property (copy) NSString *formatted;
// The given name (first name) of this person.
@property (copy) NSString *givenName;
// The honorific prefixes (such as "Dr." or "Mrs.") for this person.
@property (copy) NSString *honorificPrefix;
// The honorific suffixes (such as "Jr.") for this person.
@property (copy) NSString *honorificSuffix;
// The middle name of this person.
@property (copy) NSString *middleName;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonOrganizationsItem
//
@interface GTLPlusPersonOrganizationsItem : GTLObject
// The department within the organization.
@property (copy) NSString *department;
// A short description of the person's role in this organization.
// Remapped to 'descriptionProperty' to avoid NSObject's 'description'.
@property (copy) NSString *descriptionProperty;
// The date the person left this organization.
@property (copy) NSString *endDate;
// The location of this organization.
@property (copy) NSString *location;
// The name of the organization.
@property (copy) NSString *name;
// If "true", indicates this organization is the person's primary one (typically
// interpreted as current one).
@property (retain) NSNumber *primary; // boolValue
// The date the person joined this organization.
@property (copy) NSString *startDate;
// The person's job title or role within the organization.
@property (copy) NSString *title;
// The type of organization. Possible values are:
// - "work" - Work.
// - "school" - School.
@property (copy) NSString *type;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonPlacesLivedItem
//
@interface GTLPlusPersonPlacesLivedItem : GTLObject
// If "true", this place of residence is this person's primary residence.
@property (retain) NSNumber *primary; // boolValue
// A place where this person has lived. For example: "Seattle, WA", "Near
// Toronto".
@property (copy) NSString *value;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonUrlsItem
//
@interface GTLPlusPersonUrlsItem : GTLObject
// If "true", this URL is the person's primary URL.
@property (retain) NSNumber *primary; // boolValue
// The type of URL. Possible values are:
// - "home" - URL for home.
// - "work" - URL for work.
// - "blog" - URL for blog.
// - "profile" - URL for profile.
// - "other" - Other.
@property (copy) NSString *type;
// The URL value.
@property (copy) NSString *value;
@end

View File

@ -0,0 +1,145 @@
/* Copyright (c) 2012 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.
*/
//
// GTLPlusPerson.m
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLPlusPerson (0 custom class methods, 21 custom properties)
// GTLPlusPersonEmailsItem (0 custom class methods, 3 custom properties)
// GTLPlusPersonImage (0 custom class methods, 1 custom properties)
// GTLPlusPersonName (0 custom class methods, 6 custom properties)
// GTLPlusPersonOrganizationsItem (0 custom class methods, 9 custom properties)
// GTLPlusPersonPlacesLivedItem (0 custom class methods, 2 custom properties)
// GTLPlusPersonUrlsItem (0 custom class methods, 3 custom properties)
#import "GTLPlusPerson.h"
// ----------------------------------------------------------------------------
//
// GTLPlusPerson
//
@implementation GTLPlusPerson
@dynamic aboutMe, birthday, currentLocation, displayName, emails, ETag, gender,
hasApp, identifier, image, kind, languagesSpoken, name, nickname,
objectType, organizations, placesLived, relationshipStatus, tagline,
url, urls;
+ (NSDictionary *)propertyToJSONKeyMap {
NSDictionary *map =
[NSDictionary dictionaryWithObjectsAndKeys:
@"etag", @"ETag",
@"id", @"identifier",
nil];
return map;
}
+ (NSDictionary *)arrayPropertyToClassMap {
NSDictionary *map =
[NSDictionary dictionaryWithObjectsAndKeys:
[GTLPlusPersonEmailsItem class], @"emails",
[NSString class], @"languagesSpoken",
[GTLPlusPersonOrganizationsItem class], @"organizations",
[GTLPlusPersonPlacesLivedItem class], @"placesLived",
[GTLPlusPersonUrlsItem class], @"urls",
nil];
return map;
}
+ (void)load {
[self registerObjectClassForKind:@"plus#person"];
}
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonEmailsItem
//
@implementation GTLPlusPersonEmailsItem
@dynamic primary, type, value;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonImage
//
@implementation GTLPlusPersonImage
@dynamic url;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonName
//
@implementation GTLPlusPersonName
@dynamic familyName, formatted, givenName, honorificPrefix, honorificSuffix,
middleName;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonOrganizationsItem
//
@implementation GTLPlusPersonOrganizationsItem
@dynamic department, descriptionProperty, endDate, location, name, primary,
startDate, title, type;
+ (NSDictionary *)propertyToJSONKeyMap {
NSDictionary *map =
[NSDictionary dictionaryWithObject:@"description"
forKey:@"descriptionProperty"];
return map;
}
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonPlacesLivedItem
//
@implementation GTLPlusPersonPlacesLivedItem
@dynamic primary, value;
@end
// ----------------------------------------------------------------------------
//
// GTLPlusPersonUrlsItem
//
@implementation GTLPlusPersonUrlsItem
@dynamic primary, type, value;
@end

View File

@ -0,0 +1,90 @@
/* Copyright (c) 2012 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.
*/
//
// GTLQueryPlus.h
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLQueryPlus (2 custom class methods, 4 custom properties)
#if GTL_BUILT_AS_FRAMEWORK
#import "GTL/GTLQuery.h"
#else
#import "GTLQuery.h"
#endif
@class GTLPlusMoment;
@interface GTLQueryPlus : GTLQuery
//
// Parameters valid on all methods.
//
// Selector specifying which fields to include in a partial response.
@property (copy) NSString *fields;
//
// Method-specific parameters; see the comments below for more information.
//
@property (copy) NSString *collection;
@property (assign) BOOL debug;
@property (copy) NSString *userId;
#pragma mark -
#pragma mark "moments" methods
// These create a GTLQueryPlus object.
// Method: plus.moments.insert
// Record a user activity (e.g Bill watched a video on Youtube)
// Required:
// userId: The ID of the user to get activities for. The special value "me"
// can be used to indicate the authenticated user.
// collection: The collection to which to write moments.
// kGTLPlusCollectionVault: The default collection for writing new moments.
// Optional:
// debug: Return the moment as written. Should be used only for debugging.
// Authorization scope(s):
// kGTLAuthScopePlusMomentsWrite
// Fetches a GTLPlusMoment.
+ (id)queryForMomentsInsertWithObject:(GTLPlusMoment *)object
userId:(NSString *)userId
collection:(NSString *)collection;
#pragma mark -
#pragma mark "people" methods
// These create a GTLQueryPlus object.
// Method: plus.people.get
// Get a person's profile.
// Required:
// userId: The ID of the person to get the profile for. The special value "me"
// can be used to indicate the authenticated user.
// Authorization scope(s):
// kGTLAuthScopePlusMe
// kGTLAuthScopePlusUserinfoEmail
// Fetches a GTLPlusPerson.
+ (id)queryForPeopleGetWithUserId:(NSString *)userId;
@end

View File

@ -0,0 +1,72 @@
/* Copyright (c) 2012 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.
*/
//
// GTLQueryPlus.m
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLQueryPlus (2 custom class methods, 4 custom properties)
#import "GTLQueryPlus.h"
#import "GTLPlusMoment.h"
#import "GTLPlusPerson.h"
@implementation GTLQueryPlus
@dynamic collection, debug, fields, userId;
#pragma mark -
#pragma mark "moments" methods
// These create a GTLQueryPlus object.
+ (id)queryForMomentsInsertWithObject:(GTLPlusMoment *)object
userId:(NSString *)userId
collection:(NSString *)collection {
if (object == nil) {
GTL_DEBUG_ASSERT(object != nil, @"%@ got a nil object", NSStringFromSelector(_cmd));
return nil;
}
NSString *methodName = @"plus.moments.insert";
GTLQueryPlus *query = [self queryWithMethodName:methodName];
query.bodyObject = object;
query.userId = userId;
query.collection = collection;
query.expectedObjectClass = [GTLPlusMoment class];
return query;
}
#pragma mark -
#pragma mark "people" methods
// These create a GTLQueryPlus object.
+ (id)queryForPeopleGetWithUserId:(NSString *)userId {
NSString *methodName = @"plus.people.get";
GTLQueryPlus *query = [self queryWithMethodName:methodName];
query.userId = userId;
query.expectedObjectClass = [GTLPlusPerson class];
return query;
}
@end

View File

@ -0,0 +1,61 @@
/* Copyright (c) 2012 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.
*/
//
// GTLServicePlus.h
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLServicePlus (0 custom class methods, 0 custom properties)
#if GTL_BUILT_AS_FRAMEWORK
#import "GTL/GTLService.h"
#else
#import "GTLService.h"
#endif
@interface GTLServicePlus : GTLService
// No new methods
// Clients should create a standard query with any of the class methods in
// GTLQueryPlus.h. The query can the be sent with GTLService's execute methods,
//
// - (GTLServiceTicket *)executeQuery:(GTLQuery *)query
// completionHandler:(void (^)(GTLServiceTicket *ticket,
// id object, NSError *error))handler;
// or
// - (GTLServiceTicket *)executeQuery:(GTLQuery *)query
// delegate:(id)delegate
// didFinishSelector:(SEL)finishedSelector;
//
// where finishedSelector has a signature of:
//
// - (void)serviceTicket:(GTLServiceTicket *)ticket
// finishedWithObject:(id)object
// error:(NSError *)error;
//
// The object passed to the completion handler or delegate method
// is a subclass of GTLObject, determined by the query method executed.
@end

View File

@ -0,0 +1,63 @@
/* Copyright (c) 2012 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.
*/
//
// GTLServicePlus.m
//
// ----------------------------------------------------------------------------
// NOTE: This file is generated from Google APIs Discovery Service.
// Service:
// Google+ API (plus/v1moments)
// Description:
// The Google+ API enables developers to build on top of the Google+ platform.
// Documentation:
// http://developers.google.com/+/api/
// Classes:
// GTLServicePlus (0 custom class methods, 0 custom properties)
#import "GTLPlus.h"
@implementation GTLServicePlus
#if DEBUG
// Method compiled in debug builds just to check that all the needed support
// classes are present at link time.
+ (NSArray *)checkClasses {
NSArray *classes = [NSArray arrayWithObjects:
[GTLQueryPlus class],
[GTLPlusItemScope class],
[GTLPlusMoment class],
[GTLPlusPerson class],
nil];
return classes;
}
#endif // DEBUG
- (id)init {
self = [super init];
if (self) {
// Version from discovery.
self.apiVersion = @"v1moments";
// From discovery. Where to send JSON-RPC.
// Turn off prettyPrint for this service to save bandwidth (especially on
// mobile). The fetcher logging will pretty print.
self.rpcURL = [NSURL URLWithString:@"https://www.googleapis.com/rpc?prettyPrint=false"];
}
return self;
}
@end

View File

@ -0,0 +1,132 @@
/* Copyright (c) 2011 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.
*/
//
// GTLQuery.h
//
#import "GTLObject.h"
#import "GTLUploadParameters.h"
@protocol GTLQueryProtocol <NSObject, NSCopying>
- (BOOL)isBatchQuery;
- (BOOL)shouldSkipAuthorization;
- (void)executionDidStop;
- (NSDictionary *)additionalHTTPHeaders;
- (GTLUploadParameters *)uploadParameters;
@end
@protocol GTLQueryCollectionProtocol
@optional
@property (retain) NSString *pageToken;
@property (retain) NSNumber *startIndex;
@end
@class GTLServiceTicket;
@interface GTLQuery : NSObject <GTLQueryProtocol> {
@private
NSString *methodName_;
NSMutableDictionary *json_;
GTLObject *bodyObject_;
NSMutableDictionary *childCache_;
NSString *requestID_;
GTLUploadParameters *uploadParameters_;
NSDictionary *urlQueryParameters_;
NSDictionary *additionalHTTPHeaders_;
Class expectedObjectClass_;
BOOL skipAuthorization_;
#if NS_BLOCKS_AVAILABLE
void (^completionBlock_)(GTLServiceTicket *ticket, id object, NSError *error);
#elif !__LP64__
// Placeholders: for 32-bit builds, keep the size of the object's ivar section
// the same with and without blocks
id completionPlaceholder_;
#endif
}
// The rpc method name.
@property (readonly) NSString *methodName;
// The JSON dictionary of all the parameters set on this query.
@property (retain) NSMutableDictionary *JSON;
// The object set to be uploaded with the query.
@property (retain) GTLObject *bodyObject;
// Each query must have a request ID string. The user may replace the
// default assigned request ID with a custom string, provided that if
// used in a batch query, all request IDs in the batch must be unique.
@property (copy) NSString *requestID;
// For queries which support file upload, the MIME type and file handle
// or data must be provided.
@property (copy) GTLUploadParameters *uploadParameters;
// Any url query parameters to add to the query (useful for debugging with some
// services).
@property (copy) NSDictionary *urlQueryParameters;
// Any additional HTTP headers for this query. Not valid when this query
// is added to a batch.
//
// These headers override the same keys from the service object's
// additionalHTTPHeaders.
@property (copy) NSDictionary *additionalHTTPHeaders;
// The GTLObject subclass expected for results (used if the result doesn't
// include a kind attribute).
@property (assign) Class expectedObjectClass;
// Clients may set this to YES to disallow authorization. Defaults to NO.
@property (assign) BOOL shouldSkipAuthorization;
#if NS_BLOCKS_AVAILABLE
// Clients may provide an optional callback block to be called immediately
// before the executeQuery: callback.
//
// The completionBlock property is particularly useful for queries executed
// in a batch.
//
// Errors passed to the completionBlock will have an "underlying" GTLErrorObject
// when the server returned an error for this specific query:
//
// GTLErrorObject *errorObj = [GTLErrorObject underlyingObjectForError:error];
// if (errorObj) {
// // the server returned this error for this specific query
// } else {
// // the batch execution failed
// }
@property (copy) void (^completionBlock)(GTLServiceTicket *ticket, id object, NSError *error);
#endif
// methodName is the RPC method name to use.
+ (id)queryWithMethodName:(NSString *)methodName;
// methodName is the RPC method name to use.
- (id)initWithMethodName:(NSString *)method;
// If you need to set a parameter that is not listed as a property for a
// query class, you can do so via this api. If you need to clear it after
// setting, pass nil for obj.
- (void)setCustomParameter:(id)obj forKey:(NSString *)key;
// Auto-generated request IDs
+ (NSString *)nextRequestID;
// Methods for subclasses to override.
+ (NSDictionary *)parameterNameMap;
+ (NSDictionary *)arrayPropertyToClassMap;
@end

View File

@ -0,0 +1,267 @@
/* Copyright (c) 2011 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.
*/
//
// GTLQuery.m
//
#include <objc/runtime.h>
#import "GTLQuery.h"
#import "GTLRuntimeCommon.h"
#import "GTLUtilities.h"
@interface GTLQuery () <GTLRuntimeCommon>
@end
@implementation GTLQuery
// Implementation Note: bodyObject could be done as a dynamic property and map
// it to the key "resource". But we expose the object on the ServiceTicket
// for developers, and so sending it through the plumbing already in the
// parameters and outside of that gets into a grey area. For requests sent
// via this class, we don't need to touch the JSON, but for developers that
// have to use the lower level apis for something we'd need to know to add
// it to the JSON.
@synthesize methodName = methodName_,
JSON = json_,
bodyObject = bodyObject_,
requestID = requestID_,
uploadParameters = uploadParameters_,
urlQueryParameters = urlQueryParameters_,
additionalHTTPHeaders = additionalHTTPHeaders_,
expectedObjectClass = expectedObjectClass_,
shouldSkipAuthorization = skipAuthorization_;
#if NS_BLOCKS_AVAILABLE
@synthesize completionBlock = completionBlock_;
#endif
+ (id)queryWithMethodName:(NSString *)methodName {
return [[[self alloc] initWithMethodName:methodName] autorelease];
}
- (id)initWithMethodName:(NSString *)methodName {
self = [super init];
if (self) {
requestID_ = [[[self class] nextRequestID] retain];
methodName_ = [methodName copy];
if ([methodName_ length] == 0) {
[self release];
self = nil;
}
}
return self;
}
- (void)dealloc {
[methodName_ release];
[json_ release];
[bodyObject_ release];
[childCache_ release];
[requestID_ release];
[uploadParameters_ release];
[urlQueryParameters_ release];
[additionalHTTPHeaders_ release];
#if NS_BLOCKS_AVAILABLE
[completionBlock_ release];
#endif
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone {
GTLQuery *query =
[[[self class] allocWithZone:zone] initWithMethodName:self.methodName];
if ([json_ count] > 0) {
// Deep copy the parameters
CFPropertyListRef ref = CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
json_, kCFPropertyListMutableContainers);
query.JSON = [NSMakeCollectable(ref) autorelease];
}
query.bodyObject = self.bodyObject;
query.requestID = self.requestID;
query.uploadParameters = self.uploadParameters;
query.urlQueryParameters = self.urlQueryParameters;
query.additionalHTTPHeaders = self.additionalHTTPHeaders;
query.expectedObjectClass = self.expectedObjectClass;
query.shouldSkipAuthorization = self.shouldSkipAuthorization;
#if NS_BLOCKS_AVAILABLE
query.completionBlock = self.completionBlock;
#endif
return query;
}
- (NSString *)description {
NSArray *keys = [self.JSON allKeys];
NSArray *params = [keys sortedArrayUsingSelector:@selector(compare:)];
NSString *paramsSummary = @"";
if ([params count] > 0) {
paramsSummary = [NSString stringWithFormat:@" params:(%@)",
[params componentsJoinedByString:@","]];
}
keys = [self.urlQueryParameters allKeys];
NSArray *urlQParams = [keys sortedArrayUsingSelector:@selector(compare:)];
NSString *urlQParamsSummary = @"";
if ([urlQParams count] > 0) {
urlQParamsSummary = [NSString stringWithFormat:@" urlQParams:(%@)",
[urlQParams componentsJoinedByString:@","]];
}
GTLObject *bodyObj = self.bodyObject;
NSString *bodyObjSummary = @"";
if (bodyObj != nil) {
bodyObjSummary = [NSString stringWithFormat:@" bodyObject:%@", [bodyObj class]];
}
NSString *uploadStr = @"";
GTLUploadParameters *uploadParams = self.uploadParameters;
if (uploadParams) {
uploadStr = [NSString stringWithFormat:@" %@", uploadParams];
}
return [NSString stringWithFormat:@"%@ %p: {method:%@%@%@%@%@}",
[self class], self, self.methodName,
paramsSummary, urlQParamsSummary, bodyObjSummary, uploadStr];
}
- (void)setCustomParameter:(id)obj forKey:(NSString *)key {
[self setJSONValue:obj forKey:key];
}
- (BOOL)isBatchQuery {
return NO;
}
- (void)executionDidStop {
#if NS_BLOCKS_AVAILABLE
self.completionBlock = nil;
#endif
}
+ (NSString *)nextRequestID {
static unsigned long lastRequestID = 0;
NSString *result;
@synchronized([GTLQuery class]) {
++lastRequestID;
result = [NSString stringWithFormat:@"gtl_%lu",
(unsigned long) lastRequestID];
}
return result;
}
#pragma mark GTLRuntimeCommon Support
- (void)setJSONValue:(id)obj forKey:(NSString *)key {
NSMutableDictionary *dict = self.JSON;
if (dict == nil && obj != nil) {
dict = [NSMutableDictionary dictionaryWithCapacity:1];
self.JSON = dict;
}
[dict setValue:obj forKey:key];
}
- (id)JSONValueForKey:(NSString *)key {
id obj = [self.JSON objectForKey:key];
return obj;
}
// There is no property for childCache_ as there shouldn't be KVC/KVO
// support for it, it's an implementation detail.
- (void)setCacheChild:(id)obj forKey:(NSString *)key {
if (childCache_ == nil && obj != nil) {
childCache_ =
[[NSMutableDictionary alloc] initWithObjectsAndKeys:obj, key, nil];
} else {
[childCache_ setValue:obj forKey:key];
}
}
- (id)cacheChildForKey:(NSString *)key {
id obj = [childCache_ objectForKey:key];
return obj;
}
#pragma mark Methods for Subclasses to Override
+ (NSDictionary *)parameterNameMap {
return nil;
}
+ (NSDictionary *)arrayPropertyToClassMap {
return nil;
}
#pragma mark Runtime Utilities
static NSMutableDictionary *gParameterNameMapCache = nil;
static NSMutableDictionary *gArrayPropertyToClassMapCache = nil;
+ (void)initialize {
// note that initialize is guaranteed by the runtime to be called in a
// thread-safe manner
if (gParameterNameMapCache == nil) {
gParameterNameMapCache = [GTLUtilities newStaticDictionary];
}
if (gArrayPropertyToClassMapCache == nil) {
gArrayPropertyToClassMapCache = [GTLUtilities newStaticDictionary];
}
}
+ (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRuntimeCommon>)aClass {
NSDictionary *resultMap =
[GTLUtilities mergedClassDictionaryForSelector:@selector(parameterNameMap)
startClass:aClass
ancestorClass:[GTLQuery class]
cache:gParameterNameMapCache];
return resultMap;
}
+ (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRuntimeCommon>)aClass {
NSDictionary *resultMap =
[GTLUtilities mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap)
startClass:aClass
ancestorClass:[GTLQuery class]
cache:gArrayPropertyToClassMapCache];
return resultMap;
}
#pragma mark Runtime Support
- (NSDictionary *)surrogates {
// Stub method just needed for RumtimeCommon, query doesn't use surrogates.
return nil;
}
+ (Class<GTLRuntimeCommon>)ancestorClass {
return [GTLQuery class];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
BOOL resolved = [GTLRuntimeCommon resolveInstanceMethod:sel onClass:self];
if (resolved)
return YES;
return [super resolveInstanceMethod:sel];
}
@end

View File

@ -0,0 +1,57 @@
/* Copyright (c) 2011 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.
*/
//
// GTLRuntimeCommon.h
//
#import <Foundation/Foundation.h>
#import "GTLDefines.h"
// This protocol and support class are an internal implementation detail so
// GTLObject and GTLQuery can share some code.
@protocol GTLRuntimeCommon <NSObject>
@required
// Get/Set properties
- (void)setJSONValue:(id)obj forKey:(NSString *)key;
- (id)JSONValueForKey:(NSString *)key;
// Child cache
- (void)setCacheChild:(id)obj forKey:(NSString *)key;
- (id)cacheChildForKey:(NSString *)key;
// Surrogate class mappings.
- (NSDictionary *)surrogates;
// Key map
+ (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRuntimeCommon>)aClass;
// Array item types
+ (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRuntimeCommon>)aClass;
// The parent class for dynamic support
+ (Class<GTLRuntimeCommon>)ancestorClass;
@end
@interface GTLRuntimeCommon : NSObject
// Wire things up.
+ (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class)onClass;
// Helpers
+ (id)objectFromJSON:(id)json
defaultClass:(Class)defaultClass
surrogates:(NSDictionary *)surrogates
isCacheable:(BOOL*)isCacheable;
+ (id)jsonFromAPIObject:(id)obj
expectedClass:(Class)expectedClass
isCacheable:(BOOL*)isCacheable;
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,592 @@
/* Copyright (c) 2011 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.
*/
//
// GTLService.h
//
#import <Foundation/Foundation.h>
#import "GTLDefines.h"
#import "GTMHTTPFetcherService.h"
#import "GTLBatchQuery.h"
#import "GTLBatchResult.h"
#import "GTLDateTime.h"
#import "GTLErrorObject.h"
#import "GTLFramework.h"
#import "GTLJSONParser.h"
#import "GTLObject.h"
#import "GTLQuery.h"
#import "GTLUtilities.h"
#undef _EXTERN
#undef _INITIALIZE_AS
#ifdef GTLSERVICE_DEFINE_GLOBALS
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
#define _EXTERN extern
#define _INITIALIZE_AS(x)
#endif
// Error domains
_EXTERN NSString* const kGTLServiceErrorDomain _INITIALIZE_AS(@"com.google.GTLServiceDomain");
enum {
kGTLErrorQueryResultMissing = -3000,
kGTLErrorWaitTimedOut = -3001
};
_EXTERN NSString* const kGTLJSONRPCErrorDomain _INITIALIZE_AS(@"com.google.GTLJSONRPCErrorDomain");
// We'll consistently store the server error string in the userInfo under
// this key
_EXTERN NSString* const kGTLServerErrorStringKey _INITIALIZE_AS(@"error");
_EXTERN Class const kGTLUseRegisteredClass _INITIALIZE_AS(nil);
_EXTERN NSUInteger const kGTLStandardUploadChunkSize _INITIALIZE_AS(NSUIntegerMax);
// When servers return us structured JSON errors, the NSError will
// contain a GTLErrorObject in the userInfo dictionary under the key
// kGTLStructuredErrorsKey
_EXTERN NSString* const kGTLStructuredErrorKey _INITIALIZE_AS(@"GTLStructuredError");
// When specifying an ETag for updating or deleting a single entry, use
// kGTLETagWildcard to tell the server to replace the current value
// unconditionally. Do not use this in entries in a batch feed.
_EXTERN NSString* const kGTLETagWildcard _INITIALIZE_AS(@"*");
// Notifications when parsing of a fetcher feed or entry begins or ends
_EXTERN NSString* const kGTLServiceTicketParsingStartedNotification _INITIALIZE_AS(@"kGTLServiceTicketParsingStartedNotification");
_EXTERN NSString* const kGTLServiceTicketParsingStoppedNotification _INITIALIZE_AS(@"kGTLServiceTicketParsingStoppedNotification");
@class GTLServiceTicket;
// Block types used for fetch callbacks
//
// These typedefs are not used in the header file method declarations
// since it's more useful when code sense expansions show the argument
// types rather than the typedefs
#if NS_BLOCKS_AVAILABLE
typedef void (^GTLServiceCompletionHandler)(GTLServiceTicket *ticket, id object, NSError *error);
typedef void (^GTLServiceUploadProgressBlock)(GTLServiceTicket *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength);
#else
typedef void *GTLServiceCompletionHandler;
typedef void *GTLServiceUploadProgressBlock;
#endif // NS_BLOCKS_AVAILABLE
#pragma mark -
//
// Service base class
//
@interface GTLService : NSObject {
@private
NSOperationQueue *parseQueue_;
NSString *userAgent_;
GTMHTTPFetcherService *fetcherService_;
NSString *userAgentAddition_;
NSMutableDictionary *serviceProperties_; // initial values for properties in future tickets
NSDictionary *surrogates_; // initial value for surrogates in future tickets
SEL uploadProgressSelector_; // optional
#if NS_BLOCKS_AVAILABLE
BOOL (^retryBlock_)(GTLServiceTicket *, BOOL, NSError *);
void (^uploadProgressBlock_)(GTLServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength);
#elif !__LP64__
// Placeholders: for 32-bit builds, keep the size of the object's ivar section
// the same with and without blocks
id retryPlaceholder_;
id uploadProgressPlaceholder_;
#endif
NSUInteger uploadChunkSize_; // zero when uploading via multi-part MIME http body
BOOL isRetryEnabled_; // user allows auto-retries
SEL retrySelector_; // optional; set with setServiceRetrySelector
NSTimeInterval maxRetryInterval_; // default to 600. seconds
BOOL shouldFetchNextPages_;
NSString *apiKey_;
BOOL isRESTDataWrapperRequired_;
NSString *apiVersion_;
NSURL *rpcURL_;
NSURL *rpcUploadURL_;
NSDictionary *urlQueryParameters_;
NSDictionary *additionalHTTPHeaders_;
}
#pragma mark Query Execution
// The finishedSelector has a signature matching:
//
// - (void)serviceTicket:(GTLServiceTicket *)ticket
// finishedWithObject:(GTLObject *)object
// error:(NSError *)error
//
// If an error occurs, the error parameter will be non-nil. Otherwise,
// the object parameter will point to a GTLObject, if any was returned by
// the fetch. (Delete fetches return no object, so the second parameter will
// be nil.)
//
// If the query object is a GTLBatchQuery, the object passed to the callback
// will be a GTLBatchResult
- (GTLServiceTicket *)executeQuery:(id<GTLQueryProtocol>)query
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
#if NS_BLOCKS_AVAILABLE
- (GTLServiceTicket *)executeQuery:(id<GTLQueryProtocol>)query
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
#endif
// Automatic page fetches
//
// Tickets can optionally do a sequence of fetches for queries where
// repeated requests with nextPageToken or nextStartIndex values is required to
// retrieve items of all pages of the response collection. The client's
// callback is invoked only when all items have been retrieved, or an error has
// occurred. During the fetch, the items accumulated so far are available from
// the ticket.
//
// Note that the final object may be a combination of multiple page responses
// so it may not be the same as if all results had been returned in a single
// page. Some fields of the response such as total item counts may reflect only
// the final page's values.
//
// Automatic page fetches will return an error if more than 25 page fetches are
// required. For debug builds, this will log a warning to the console when more
// than 2 page fetches occur, as a reminder that the query's maxResults
// parameter should probably be increased to specify more items returned per
// page.
//
// Default value is NO.
@property (nonatomic, assign) BOOL shouldFetchNextPages;
// Retrying; see comments on retry support at the top of GTMHTTPFetcher.
//
// Default value is NO.
@property (nonatomic, assign, getter=isRetryEnabled) BOOL retryEnabled;
// Some services require a developer key for quotas and limits. Setting this
// will include it on all request sent to this service via a GTLQuery class.
@property (nonatomic, copy) NSString *APIKey;
// An authorizer adds user authentication headers to the request as needed.
@property (nonatomic, retain) id <GTMFetcherAuthorizationProtocol> authorizer;
// Retry selector is optional for retries.
//
// If present, it should have the signature:
// -(BOOL)ticket:(GTLServiceTicket *)ticket willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error
// and return YES to cause a retry. Note that unlike the GTMHTTPFetcher retry
// selector, this selector's first argument is a ticket, not a fetcher.
@property (nonatomic, assign) SEL retrySelector;
#if NS_BLOCKS_AVAILABLE
@property (copy) BOOL (^retryBlock)(GTLServiceTicket *ticket, BOOL suggestedWillRetry, NSError *error);
#endif
@property (nonatomic, assign) NSTimeInterval maxRetryInterval;
//
// Fetches may be done using RPC or REST APIs, without creating
// a GTLQuery object
//
#pragma mark RPC Fetch Methods
//
// These methods may be used for RPC fetches without creating a GTLQuery object
//
- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName
parameters:(NSDictionary *)parameters
objectClass:(Class)objectClass
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName
insertingObject:(GTLObject *)bodyObject
objectClass:(Class)objectClass
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName
parameters:(NSDictionary *)parameters
insertingObject:(GTLObject *)bodyObject
objectClass:(Class)objectClass
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
#if NS_BLOCKS_AVAILABLE
- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName
parameters:(NSDictionary *)parameters
objectClass:(Class)objectClass
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName
insertingObject:(GTLObject *)bodyObject
objectClass:(Class)objectClass
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName
parameters:(NSDictionary *)parameters
insertingObject:(GTLObject *)bodyObject
objectClass:(Class)objectClass
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
#endif
#pragma mark REST Fetch Methods
- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)objectURL
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)objectURL
objectClass:(Class)objectClass
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
- (GTLServiceTicket *)fetchPublicObjectWithURL:(NSURL *)objectURL
objectClass:(Class)objectClass
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
- (GTLServiceTicket *)fetchObjectByInsertingObject:(GTLObject *)bodyToPut
forURL:(NSURL *)destinationURL
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
- (GTLServiceTicket *)fetchObjectByUpdatingObject:(GTLObject *)bodyToPut
forURL:(NSURL *)destinationURL
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
- (GTLServiceTicket *)deleteResourceURL:(NSURL *)destinationURL
ETag:(NSString *)etagOrNil
delegate:(id)delegate
didFinishSelector:(SEL)finishedSelector;
#if NS_BLOCKS_AVAILABLE
- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)objectURL
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
- (GTLServiceTicket *)fetchObjectByInsertingObject:(GTLObject *)bodyToPut
forURL:(NSURL *)destinationURL
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
- (GTLServiceTicket *)fetchObjectByUpdatingObject:(GTLObject *)bodyToPut
forURL:(NSURL *)destinationURL
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
- (GTLServiceTicket *)deleteResourceURL:(NSURL *)destinationURL
ETag:(NSString *)etagOrNil
completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler;
#endif
#pragma mark User Properties
// Properties and userData are supported for client convenience.
//
// Property keys beginning with _ are reserved by the library.
//
// The service properties dictionary is copied to become the initial property
// dictionary for each ticket.
- (void)setServiceProperty:(id)obj forKey:(NSString *)key; // pass nil obj to remove property
- (id)servicePropertyForKey:(NSString *)key;
@property (nonatomic, copy) NSDictionary *serviceProperties;
// The service userData becomes the initial value for each future ticket's
// userData.
@property (nonatomic, retain) id serviceUserData;
#pragma mark Request Settings
// Set the surrogates to be used for future tickets. Surrogates are subclasses
// to be used instead of standard classes when creating objects from the JSON.
// For example, this code will make the framework generate objects
// using MyCalendarItemSubclass instead of GTLItemCalendar and
// MyCalendarEventSubclass instead of GTLItemCalendarEvent.
//
// NSDictionary *surrogates = [NSDictionary dictionaryWithObjectsAndKeys:
// [MyCalendarEntrySubclass class], [GTLItemCalendar class],
// [MyCalendarEventSubclass class], [GTLItemCalendarEvent class],
// nil];
// [calendarService setServiceSurrogates:surrogates];
//
@property (nonatomic, retain) NSDictionary *surrogates;
// On iOS 4 and later, the fetch may optionally continue in the background
// until finished or stopped by OS expiration.
//
// The default value is NO.
//
// For Mac OS X, background fetches are always supported, and this property
// is ignored.
@property (nonatomic, assign) BOOL shouldFetchInBackground;
// Run loop modes are used for scheduling NSURLConnections.
//
// The default value, nil, schedules connections using the current run
// loop mode. To use the service during a modal dialog, be sure to specify
// NSModalPanelRunLoopMode as one of the modes.
@property (nonatomic, retain) NSArray *runLoopModes;
// Applications needing an additional identifier in the server logs may specify
// one.
@property (nonatomic, copy) NSString *userAgentAddition;
// Applications have a default user-agent based on the application signature
// in the Info.plist settings. Most applications should not explicitly set
// this property.
@property (nonatomic, copy) NSString *userAgent;
// The request user agent includes the library and OS version appended to the
// base userAgent, along with the optional addition string.
@property (nonatomic, readonly) NSString *requestUserAgent;
// Applications may call requestForURL:httpMethod to get a request with the
// proper user-agent and ETag headers
//
// For http method, pass nil (for default GET method), POST, PUT, or DELETE
- (NSMutableURLRequest *)requestForURL:(NSURL *)url
ETag:(NSString *)etagOrNil
httpMethod:(NSString *)httpMethodOrNil;
// objectRequestForURL returns an NSMutableURLRequest for a JSON GTL object
//
// The object is the object being sent to the server, or nil;
// the http method may be nil for GET, or POST, PUT, DELETE
- (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
object:(GTLObject *)object
ETag:(NSString *)etag
httpMethod:(NSString *)httpMethod
isREST:(BOOL)isREST
additionalHeaders:(NSDictionary *)additionalHeaders
ticket:(GTLServiceTicket *)ticket;
// The queue used for parsing JSON responses (previously this property
// was called operationQueue)
@property (nonatomic, retain) NSOperationQueue *parseQueue;
// The fetcher service object issues the GTMHTTPFetcher instances
// for this API service
@property (nonatomic, retain) GTMHTTPFetcherService *fetcherService;
// Default storage for cookies is in the service object's fetchHistory.
//
// Apps that want to share cookies between all standalone fetchers and the
// service object may specify static application-wide cookie storage,
// kGTMHTTPFetcherCookieStorageMethodStatic.
@property (nonatomic, assign) NSInteger cookieStorageMethod;
// When sending REST style queries, should the payload be wrapped in a "data"
// element, and will the reply be wrapped in an "data" element.
@property (nonatomic, assign) BOOL isRESTDataWrapperRequired;
// Any url query parameters to add to urls (useful for debugging with some
// services).
@property (copy) NSDictionary *urlQueryParameters;
// Any extra http headers to set on requests for GTLObjects.
@property (copy) NSDictionary *additionalHTTPHeaders;
// The service API version.
@property (nonatomic, copy) NSString *apiVersion;
// The URL for sending RPC requests for this service.
@property (nonatomic, retain) NSURL *rpcURL;
// The URL for sending RPC requests which initiate file upload.
@property (nonatomic, retain) NSURL *rpcUploadURL;
// Set a non-zero value to enable uploading via chunked fetches
// (resumable uploads); typically this defaults to kGTLStandardUploadChunkSize
// for service subclasses that support chunked uploads
@property (nonatomic, assign) NSUInteger serviceUploadChunkSize;
// Service subclasses may specify their own default chunk size
+ (NSUInteger)defaultServiceUploadChunkSize;
// The service uploadProgressSelector becomes the initial value for each future
// ticket's uploadProgressSelector.
//
// The optional uploadProgressSelector will be called in the delegate as bytes
// are uploaded to the server. It should have a signature matching
//
// - (void)ticket:(GTLServiceTicket *)ticket
// hasDeliveredByteCount:(unsigned long long)numberOfBytesRead
// ofTotalByteCount:(unsigned long long)dataLength;
@property (nonatomic, assign) SEL uploadProgressSelector;
#if NS_BLOCKS_AVAILABLE
@property (copy) void (^uploadProgressBlock)(GTLServiceTicket *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength);
#endif
// Wait synchronously for fetch to complete (strongly discouraged)
//
// This just runs the current event loop until the fetch completes
// or the timout limit is reached. This may discard unexpected events
// that occur while spinning, so it's really not appropriate for use
// in serious applications.
//
// Returns true if an object was successfully fetched. If the wait
// timed out, returns false and the returned error is nil.
//
// The returned object or error, if any, will be already autoreleased
//
// This routine will likely be removed in some future releases of the library.
- (BOOL)waitForTicket:(GTLServiceTicket *)ticket
timeout:(NSTimeInterval)timeoutInSeconds
fetchedObject:(GTLObject **)outObjectOrNil
error:(NSError **)outErrorOrNil;
@end
#pragma mark -
//
// Ticket base class
//
@interface GTLServiceTicket : NSObject {
GTLService *service_;
NSMutableDictionary *ticketProperties_;
NSDictionary *surrogates_;
GTMHTTPFetcher *objectFetcher_;
SEL uploadProgressSelector_;
BOOL shouldFetchNextPages_;
BOOL isRetryEnabled_;
SEL retrySelector_;
NSTimeInterval maxRetryInterval_;
#if NS_BLOCKS_AVAILABLE
BOOL (^retryBlock_)(GTLServiceTicket *, BOOL, NSError *);
void (^uploadProgressBlock_)(GTLServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength);
#elif !__LP64__
// Placeholders: for 32-bit builds, keep the size of the object's ivar section
// the same with and without blocks
id retryPlaceholder_;
id uploadProgressPlaceholder_;
#endif
GTLObject *postedObject_;
GTLObject *fetchedObject_;
id<GTLQueryProtocol> executingQuery_;
id<GTLQueryProtocol> originalQuery_;
NSError *fetchError_;
BOOL hasCalledCallback_;
NSUInteger pagesFetchedCounter_;
NSString *apiKey_;
BOOL isREST_;
NSOperation *parseOperation_;
}
+ (id)ticketForService:(GTLService *)service;
- (id)initWithService:(GTLService *)service;
- (id)service;
#pragma mark Execution Control
// if cancelTicket is called, the fetch is stopped if it is in progress,
// the callbacks will not be called, and the ticket will no longer be useful
// (though the client must still release the ticket if it retained the ticket)
- (void)cancelTicket;
// chunked upload tickets may be paused
- (void)pauseUpload;
- (void)resumeUpload;
- (BOOL)isUploadPaused;
@property (nonatomic, retain) GTMHTTPFetcher *objectFetcher;
@property (nonatomic, assign) SEL uploadProgressSelector;
// Services which do not require an user authorization may require a developer
// API key for quota management
@property (nonatomic, copy) NSString *APIKey;
#pragma mark User Properties
// Properties and userData are supported for client convenience.
//
// Property keys beginning with _ are reserved by the library.
- (void)setProperty:(id)obj forKey:(NSString *)key; // pass nil obj to remove property
- (id)propertyForKey:(NSString *)key;
@property (nonatomic, copy) NSDictionary *properties;
@property (nonatomic, retain) id userData;
#pragma mark Payload
@property (nonatomic, retain) GTLObject *postedObject;
@property (nonatomic, retain) GTLObject *fetchedObject;
@property (nonatomic, retain) id<GTLQueryProtocol> executingQuery; // Query currently being fetched by this ticket
@property (nonatomic, retain) id<GTLQueryProtocol> originalQuery; // Query used to create this ticket
- (GTLQuery *)queryForRequestID:(NSString *)requestID; // Returns the query from within the batch with the given id.
@property (nonatomic, retain) NSDictionary *surrogates;
#pragma mark Retry
@property (nonatomic, assign, getter=isRetryEnabled) BOOL retryEnabled;
@property (nonatomic, assign) SEL retrySelector;
#if NS_BLOCKS_AVAILABLE
@property (copy) BOOL (^retryBlock)(GTLServiceTicket *ticket, BOOL suggestedWillRetry, NSError *error);
#endif
@property (nonatomic, assign) NSTimeInterval maxRetryInterval;
#pragma mark Status
@property (nonatomic, readonly) NSInteger statusCode; // server status from object fetch
@property (nonatomic, retain) NSError *fetchError;
@property (nonatomic, assign) BOOL hasCalledCallback;
#pragma mark Pagination
@property (nonatomic, assign) BOOL shouldFetchNextPages;
@property (nonatomic, assign) NSUInteger pagesFetchedCounter;
#pragma mark Upload
#if NS_BLOCKS_AVAILABLE
@property (copy) void (^uploadProgressBlock)(GTLServiceTicket *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength);
#endif
@end
// Category to provide opaque access to tickets stored in fetcher properties
@interface GTMHTTPFetcher (GTLServiceTicketAdditions)
- (id)ticket;
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
//
// Makes the value of GTL_TARGET_NAMESPACE a prefix for all GTL
// library class names
//
//
// To avoid global namespace issues, define GTL_TARGET_NAMESPACE to a short
// string in your target if you are using the GTL library in a shared-code
// environment like a plug-in.
//
// For example: -DGTL_TARGET_NAMESPACE=MyPlugin
//
//
// com.google.GTLFramework v. 2.0 (29 classes) 2011-10-25 19:25:36 -0700
//
#if defined(__OBJC__) && defined(GTL_TARGET_NAMESPACE)
#define _GTL_NS_SYMBOL_INNER(ns, symbol) ns ## _ ## symbol
#define _GTL_NS_SYMBOL_MIDDLE(ns, symbol) _GTL_NS_SYMBOL_INNER(ns, symbol)
#define _GTL_NS_SYMBOL(symbol) _GTL_NS_SYMBOL_MIDDLE(GTL_TARGET_NAMESPACE, symbol)
#define _GTL_NS_STRING_INNER(ns) #ns
#define _GTL_NS_STRING_MIDDLE(ns) _GTL_NS_STRING_INNER(ns)
#define GTL_TARGET_NAMESPACE_STRING _GTL_NS_STRING_MIDDLE(GTL_TARGET_NAMESPACE)
#define GTLBatchQuery _GTL_NS_SYMBOL(GTLBatchQuery)
#define GTLBatchResult _GTL_NS_SYMBOL(GTLBatchResult)
#define GTLCollectionObject _GTL_NS_SYMBOL(GTLCollectionObject)
#define GTLDateTime _GTL_NS_SYMBOL(GTLDateTime)
#define GTLErrorObject _GTL_NS_SYMBOL(GTLErrorObject)
#define GTLErrorObjectData _GTL_NS_SYMBOL(GTLErrorObjectData)
#define GTLJSONParser _GTL_NS_SYMBOL(GTLJSONParser)
#define GTLObject _GTL_NS_SYMBOL(GTLObject)
#define GTLQuery _GTL_NS_SYMBOL(GTLQuery)
#define GTLRuntimeCommon _GTL_NS_SYMBOL(GTLRuntimeCommon)
#define GTLService _GTL_NS_SYMBOL(GTLService)
#define GTLServiceTicket _GTL_NS_SYMBOL(GTLServiceTicket)
#define GTLUploadParameters _GTL_NS_SYMBOL(GTLUploadParameters)
#define GTLUtilities _GTL_NS_SYMBOL(GTLUtilities)
#define GTMCachedURLResponse _GTL_NS_SYMBOL(GTMCachedURLResponse)
#define GTMCookieStorage _GTL_NS_SYMBOL(GTMCookieStorage)
#define GTMGatherInputStream _GTL_NS_SYMBOL(GTMGatherInputStream)
#define GTMHTTPFetcher _GTL_NS_SYMBOL(GTMHTTPFetcher)
#define GTMHTTPFetcherService _GTL_NS_SYMBOL(GTMHTTPFetcherService)
#define GTMHTTPFetchHistory _GTL_NS_SYMBOL(GTMHTTPFetchHistory)
#define GTMHTTPUploadFetcher _GTL_NS_SYMBOL(GTMHTTPUploadFetcher)
#define GTMMIMEDocument _GTL_NS_SYMBOL(GTMMIMEDocument)
#define GTMMIMEPart _GTL_NS_SYMBOL(GTMMIMEPart)
#define GTMOAuth2Authentication _GTL_NS_SYMBOL(GTMOAuth2Authentication)
#define GTMOAuth2AuthorizationArgs _GTL_NS_SYMBOL(GTMOAuth2AuthorizationArgs)
#define GTMOAuth2SignIn _GTL_NS_SYMBOL(GTMOAuth2SignIn)
#define GTMOAuth2WindowController _GTL_NS_SYMBOL(GTMOAuth2WindowController)
#define GTMReadMonitorInputStream _GTL_NS_SYMBOL(GTMReadMonitorInputStream)
#define GTMURLCache _GTL_NS_SYMBOL(GTMURLCache)
#endif

View File

@ -0,0 +1,57 @@
/* Copyright (c) 2011 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.
*/
//
// GTLUploadParameters.h
//
#import <Foundation/Foundation.h>
#import "GTLDefines.h"
@interface GTLUploadParameters : NSObject <NSCopying> {
@private
NSString *MIMEType_;
NSData *data_;
NSFileHandle *fileHandle_;
NSURL *uploadLocationURL_;
NSString *slug_;
BOOL shouldSendUploadOnly_;
}
// Uploading requires MIME type and one of
// - data to be uploaded
// - file handle for uploading
@property (copy) NSString *MIMEType;
@property (retain) NSData *data;
@property (retain) NSFileHandle *fileHandle;
// Resuming an in-progress upload is done with the upload location URL,
// and requires a file handle for uploading
@property (retain) NSURL *uploadLocationURL;
// Some services need a slug (filename) header
@property (copy) NSString *slug;
// Uploads may be done without a JSON body in the initial request
@property (assign) BOOL shouldSendUploadOnly;
+ (GTLUploadParameters *)uploadParametersWithData:(NSData *)data
MIMEType:(NSString *)mimeType;
+ (GTLUploadParameters *)uploadParametersWithFileHandle:(NSFileHandle *)fileHandle
MIMEType:(NSString *)mimeType;
@end

View File

@ -0,0 +1,107 @@
/* Copyright (c) 2011 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.
*/
//
// GTLObject.m
//
#include <objc/runtime.h>
#import "GTLUploadParameters.h"
@implementation GTLUploadParameters
@synthesize MIMEType = MIMEType_,
data = data_,
fileHandle = fileHandle_,
uploadLocationURL = uploadLocationURL_,
slug = slug_,
shouldSendUploadOnly = shouldSendUploadOnly_;
+ (GTLUploadParameters *)uploadParametersWithData:(NSData *)data
MIMEType:(NSString *)mimeType {
GTLUploadParameters *params = [[[GTLUploadParameters alloc] init] autorelease];
params.data = data;
params.MIMEType = mimeType;
return params;
}
+ (GTLUploadParameters *)uploadParametersWithFileHandle:(NSFileHandle *)fileHandle
MIMEType:(NSString *)mimeType {
GTLUploadParameters *params = [[[GTLUploadParameters alloc] init] autorelease];
params.fileHandle = fileHandle;
params.MIMEType = mimeType;
return params;
}
- (id)copyWithZone:(NSZone *)zone {
GTLUploadParameters *newParams = [[[self class] allocWithZone:zone] init];
newParams.MIMEType = self.MIMEType;
newParams.data = self.data;
newParams.fileHandle = self.fileHandle;
newParams.uploadLocationURL = self.uploadLocationURL;
newParams.slug = self.slug;
newParams.shouldSendUploadOnly = self.shouldSendUploadOnly;
return newParams;
}
- (void)dealloc {
[MIMEType_ release];
[data_ release];
[fileHandle_ release];
[uploadLocationURL_ release];
[slug_ release];
[super dealloc];
}
- (NSString *)description {
NSMutableArray *array = [NSMutableArray array];
NSString *str = [NSString stringWithFormat:@"MIMEType:%@", MIMEType_];
[array addObject:str];
if (data_) {
str = [NSString stringWithFormat:@"data:%llu bytes",
(unsigned long long)[data_ length]];
[array addObject:str];
}
if (fileHandle_) {
str = [NSString stringWithFormat:@"fileHandle:%@", fileHandle_];
[array addObject:str];
}
if (uploadLocationURL_) {
str = [NSString stringWithFormat:@"uploadLocation:%@",
[uploadLocationURL_ absoluteString]];
[array addObject:str];
}
if (slug_) {
str = [NSString stringWithFormat:@"slug:%@", slug_];
[array addObject:str];
}
if (shouldSendUploadOnly_) {
[array addObject:@"shouldSendUploadOnly"];
}
NSString *descStr = [array componentsJoinedByString:@", "];
str = [NSString stringWithFormat:@"%@ %p: {%@}",
[self class], self, descStr];
return str;
}
@end

View File

@ -0,0 +1,105 @@
/* Copyright (c) 2011 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>
#ifndef SKIP_GTL_DEFINES
#import "GTLDefines.h"
#endif
// helper functions for implementing isEqual:
BOOL GTL_AreEqualOrBothNil(id obj1, id obj2);
BOOL GTL_AreBoolsEqual(BOOL b1, BOOL b2);
// Helper to ensure a number is a number.
//
// The GoogleAPI servers will send numbers >53 bits as strings to avoid
// bugs in some JavaScript implementations. Work around this by catching
// the string and turning it back into a number.
NSNumber *GTL_EnsureNSNumber(NSNumber *num);
@interface GTLUtilities : NSObject
//
// String encoding
//
// URL encoding, different for parts of URLs and parts of URL parameters
//
// +stringByURLEncodingString just makes a string legal for a URL
//
// +stringByURLEncodingForURI also encodes some characters that are legal in
// URLs but should not be used in URIs,
// per http://bitworking.org/projects/atom/rfc5023.html#rfc.section.9.7
//
// +stringByURLEncodingStringParameter is like +stringByURLEncodingForURI but
// replaces space characters with + characters rather than percent-escaping them
//
+ (NSString *)stringByURLEncodingString:(NSString *)str;
+ (NSString *)stringByURLEncodingForURI:(NSString *)str;
+ (NSString *)stringByURLEncodingStringParameter:(NSString *)str;
// Percent-encoded UTF-8
+ (NSString *)stringByPercentEncodingUTF8ForString:(NSString *)str;
// Key-value coding searches in an array
//
// Utilities to get from an array objects having a known value (or nil)
// at a keyPath
+ (NSArray *)objectsFromArray:(NSArray *)sourceArray
withValue:(id)desiredValue
forKeyPath:(NSString *)keyPath;
+ (id)firstObjectFromArray:(NSArray *)sourceArray
withValue:(id)desiredValue
forKeyPath:(NSString *)keyPath;
//
// Version helpers
//
+ (NSComparisonResult)compareVersion:(NSString *)ver1 toVersion:(NSString *)ver2;
//
// URL builder
//
// If there are already query parameters on urlString, the new ones are simple
// appended after them.
+ (NSURL *)URLWithString:(NSString *)urlString
queryParameters:(NSDictionary *)queryParameters;
// Allocate a global dictionary
+ (NSMutableDictionary *)newStaticDictionary;
// Walk up the class tree merging dictionaries and return the result.
+ (NSDictionary *)mergedClassDictionaryForSelector:(SEL)selector
startClass:(Class)startClass
ancestorClass:(Class)ancestorClass
cache:(NSMutableDictionary *)cache;
//
// MIME Types
//
// Utility routine to convert a file path to the file's MIME type using
// Mac OS X's UTI database
#if !GTL_FOUNDATION_ONLY
+ (NSString *)MIMETypeForFileAtPath:(NSString *)path
defaultMIMEType:(NSString *)defaultType;
#endif
@end

View File

@ -0,0 +1,376 @@
/* Copyright (c) 2011 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 "GTLUtilities.h"
#include <objc/runtime.h>
@implementation GTLUtilities
#pragma mark String encoding
// URL Encoding
+ (NSString *)stringByURLEncodingString:(NSString *)str {
NSString *result = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
// NSURL's stringByAddingPercentEscapesUsingEncoding: does not escape
// some characters that should be escaped in URL parameters, like / and ?;
// we'll use CFURL to force the encoding of those
//
// Reference: http://www.ietf.org/rfc/rfc3986.txt
const CFStringRef kCharsToForceEscape = CFSTR("!*'();:@&=+$,/?%#[]");
+ (NSString *)stringByURLEncodingForURI:(NSString *)str {
NSString *resultStr = str;
CFStringRef originalString = (CFStringRef) str;
CFStringRef leaveUnescaped = NULL;
CFStringRef escapedStr;
escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
originalString,
leaveUnescaped,
kCharsToForceEscape,
kCFStringEncodingUTF8);
if (escapedStr) {
resultStr = [(id)CFMakeCollectable(escapedStr) autorelease];
}
return resultStr;
}
+ (NSString *)stringByURLEncodingStringParameter:(NSString *)str {
// For parameters, we'll explicitly leave spaces unescaped now, and replace
// them with +'s
NSString *resultStr = str;
CFStringRef originalString = (CFStringRef) str;
CFStringRef leaveUnescaped = CFSTR(" ");
CFStringRef escapedStr;
escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
originalString,
leaveUnescaped,
kCharsToForceEscape,
kCFStringEncodingUTF8);
if (escapedStr) {
NSMutableString *mutableStr = [NSMutableString stringWithString:(NSString *)escapedStr];
CFRelease(escapedStr);
// replace spaces with plusses
[mutableStr replaceOccurrencesOfString:@" "
withString:@"+"
options:0
range:NSMakeRange(0, [mutableStr length])];
resultStr = mutableStr;
}
return resultStr;
}
+ (NSString *)stringByPercentEncodingUTF8ForString:(NSString *)inputStr {
// Encode per http://bitworking.org/projects/atom/rfc5023.html#rfc.section.9.7
//
// This is used for encoding upload slug headers
//
// Step through the string as UTF-8, and replace characters outside 20..7E
// (and the percent symbol itself, 25) with percent-encodings
//
// We avoid creating an encoding string unless we encounter some characters
// that require it
const char* utf8 = [inputStr UTF8String];
if (utf8 == NULL) {
return nil;
}
NSMutableString *encoded = nil;
for (unsigned int idx = 0; utf8[idx] != '\0'; idx++) {
unsigned char currChar = utf8[idx];
if (currChar < 0x20 || currChar == 0x25 || currChar > 0x7E) {
if (encoded == nil) {
// Start encoding and catch up on the character skipped so far
encoded = [[[NSMutableString alloc] initWithBytes:utf8
length:idx
encoding:NSUTF8StringEncoding] autorelease];
}
// append this byte as a % and then uppercase hex
[encoded appendFormat:@"%%%02X", currChar];
} else {
// This character does not need encoding
//
// Encoded is nil here unless we've encountered a previous character
// that needed encoding
[encoded appendFormat:@"%c", currChar];
}
}
if (encoded) {
return encoded;
}
return inputStr;
}
#pragma mark Key-Value Coding Searches in an Array
+ (NSArray *)objectsFromArray:(NSArray *)sourceArray
withValue:(id)desiredValue
forKeyPath:(NSString *)keyPath {
// Step through all entries, get the value from
// the key path, and see if it's equal to the
// desired value
NSMutableArray *results = [NSMutableArray array];
for(id obj in sourceArray) {
id val = [obj valueForKeyPath:keyPath];
if (GTL_AreEqualOrBothNil(val, desiredValue)) {
// found a match; add it to the results array
[results addObject:obj];
}
}
return results;
}
+ (id)firstObjectFromArray:(NSArray *)sourceArray
withValue:(id)desiredValue
forKeyPath:(NSString *)keyPath {
for (id obj in sourceArray) {
id val = [obj valueForKeyPath:keyPath];
if (GTL_AreEqualOrBothNil(val, desiredValue)) {
// found a match; return it
return obj;
}
}
return nil;
}
#pragma mark Version helpers
// compareVersion compares two strings in 1.2.3.4 format
// missing fields are interpreted as zeros, so 1.2 = 1.2.0.0
+ (NSComparisonResult)compareVersion:(NSString *)ver1 toVersion:(NSString *)ver2 {
static NSCharacterSet* dotSet = nil;
if (dotSet == nil) {
dotSet = [[NSCharacterSet characterSetWithCharactersInString:@"."] retain];
}
if (ver1 == nil) ver1 = @"";
if (ver2 == nil) ver2 = @"";
NSScanner* scanner1 = [NSScanner scannerWithString:ver1];
NSScanner* scanner2 = [NSScanner scannerWithString:ver2];
[scanner1 setCharactersToBeSkipped:dotSet];
[scanner2 setCharactersToBeSkipped:dotSet];
int partA1 = 0, partA2 = 0, partB1 = 0, partB2 = 0;
int partC1 = 0, partC2 = 0, partD1 = 0, partD2 = 0;
if ([scanner1 scanInt:&partA1] && [scanner1 scanInt:&partB1]
&& [scanner1 scanInt:&partC1] && [scanner1 scanInt:&partD1]) {
}
if ([scanner2 scanInt:&partA2] && [scanner2 scanInt:&partB2]
&& [scanner2 scanInt:&partC2] && [scanner2 scanInt:&partD2]) {
}
if (partA1 != partA2) return ((partA1 < partA2) ? NSOrderedAscending : NSOrderedDescending);
if (partB1 != partB2) return ((partB1 < partB2) ? NSOrderedAscending : NSOrderedDescending);
if (partC1 != partC2) return ((partC1 < partC2) ? NSOrderedAscending : NSOrderedDescending);
if (partD1 != partD2) return ((partD1 < partD2) ? NSOrderedAscending : NSOrderedDescending);
return NSOrderedSame;
}
#pragma mark URL builder
+ (NSURL *)URLWithString:(NSString *)urlString
queryParameters:(NSDictionary *)queryParameters {
if ([urlString length] == 0) return nil;
NSString *fullURLString;
if ([queryParameters count] > 0) {
NSMutableArray *queryItems = [NSMutableArray arrayWithCapacity:[queryParameters count]];
// sort the custom parameter keys so that we have deterministic parameter
// order for unit tests
NSArray *queryKeys = [queryParameters allKeys];
NSArray *sortedQueryKeys = [queryKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
for (NSString *paramKey in sortedQueryKeys) {
NSString *paramValue = [queryParameters valueForKey:paramKey];
NSString *paramItem = [NSString stringWithFormat:@"%@=%@",
[self stringByURLEncodingStringParameter:paramKey],
[self stringByURLEncodingStringParameter:paramValue]];
[queryItems addObject:paramItem];
}
NSString *paramStr = [queryItems componentsJoinedByString:@"&"];
BOOL hasQMark = ([urlString rangeOfString:@"?"].location == NSNotFound);
char joiner = hasQMark ? '?' : '&';
fullURLString = [NSString stringWithFormat:@"%@%c%@",
urlString, joiner, paramStr];
} else {
fullURLString = urlString;
}
NSURL *result = [NSURL URLWithString:fullURLString];
return result;
}
#pragma mark Collections
+ (NSMutableDictionary *)newStaticDictionary {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
// make the dictionary ineligible for garbage collection
#if !GTL_IPHONE
[[NSGarbageCollector defaultCollector] disableCollectorForPointer:dict];
#endif
return dict;
}
+ (NSDictionary *)mergedClassDictionaryForSelector:(SEL)selector
startClass:(Class)startClass
ancestorClass:(Class)ancestorClass
cache:(NSMutableDictionary *)cache {
NSDictionary *result;
@synchronized(cache) {
result = [cache objectForKey:startClass];
if (result == nil) {
// Collect the class's dictionary.
NSDictionary *classDict = [startClass performSelector:selector];
// Collect the parent class's merged dictionary.
NSDictionary *parentClassMergedDict;
if ([startClass isEqual:ancestorClass]) {
parentClassMergedDict = nil;
} else {
Class parentClass = class_getSuperclass(startClass);
parentClassMergedDict =
[GTLUtilities mergedClassDictionaryForSelector:selector
startClass:parentClass
ancestorClass:ancestorClass
cache:cache];
}
// Merge this class's into the parent's so things properly override.
NSMutableDictionary *mergeDict;
if (parentClassMergedDict != nil) {
mergeDict =
[NSMutableDictionary dictionaryWithDictionary:parentClassMergedDict];
} else {
mergeDict = [NSMutableDictionary dictionary];
}
if (classDict != nil) {
[mergeDict addEntriesFromDictionary:classDict];
}
// Make an immutable version.
result = [NSDictionary dictionaryWithDictionary:mergeDict];
// Save it.
[cache setObject:result forKey:(id<NSCopying>)startClass];
}
}
return result;
}
#pragma mark MIME Types
// Utility routine to convert a file path to the file's MIME type using
// Mac OS X's UTI database
#if !GTL_FOUNDATION_ONLY
+ (NSString *)MIMETypeForFileAtPath:(NSString *)path
defaultMIMEType:(NSString *)defaultType {
NSString *result = defaultType;
// Convert the path to an FSRef
FSRef fileFSRef;
Boolean isDirectory;
OSStatus err = FSPathMakeRef((UInt8 *) [path fileSystemRepresentation],
&fileFSRef, &isDirectory);
if (err == noErr) {
// Get the UTI (content type) for the FSRef
CFStringRef fileUTI;
err = LSCopyItemAttribute(&fileFSRef, kLSRolesAll, kLSItemContentType,
(CFTypeRef *)&fileUTI);
if (err == noErr) {
// Get the MIME type for the UTI
CFStringRef mimeTypeTag;
mimeTypeTag = UTTypeCopyPreferredTagWithClass(fileUTI,
kUTTagClassMIMEType);
if (mimeTypeTag) {
// Convert the CFStringRef to an autoreleased NSString
result = [(id)CFMakeCollectable(mimeTypeTag) autorelease];
}
CFRelease(fileUTI);
}
}
return result;
}
#endif
@end
// isEqual: has the fatal flaw that it doesn't deal well with the receiver
// being nil. We'll use this utility instead.
BOOL GTL_AreEqualOrBothNil(id obj1, id obj2) {
if (obj1 == obj2) {
return YES;
}
if (obj1 && obj2) {
BOOL areEqual = [obj1 isEqual:obj2];
return areEqual;
}
return NO;
}
BOOL GTL_AreBoolsEqual(BOOL b1, BOOL b2) {
// avoid comparison problems with boolean types by negating
// both booleans
return (!b1 == !b2);
}
NSNumber *GTL_EnsureNSNumber(NSNumber *num) {
if ([num isKindOfClass:[NSString class]]) {
NSDecimalNumber *reallyNum;
// Force the parse to use '.' as the number seperator.
static NSLocale *usLocale = nil;
@synchronized([GTLUtilities class]) {
if (usLocale == nil) {
usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
}
reallyNum = [NSDecimalNumber decimalNumberWithString:(NSString*)num
locale:(id)usLocale];
}
if (reallyNum != nil) {
num = reallyNum;
}
}
return num;
}

View File

@ -0,0 +1,439 @@
//
// GTMDefines.h
//
// 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.
//
// ============================================================================
#include <AvailabilityMacros.h>
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
#include <Availability.h>
#endif // TARGET_OS_IPHONE
// Not all MAC_OS_X_VERSION_10_X macros defined in past SDKs
#ifndef MAC_OS_X_VERSION_10_5
#define MAC_OS_X_VERSION_10_5 1050
#endif
#ifndef MAC_OS_X_VERSION_10_6
#define MAC_OS_X_VERSION_10_6 1060
#endif
#ifndef MAC_OS_X_VERSION_10_7
#define MAC_OS_X_VERSION_10_7 1070
#endif
// Not all __IPHONE_X macros defined in past SDKs
#ifndef __IPHONE_3_0
#define __IPHONE_3_0 30000
#endif
#ifndef __IPHONE_3_1
#define __IPHONE_3_1 30100
#endif
#ifndef __IPHONE_3_2
#define __IPHONE_3_2 30200
#endif
#ifndef __IPHONE_4_0
#define __IPHONE_4_0 40000
#endif
#ifndef __IPHONE_4_3
#define __IPHONE_4_3 40300
#endif
#ifndef __IPHONE_5_0
#define __IPHONE_5_0 50000
#endif
// ----------------------------------------------------------------------------
// CPP symbols that can be overridden in a prefix to control how the toolbox
// is compiled.
// ----------------------------------------------------------------------------
// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT macros you can control what happens
// when a validation fails. If you implement your own validators, you may want
// to control their internals using the same macros for consistency.
#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT 0
#endif
// Give ourselves a consistent way to do inlines. Apple's macros even use
// a few different actual definitions, so we're based off of the foundation
// one.
#if !defined(GTM_INLINE)
#if (defined (__GNUC__) && (__GNUC__ == 4)) || defined (__clang__)
#define GTM_INLINE static __inline__ __attribute__((always_inline))
#else
#define GTM_INLINE static __inline__
#endif
#endif
// Give ourselves a consistent way of doing externs that links up nicely
// when mixing objc and objc++
#if !defined (GTM_EXTERN)
#if defined __cplusplus
#define GTM_EXTERN extern "C"
#define GTM_EXTERN_C_BEGIN extern "C" {
#define GTM_EXTERN_C_END }
#else
#define GTM_EXTERN extern
#define GTM_EXTERN_C_BEGIN
#define GTM_EXTERN_C_END
#endif
#endif
// Give ourselves a consistent way of exporting things if we have visibility
// set to hidden.
#if !defined (GTM_EXPORT)
#define GTM_EXPORT __attribute__((visibility("default")))
#endif
// Give ourselves a consistent way of declaring something as unused. This
// doesn't use __unused because that is only supported in gcc 4.2 and greater.
#if !defined (GTM_UNUSED)
#define GTM_UNUSED(x) ((void)(x))
#endif
// _GTMDevLog & _GTMDevAssert
//
// _GTMDevLog & _GTMDevAssert are meant to be a very lightweight shell for
// developer level errors. This implementation simply macros to NSLog/NSAssert.
// It is not intended to be a general logging/reporting system.
//
// Please see http://code.google.com/p/google-toolbox-for-mac/wiki/DevLogNAssert
// for a little more background on the usage of these macros.
//
// _GTMDevLog log some error/problem in debug builds
// _GTMDevAssert assert if conditon isn't met w/in a method/function
// in all builds.
//
// To replace this system, just provide different macro definitions in your
// prefix header. Remember, any implementation you provide *must* be thread
// safe since this could be called by anything in what ever situtation it has
// been placed in.
//
// We only define the simple macros if nothing else has defined this.
#ifndef _GTMDevLog
#ifdef DEBUG
#define _GTMDevLog(...) NSLog(__VA_ARGS__)
#else
#define _GTMDevLog(...) do { } while (0)
#endif
#endif // _GTMDevLog
#ifndef _GTMDevAssert
// we directly invoke the NSAssert handler so we can pass on the varargs
// (NSAssert doesn't have a macro we can use that takes varargs)
#if !defined(NS_BLOCK_ASSERTIONS)
#define _GTMDevAssert(condition, ...) \
do { \
if (!(condition)) { \
[[NSAssertionHandler currentHandler] \
handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ \
description:__VA_ARGS__]; \
} \
} while(0)
#else // !defined(NS_BLOCK_ASSERTIONS)
#define _GTMDevAssert(condition, ...) do { } while (0)
#endif // !defined(NS_BLOCK_ASSERTIONS)
#endif // _GTMDevAssert
// _GTMCompileAssert
// _GTMCompileAssert is an assert that is meant to fire at compile time if you
// want to check things at compile instead of runtime. For example if you
// want to check that a wchar is 4 bytes instead of 2 you would use
// _GTMCompileAssert(sizeof(wchar_t) == 4, wchar_t_is_4_bytes_on_OS_X)
// Note that the second "arg" is not in quotes, and must be a valid processor
// symbol in it's own right (no spaces, punctuation etc).
// Wrapping this in an #ifndef allows external groups to define their own
// compile time assert scheme.
#ifndef _GTMCompileAssert
// We got this technique from here:
// http://unixjunkie.blogspot.com/2007/10/better-compile-time-asserts_29.html
#define _GTMCompileAssertSymbolInner(line, msg) _GTMCOMPILEASSERT ## line ## __ ## msg
#define _GTMCompileAssertSymbol(line, msg) _GTMCompileAssertSymbolInner(line, msg)
#define _GTMCompileAssert(test, msg) \
typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ]
#endif // _GTMCompileAssert
// ----------------------------------------------------------------------------
// CPP symbols defined based on the project settings so the GTM code has
// simple things to test against w/o scattering the knowledge of project
// setting through all the code.
// ----------------------------------------------------------------------------
// Provide a single constant CPP symbol that all of GTM uses for ifdefing
// iPhone code.
#if TARGET_OS_IPHONE // iPhone SDK
// For iPhone specific stuff
#define GTM_IPHONE_SDK 1
#if TARGET_IPHONE_SIMULATOR
#define GTM_IPHONE_SIMULATOR 1
#else
#define GTM_IPHONE_DEVICE 1
#endif // TARGET_IPHONE_SIMULATOR
// By default, GTM has provided it's own unittesting support, define this
// to use the support provided by Xcode, especially for the Xcode4 support
// for unittesting.
#ifndef GTM_IPHONE_USE_SENTEST
#define GTM_IPHONE_USE_SENTEST 0
#endif
#else
// For MacOS specific stuff
#define GTM_MACOS_SDK 1
#endif
// Some of our own availability macros
#if GTM_MACOS_SDK
#define GTM_AVAILABLE_ONLY_ON_IPHONE UNAVAILABLE_ATTRIBUTE
#define GTM_AVAILABLE_ONLY_ON_MACOS
#else
#define GTM_AVAILABLE_ONLY_ON_IPHONE
#define GTM_AVAILABLE_ONLY_ON_MACOS UNAVAILABLE_ATTRIBUTE
#endif
// Provide a symbol to include/exclude extra code for GC support. (This mainly
// just controls the inclusion of finalize methods).
#ifndef GTM_SUPPORT_GC
#if GTM_IPHONE_SDK
// iPhone never needs GC
#define GTM_SUPPORT_GC 0
#else
// We can't find a symbol to tell if GC is supported/required, so best we
// do on Mac targets is include it if we're on 10.5 or later.
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
#define GTM_SUPPORT_GC 0
#else
#define GTM_SUPPORT_GC 1
#endif
#endif
#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_5)
// 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_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
// Some support for advanced clang static analysis functionality
// See http://clang-analyzer.llvm.org/annotations.html
#ifndef __has_feature // Optional.
#define __has_feature(x) 0 // Compatibility with non-clang compilers.
#endif
#ifndef NS_RETURNS_RETAINED
#if __has_feature(attribute_ns_returns_retained)
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#else
#define NS_RETURNS_RETAINED
#endif
#endif
#ifndef NS_RETURNS_NOT_RETAINED
#if __has_feature(attribute_ns_returns_not_retained)
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
#else
#define NS_RETURNS_NOT_RETAINED
#endif
#endif
#ifndef CF_RETURNS_RETAINED
#if __has_feature(attribute_cf_returns_retained)
#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
#else
#define CF_RETURNS_RETAINED
#endif
#endif
#ifndef CF_RETURNS_NOT_RETAINED
#if __has_feature(attribute_cf_returns_not_retained)
#define CF_RETURNS_NOT_RETAINED __attribute__((cf_returns_not_retained))
#else
#define CF_RETURNS_NOT_RETAINED
#endif
#endif
#ifndef NS_CONSUMED
#if __has_feature(attribute_ns_consumed)
#define NS_CONSUMED __attribute__((ns_consumed))
#else
#define NS_CONSUMED
#endif
#endif
#ifndef CF_CONSUMED
#if __has_feature(attribute_cf_consumed)
#define CF_CONSUMED __attribute__((cf_consumed))
#else
#define CF_CONSUMED
#endif
#endif
#ifndef NS_CONSUMES_SELF
#if __has_feature(attribute_ns_consumes_self)
#define NS_CONSUMES_SELF __attribute__((ns_consumes_self))
#else
#define NS_CONSUMES_SELF
#endif
#endif
// Defined on 10.6 and above.
#ifndef NS_FORMAT_ARGUMENT
#define NS_FORMAT_ARGUMENT(A)
#endif
// Defined on 10.6 and above.
#ifndef NS_FORMAT_FUNCTION
#define NS_FORMAT_FUNCTION(F,A)
#endif
// Defined on 10.6 and above.
#ifndef CF_FORMAT_ARGUMENT
#define CF_FORMAT_ARGUMENT(A)
#endif
// Defined on 10.6 and above.
#ifndef CF_FORMAT_FUNCTION
#define CF_FORMAT_FUNCTION(F,A)
#endif
#ifndef GTM_NONNULL
#define GTM_NONNULL(x) __attribute__((nonnull(x)))
#endif
// Invalidates the initializer from which it's called.
#ifndef GTMInvalidateInitializer
#if __has_feature(objc_arc)
#define GTMInvalidateInitializer() \
do { \
_GTMDevAssert(NO, @"Invalid initializer."); \
return nil; \
} while (0)
#else
#define GTMInvalidateInitializer() \
do { \
[self release]; \
_GTMDevAssert(NO, @"Invalid initializer."); \
return nil; \
} while (0)
#endif
#endif
#ifdef __OBJC__
// Declared here so that it can easily be used for logging tracking if
// necessary. See GTMUnitTestDevLog.h for details.
@class NSString;
GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2);
// Macro to allow you to create NSStrings out of other macros.
// #define FOO foo
// NSString *fooString = GTM_NSSTRINGIFY(FOO);
#if !defined (GTM_NSSTRINGIFY)
#define GTM_NSSTRINGIFY_INNER(x) @#x
#define GTM_NSSTRINGIFY(x) GTM_NSSTRINGIFY_INNER(x)
#endif
// Macro to allow fast enumeration when building for 10.5 or later, and
// reliance on NSEnumerator for 10.4. Remember, NSDictionary w/ FastEnumeration
// does keys, so pick the right thing, nothing is done on the FastEnumeration
// side to be sure you're getting what you wanted.
#ifndef GTM_FOREACH_OBJECT
#if TARGET_OS_IPHONE || !(MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
#define GTM_FOREACH_ENUMEREE(element, enumeration) \
for (element in enumeration)
#define GTM_FOREACH_OBJECT(element, collection) \
for (element in collection)
#define GTM_FOREACH_KEY(element, collection) \
for (element in collection)
#else
#define GTM_FOREACH_ENUMEREE(element, enumeration) \
for (NSEnumerator *_ ## element ## _enum = enumeration; \
(element = [_ ## element ## _enum nextObject]) != nil; )
#define GTM_FOREACH_OBJECT(element, collection) \
GTM_FOREACH_ENUMEREE(element, [collection objectEnumerator])
#define GTM_FOREACH_KEY(element, collection) \
GTM_FOREACH_ENUMEREE(element, [collection keyEnumerator])
#endif
#endif
// ============================================================================
// To simplify support for both Leopard and Snow Leopard we declare
// the Snow Leopard protocols that we need here.
#if !defined(GTM_10_6_PROTOCOLS_DEFINED) && !(MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
#define GTM_10_6_PROTOCOLS_DEFINED 1
@protocol NSConnectionDelegate
@end
@protocol NSAnimationDelegate
@end
@protocol NSImageDelegate
@end
@protocol NSTabViewDelegate
@end
#endif // !defined(GTM_10_6_PROTOCOLS_DEFINED) && !(MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
// GTM_SEL_STRING is for specifying selector (usually property) names to KVC
// or KVO methods.
// In debug it will generate warnings for undeclared selectors if
// -Wunknown-selector is turned on.
// In release it will have no runtime overhead.
#ifndef GTM_SEL_STRING
#ifdef DEBUG
#define GTM_SEL_STRING(selName) NSStringFromSelector(@selector(selName))
#else
#define GTM_SEL_STRING(selName) @#selName
#endif // DEBUG
#endif // GTM_SEL_STRING
#endif // __OBJC__

View File

@ -0,0 +1,72 @@
//
// GTMGarbageCollection.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 <Foundation/Foundation.h>
#import "GTMDefines.h"
// This allows us to easily move our code from GC to non GC.
// They are no-ops unless we are require Leopard or above.
// See
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/index.html
// and
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcCoreFoundation.html#//apple_ref/doc/uid/TP40006687-SW1
// for details.
#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) && !GTM_IPHONE_SDK
// General use would be to call this through GTMCFAutorelease
// but there may be a reason the you want to make something collectable
// but not autoreleased, especially in pure GC code where you don't
// want to bother with the nop autorelease. Done as a define instead of an
// inline so that tools like Clang's scan-build don't report code as leaking.
#define GTMNSMakeCollectable(cf) ((id)NSMakeCollectable(cf))
// GTMNSMakeUncollectable is for global maps, etc. that we don't
// want released ever. You should still retain these in non-gc code.
GTM_INLINE void GTMNSMakeUncollectable(id object) {
[[NSGarbageCollector defaultCollector] disableCollectorForPointer:object];
}
// Hopefully no code really needs this, but GTMIsGarbageCollectionEnabled is
// a common way to check at runtime if GC is on.
// There are some places where GC doesn't work w/ things w/in Apple's
// frameworks, so this is here so GTM unittests and detect it, and not run
// individual tests to work around bugs in Apple's frameworks.
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
return ([NSGarbageCollector defaultCollector] != nil);
}
#else
#define GTMNSMakeCollectable(cf) ((id)(cf))
GTM_INLINE void GTMNSMakeUncollectable(id object) {
}
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
return NO;
}
#endif
// GTMCFAutorelease makes a CF object collectable in GC mode, or adds it
// to the autorelease pool in non-GC mode. Either way it is taken care
// of. Done as a define instead of an inline so that tools like Clang's
// scan-build don't report code as leaking.
#define GTMCFAutorelease(cf) ([GTMNSMakeCollectable(cf) autorelease])

View File

@ -0,0 +1,187 @@
/* Copyright (c) 2011 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.
*/
//
// GTMHTTPFetchHistory.h
//
//
// Users of the GTMHTTPFetcher class may optionally create and set a fetch
// history object. The fetch history provides "memory" between subsequent
// fetches, including:
//
// - For fetch responses with Etag headers, the fetch history
// remembers the response headers. Future fetcher requests to the same URL
// will be given an "If-None-Match" header, telling the server to return
// a 304 Not Modified status if the response is unchanged, reducing the
// server load and network traffic.
//
// - Optionally, the fetch history can cache the ETagged data that was returned
// in the responses that contained Etag headers. If a later fetch
// results in a 304 status, the fetcher will return the cached ETagged data
// to the client along with a 200 status, hiding the 304.
//
// - The fetch history can track cookies.
//
#pragma once
#import <Foundation/Foundation.h>
#import "GTMHTTPFetcher.h"
#undef _EXTERN
#undef _INITIALIZE_AS
#ifdef GTMHTTPFETCHHISTORY_DEFINE_GLOBALS
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
#if defined(__cplusplus)
#define _EXTERN extern "C"
#else
#define _EXTERN extern
#endif
#define _INITIALIZE_AS(x)
#endif
// default data cache size for when we're caching responses to handle "not
// modified" errors for the client
#if GTM_IPHONE
// iPhone: up to 1MB memory
_EXTERN const NSUInteger kGTMDefaultETaggedDataCacheMemoryCapacity _INITIALIZE_AS(1*1024*1024);
#else
// Mac OS X: up to 15MB memory
_EXTERN const NSUInteger kGTMDefaultETaggedDataCacheMemoryCapacity _INITIALIZE_AS(15*1024*1024);
#endif
// forward declarations
@class GTMURLCache;
@class GTMCookieStorage;
@interface GTMHTTPFetchHistory : NSObject <GTMHTTPFetchHistoryProtocol> {
@private
GTMURLCache *etaggedDataCache_;
BOOL shouldRememberETags_;
BOOL shouldCacheETaggedData_; // if NO, then only headers are cached
GTMCookieStorage *cookieStorage_;
}
// With caching enabled, previously-cached data will be returned instead of
// 304 Not Modified responses when repeating a fetch of an URL that previously
// included an ETag header in its response
@property (assign) BOOL shouldRememberETags; // default: NO
@property (assign) BOOL shouldCacheETaggedData; // default: NO
// the default ETag data cache capacity is kGTMDefaultETaggedDataCacheMemoryCapacity
@property (assign) NSUInteger memoryCapacity;
@property (retain) GTMCookieStorage *cookieStorage;
- (id)initWithMemoryCapacity:(NSUInteger)totalBytes
shouldCacheETaggedData:(BOOL)shouldCacheETaggedData;
- (void)updateRequest:(NSMutableURLRequest *)request isHTTPGet:(BOOL)isHTTPGet;
- (void)clearETaggedDataCache;
- (void)clearHistory;
- (void)removeAllCookies;
@end
// GTMURLCache and GTMCachedURLResponse have interfaces similar to their
// NSURLCache counterparts, in hopes that someday the NSURLCache versions
// can be used. But in 10.5.8, those are not reliable enough except when
// used with +setSharedURLCache. Our goal here is just to cache
// responses for handling If-None-Match requests that return
// "Not Modified" responses, not for replacing the general URL
// caches.
@interface GTMCachedURLResponse : NSObject {
@private
NSURLResponse *response_;
NSData *data_;
NSDate *useDate_; // date this response was last saved or used
NSDate *reservationDate_; // date this response's ETag was used
}
@property (readonly) NSURLResponse* response;
@property (readonly) NSData* data;
// date the response was saved or last accessed
@property (retain) NSDate *useDate;
// date the response's ETag header was last used for a fetch request
@property (retain) NSDate *reservationDate;
- (id)initWithResponse:(NSURLResponse *)response data:(NSData *)data;
@end
@interface GTMURLCache : NSObject {
NSMutableDictionary *responses_; // maps request URL to GTMCachedURLResponse
NSUInteger memoryCapacity_; // capacity of NSDatas in the responses
NSUInteger totalDataSize_; // sum of sizes of NSDatas of all responses
NSTimeInterval reservationInterval_; // reservation expiration interval
}
@property (assign) NSUInteger memoryCapacity;
- (id)initWithMemoryCapacity:(NSUInteger)totalBytes;
- (GTMCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
- (void)storeCachedResponse:(GTMCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;
- (void)removeCachedResponseForRequest:(NSURLRequest *)request;
- (void)removeAllCachedResponses;
// for unit testing
- (void)setReservationInterval:(NSTimeInterval)secs;
- (NSDictionary *)responses;
- (NSUInteger)totalDataSize;
@end
@interface GTMCookieStorage : NSObject <GTMCookieStorageProtocol> {
@private
// The cookie storage object manages an array holding cookies, but the array
// is allocated externally (it may be in a fetcher object or the static
// fetcher cookie array.) See the fetcher's setCookieStorageMethod:
// for allocation of this object and assignment of its cookies array.
NSMutableArray *cookies_;
}
// add all NSHTTPCookies in the supplied array 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;
// retrieve all cookies appropriate for the given URL, considering
// domain, path, cookie name, expiration, security setting.
// Side effect: removes expired cookies from the storage array
- (NSArray *)cookiesForURL:(NSURL *)theURL;
// return a cookie 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 stored cookies should
// be valid (non-nil name, domains, paths)
- (NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie;
// remove any expired cookies, excluding cookies with nil expirations
- (void)removeExpiredCookies;
- (void)removeAllCookies;
@end

View File

@ -0,0 +1,590 @@
/* Copyright (c) 2010 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.
*/
//
// GTMHTTPFetchHistory.m
//
#define GTMHTTPFETCHHISTORY_DEFINE_GLOBALS 1
#import "GTMHTTPFetchHistory.h"
const NSTimeInterval kCachedURLReservationInterval = 60.0; // 1 minute
static NSString* const kGTMIfNoneMatchHeader = @"If-None-Match";
static NSString* const kGTMETagHeader = @"Etag";
@implementation GTMCookieStorage
- (id)init {
self = [super init];
if (self != nil) {
cookies_ = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
[cookies_ release];
[super dealloc];
}
// add all cookies in the new cookie array to the storage,
// replacing stored cookies as appropriate
//
// Side effect: removes expired cookies from the storage array
- (void)setCookies:(NSArray *)newCookies {
@synchronized(cookies_) {
[self removeExpiredCookies];
for (NSHTTPCookie *newCookie in newCookies) {
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];
if (oldCookie) {
[cookies_ removeObjectIdenticalTo:oldCookie];
}
// make sure the cookie hasn't already expired
NSDate *expiresDate = [newCookie expiresDate];
if ((!expiresDate) || [expiresDate timeIntervalSinceNow] > 0) {
[cookies_ addObject:newCookie];
}
} else {
NSAssert1(NO, @"Cookie incomplete: %@", newCookie);
}
}
}
}
- (void)deleteCookie:(NSHTTPCookie *)cookie {
@synchronized(cookies_) {
NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie];
if (foundCookie) {
[cookies_ removeObjectIdenticalTo:foundCookie];
}
}
}
// 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 {
NSMutableArray *foundCookies = nil;
@synchronized(cookies_) {
[self removeExpiredCookies];
// 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] lowercaseString];
NSString *path = [theURL path];
NSString *scheme = [theURL scheme];
NSString *domain = nil;
BOOL isLocalhostRetrieval = NO;
if ([host isEqual:@"localhost"]) {
isLocalhostRetrieval = YES;
} else {
if (host) {
domain = [@"." stringByAppendingString:host];
}
}
NSUInteger numberOfCookies = [cookies_ count];
for (NSUInteger idx = 0; idx < numberOfCookies; idx++) {
NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
NSString *cookieDomain = [[storedCookie domain] lowercaseString];
NSString *cookiePath = [storedCookie path];
BOOL cookieIsSecure = [storedCookie isSecure];
BOOL isDomainOK;
if (isLocalhostRetrieval) {
// prior to 10.5.6, the domain stored into NSHTTPCookies for localhost
// is "localhost.local"
isDomainOK = [cookieDomain isEqual:@"localhost"]
|| [cookieDomain isEqual:@"localhost.local"];
} else {
isDomainOK = [domain hasSuffix:cookieDomain];
}
BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath];
BOOL isSecureOK = (!cookieIsSecure) || [scheme isEqual:@"https"];
if (isDomainOK && isPathOK && isSecureOK) {
if (foundCookies == nil) {
foundCookies = [NSMutableArray arrayWithCapacity:1];
}
[foundCookies addObject:storedCookie];
}
}
}
return foundCookies;
}
// 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 the storage array should
// be valid (non-nil name, domains, paths)
//
// note: this should only be called from inside a @synchronized(cookies_) block
- (NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie {
NSUInteger numberOfCookies = [cookies_ count];
NSString *name = [cookie name];
NSString *domain = [cookie domain];
NSString *path = [cookie path];
NSAssert3(name && domain && path, @"Invalid cookie (name:%@ domain:%@ path:%@)",
name, domain, path);
for (NSUInteger idx = 0; idx < numberOfCookies; idx++) {
NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
if ([[storedCookie name] isEqual:name]
&& [[storedCookie domain] isEqual:domain]
&& [[storedCookie path] isEqual:path]) {
return storedCookie;
}
}
return nil;
}
// internal routine to remove any expired cookies from the array, excluding
// cookies with nil expirations
//
// note: this should only be called from inside a @synchronized(cookies_) block
- (void)removeExpiredCookies {
// count backwards since we're deleting items from the array
for (NSInteger idx = [cookies_ count] - 1; idx >= 0; idx--) {
NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
NSDate *expiresDate = [storedCookie expiresDate];
if (expiresDate && [expiresDate timeIntervalSinceNow] < 0) {
[cookies_ removeObjectAtIndex:idx];
}
}
}
- (void)removeAllCookies {
@synchronized(cookies_) {
[cookies_ removeAllObjects];
}
}
@end
//
// GTMCachedURLResponse
//
@implementation GTMCachedURLResponse
@synthesize response = response_;
@synthesize data = data_;
@synthesize reservationDate = reservationDate_;
@synthesize useDate = useDate_;
- (id)initWithResponse:(NSURLResponse *)response data:(NSData *)data {
self = [super init];
if (self != nil) {
response_ = [response retain];
data_ = [data retain];
useDate_ = [[NSDate alloc] init];
}
return self;
}
- (void)dealloc {
[response_ release];
[data_ release];
[useDate_ release];
[reservationDate_ release];
[super dealloc];
}
- (NSString *)description {
NSString *reservationStr = reservationDate_ ?
[NSString stringWithFormat:@" resDate:%@", reservationDate_] : @"";
return [NSString stringWithFormat:@"%@ %p: {bytes:%@ useDate:%@%@}",
[self class], self,
data_ ? [NSNumber numberWithInt:(int)[data_ length]] : nil,
useDate_,
reservationStr];
}
- (NSComparisonResult)compareUseDate:(GTMCachedURLResponse *)other {
return [useDate_ compare:[other useDate]];
}
@end
//
// GTMURLCache
//
@implementation GTMURLCache
@dynamic memoryCapacity;
- (id)init {
return [self initWithMemoryCapacity:kGTMDefaultETaggedDataCacheMemoryCapacity];
}
- (id)initWithMemoryCapacity:(NSUInteger)totalBytes {
self = [super init];
if (self != nil) {
memoryCapacity_ = totalBytes;
responses_ = [[NSMutableDictionary alloc] initWithCapacity:5];
reservationInterval_ = kCachedURLReservationInterval;
}
return self;
}
- (void)dealloc {
[responses_ release];
[super dealloc];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %p: {responses:%@}",
[self class], self, [responses_ allValues]];
}
// setters/getters
- (void)pruneCacheResponses {
// internal routine to remove the least-recently-used responses when the
// cache has grown too large
if (memoryCapacity_ >= totalDataSize_) return;
// sort keys by date
SEL sel = @selector(compareUseDate:);
NSArray *sortedKeys = [responses_ keysSortedByValueUsingSelector:sel];
// the least-recently-used keys are at the beginning of the sorted array;
// remove those (except ones still reserved) until the total data size is
// reduced sufficiently
for (NSURL *key in sortedKeys) {
GTMCachedURLResponse *response = [responses_ objectForKey:key];
NSDate *resDate = [response reservationDate];
BOOL isResponseReserved = (resDate != nil)
&& ([resDate timeIntervalSinceNow] > -reservationInterval_);
if (!isResponseReserved) {
// we can remove this response from the cache
NSUInteger storedSize = [[response data] length];
totalDataSize_ -= storedSize;
[responses_ removeObjectForKey:key];
}
// if we've removed enough response data, then we're done
if (memoryCapacity_ >= totalDataSize_) break;
}
}
- (void)storeCachedResponse:(GTMCachedURLResponse *)cachedResponse
forRequest:(NSURLRequest *)request {
@synchronized(self) {
// remove any previous entry for this request
[self removeCachedResponseForRequest:request];
// cache this one only if it's not bigger than our cache
NSUInteger storedSize = [[cachedResponse data] length];
if (storedSize < memoryCapacity_) {
NSURL *key = [request URL];
[responses_ setObject:cachedResponse forKey:key];
totalDataSize_ += storedSize;
[self pruneCacheResponses];
}
}
}
- (GTMCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
GTMCachedURLResponse *response;
@synchronized(self) {
NSURL *key = [request URL];
response = [[[responses_ objectForKey:key] retain] autorelease];
// touch the date to indicate this was recently retrieved
[response setUseDate:[NSDate date]];
}
return response;
}
- (void)removeCachedResponseForRequest:(NSURLRequest *)request {
@synchronized(self) {
NSURL *key = [request URL];
totalDataSize_ -= [[[responses_ objectForKey:key] data] length];
[responses_ removeObjectForKey:key];
}
}
- (void)removeAllCachedResponses {
@synchronized(self) {
[responses_ removeAllObjects];
totalDataSize_ = 0;
}
}
- (NSUInteger)memoryCapacity {
return memoryCapacity_;
}
- (void)setMemoryCapacity:(NSUInteger)totalBytes {
@synchronized(self) {
BOOL didShrink = (totalBytes < memoryCapacity_);
memoryCapacity_ = totalBytes;
if (didShrink) {
[self pruneCacheResponses];
}
}
}
// methods for unit testing
- (void)setReservationInterval:(NSTimeInterval)secs {
reservationInterval_ = secs;
}
- (NSDictionary *)responses {
return responses_;
}
- (NSUInteger)totalDataSize {
return totalDataSize_;
}
@end
//
// GTMHTTPFetchHistory
//
@interface GTMHTTPFetchHistory ()
- (NSString *)cachedETagForRequest:(NSURLRequest *)request;
- (void)removeCachedDataForRequest:(NSURLRequest *)request;
@end
@implementation GTMHTTPFetchHistory
@synthesize cookieStorage = cookieStorage_;
@dynamic shouldRememberETags;
@dynamic shouldCacheETaggedData;
@dynamic memoryCapacity;
- (id)init {
return [self initWithMemoryCapacity:kGTMDefaultETaggedDataCacheMemoryCapacity
shouldCacheETaggedData:NO];
}
- (id)initWithMemoryCapacity:(NSUInteger)totalBytes
shouldCacheETaggedData:(BOOL)shouldCacheETaggedData {
self = [super init];
if (self != nil) {
etaggedDataCache_ = [[GTMURLCache alloc] initWithMemoryCapacity:totalBytes];
shouldRememberETags_ = shouldCacheETaggedData;
shouldCacheETaggedData_ = shouldCacheETaggedData;
cookieStorage_ = [[GTMCookieStorage alloc] init];
}
return self;
}
- (void)dealloc {
[etaggedDataCache_ release];
[cookieStorage_ release];
[super dealloc];
}
- (void)updateRequest:(NSMutableURLRequest *)request isHTTPGet:(BOOL)isHTTPGet {
if ([self shouldRememberETags]) {
// If this URL is in the history, and no ETag has been set, then
// set the ETag header field
// if we have a history, we're tracking across fetches, so we don't
// want to pull results from any other cache
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
if (isHTTPGet) {
// we'll only add an ETag if there's no ETag specified in the user's
// request
NSString *specifiedETag = [request valueForHTTPHeaderField:kGTMIfNoneMatchHeader];
if (specifiedETag == nil) {
// no ETag: extract the previous ETag for this request from the
// fetch history, and add it to the request
NSString *cachedETag = [self cachedETagForRequest:request];
if (cachedETag != nil) {
[request addValue:cachedETag forHTTPHeaderField:kGTMIfNoneMatchHeader];
}
} else {
// has an ETag: remove any stored response in the fetch history
// for this request, as the If-None-Match header could lead to
// a 304 Not Modified, and we want that error delivered to the
// user since they explicitly specified the ETag
[self removeCachedDataForRequest:request];
}
}
}
}
- (void)updateFetchHistoryWithRequest:(NSURLRequest *)request
response:(NSURLResponse *)response
downloadedData:(NSData *)downloadedData {
if (![self shouldRememberETags]) return;
if (![response respondsToSelector:@selector(allHeaderFields)]) return;
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != kGTMHTTPFetcherStatusNotModified) {
// save this ETag 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 etaggedDataCache is non-nil.)
NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
NSString* etag = [headers objectForKey:kGTMETagHeader];
if (etag != nil && statusCode < 300) {
// we want to cache responses for the headers, even if the client
// doesn't want the response body data caches
NSData *dataToStore = shouldCacheETaggedData_ ? downloadedData : nil;
GTMCachedURLResponse *cachedResponse;
cachedResponse = [[[GTMCachedURLResponse alloc] initWithResponse:response
data:dataToStore] autorelease];
[etaggedDataCache_ storeCachedResponse:cachedResponse
forRequest:request];
} else {
[etaggedDataCache_ removeCachedResponseForRequest:request];
}
}
}
- (NSString *)cachedETagForRequest:(NSURLRequest *)request {
GTMCachedURLResponse *cachedResponse;
cachedResponse = [etaggedDataCache_ cachedResponseForRequest:request];
NSURLResponse *response = [cachedResponse response];
NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
NSString *cachedETag = [headers objectForKey:kGTMETagHeader];
if (cachedETag) {
// since the request having an ETag implies this request is about
// to be fetched again, reserve the cached response to ensure that
// that it will be around at least until the fetch completes
//
// when the fetch completes, either the cached response will be replaced
// with a new response, or the cachedDataForRequest: method below will
// clear the reservation
[cachedResponse setReservationDate:[NSDate date]];
}
return cachedETag;
}
- (NSData *)cachedDataForRequest:(NSURLRequest *)request {
GTMCachedURLResponse *cachedResponse;
cachedResponse = [etaggedDataCache_ cachedResponseForRequest:request];
NSData *cachedData = [cachedResponse data];
// since the data for this cached request is being obtained from the cache,
// we can clear the reservation as the fetch has completed
[cachedResponse setReservationDate:nil];
return cachedData;
}
- (void)removeCachedDataForRequest:(NSURLRequest *)request {
[etaggedDataCache_ removeCachedResponseForRequest:request];
}
- (void)clearETaggedDataCache {
[etaggedDataCache_ removeAllCachedResponses];
}
- (void)clearHistory {
[self clearETaggedDataCache];
[cookieStorage_ removeAllCookies];
}
- (void)removeAllCookies {
[cookieStorage_ removeAllCookies];
}
- (BOOL)shouldRememberETags {
return shouldRememberETags_;
}
- (void)setShouldRememberETags:(BOOL)flag {
BOOL wasRemembering = shouldRememberETags_;
shouldRememberETags_ = flag;
if (wasRemembering && !flag) {
// free up the cache memory
[self clearETaggedDataCache];
}
}
- (BOOL)shouldCacheETaggedData {
return shouldCacheETaggedData_;
}
- (void)setShouldCacheETaggedData:(BOOL)flag {
BOOL wasCaching = shouldCacheETaggedData_;
shouldCacheETaggedData_ = flag;
if (flag) {
self.shouldRememberETags = YES;
}
if (wasCaching && !flag) {
// users expect turning off caching to free up the cache memory
[self clearETaggedDataCache];
}
}
- (NSUInteger)memoryCapacity {
return [etaggedDataCache_ memoryCapacity];
}
- (void)setMemoryCapacity:(NSUInteger)totalBytes {
[etaggedDataCache_ setMemoryCapacity:totalBytes];
}
@end

View File

@ -0,0 +1,704 @@
/* Copyright (c) 2011 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.
*/
//
// GTMHTTPFetcher.h
//
// 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, POST, or PUT
// - When you want the "standard" behavior for connections (redirection handling
// an so on)
// - When you want automatic retry on failures
// - When you want to avoid cookie collisions with Safari and other applications
// - When you are fetching resources with ETags and want to avoid the overhead
// of repeated fetches of unchanged data
// - When you need to set a credential for the http operation
//
// 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, retain it and have the delegate
// release the fetcher in the callbacks.
//
// Sample usage:
//
// NSURLRequest *request = [NSURLRequest requestWithURL:myURL];
// GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher fetcherWithRequest:request];
//
// // optional upload body data
// [myFetcher setPostData:[postString dataUsingEncoding:NSUTF8StringEncoding]];
//
// [myFetcher beginFetchWithDelegate:self
// didFinishSelector:@selector(myFetcher:finishedWithData:error:)];
//
// Upon fetch completion, the callback selector is invoked; it should have
// this signature (you can use any callback method name you want so long as
// the signature matches this):
//
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)retrievedData error:(NSError *)error;
//
// The block callback version looks like:
//
// [myFetcher beginFetchWithCompletionHandler:^(NSData *retrievedData, NSError *error) {
// if (error != nil) {
// // status code or network error
// } else {
// // succeeded
// }
// }];
//
// NOTE: Fetches may retrieve data from the server even though the server
// returned an error. The failure selector is called when the server
// status is >= 300, with an NSError having domain
// kGTMHTTPFetcherStatusDomain and code set to the server status.
//
// Status codes are at <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>
//
//
// Downloading to disk:
//
// To have downloaded data saved directly to disk, specify either a path for the
// downloadPath property, or a file handle for the downloadFileHandle property.
// When downloading to disk, callbacks will be passed a nil for the NSData*
// arguments.
//
//
// HTTP methods and headers:
//
// Alternative HTTP methods, like PUT, and custom headers can be specified by
// creating the fetcher with an appropriate NSMutableURLRequest
//
//
// 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 the fetcher is created from a GTMHTTPFetcherService object
// then the cookie storage mechanism is set to use the cookie storage in the
// service object rather than the static storage.
//
//
// Fetching for periodic checks:
//
// The fetcher object tracks ETag headers from responses and
// provide an "If-None-Match" header. This allows the server to save
// bandwidth by providing a status message instead of repeated response
// data.
//
// To get this behavior, create the fetcher from an GTMHTTPFetcherService object
// and look for a fetch callback error with code 304
// (kGTMHTTPFetcherStatusNotModified) like this:
//
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
// if ([error code] == kGTMHTTPFetcherStatusNotModified) {
// // |data| is empty; use the data from the previous finishedWithData: for this URL
// } else {
// // handle other server status code
// }
// }
//
//
// Monitoring received data
//
// The optional received data selector can be set with setReceivedDataSelector:
// and should have the signature
//
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher receivedData:(NSData *)dataReceivedSoFar;
//
// The number bytes received so far is available as [fetcher downloadedLength].
// 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] (and 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 setRetryEnabled: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.0]; // in seconds; default is 60 seconds
// // for downloads, 600 for uploads
//
// 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>
#if defined(GTL_TARGET_NAMESPACE)
// we're using target namespace macros
#import "GTLDefines.h"
#elif defined(GDATA_TARGET_NAMESPACE)
#import "GDataDefines.h"
#else
#if TARGET_OS_IPHONE
#ifndef GTM_FOUNDATION_ONLY
#define GTM_FOUNDATION_ONLY 1
#endif
#ifndef GTM_IPHONE
#define GTM_IPHONE 1
#endif
#endif
#endif
#if TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 40000)
#define GTM_BACKGROUND_FETCHING 1
#endif
#undef _EXTERN
#undef _INITIALIZE_AS
#ifdef GTMHTTPFETCHER_DEFINE_GLOBALS
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
#if defined(__cplusplus)
#define _EXTERN extern "C"
#else
#define _EXTERN extern
#endif
#define _INITIALIZE_AS(x)
#endif
// notifications
//
// fetch started and stopped, and fetch retry delay started and stopped
_EXTERN NSString* const kGTMHTTPFetcherStartedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherStartedNotification");
_EXTERN NSString* const kGTMHTTPFetcherStoppedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherStoppedNotification");
_EXTERN NSString* const kGTMHTTPFetcherRetryDelayStartedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherRetryDelayStartedNotification");
_EXTERN NSString* const kGTMHTTPFetcherRetryDelayStoppedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherRetryDelayStoppedNotification");
// callback constants
_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"); // data returned with a kGTMHTTPFetcherStatusDomain error
enum {
kGTMHTTPFetcherErrorDownloadFailed = -1,
kGTMHTTPFetcherErrorAuthenticationChallengeFailed = -2,
kGTMHTTPFetcherErrorChunkUploadFailed = -3,
kGTMHTTPFetcherErrorFileHandleException = -4,
kGTMHTTPFetcherErrorBackgroundExpiration = -6,
// The code kGTMHTTPFetcherErrorAuthorizationFailed (-5) has been removed;
// look for status 401 instead.
kGTMHTTPFetcherStatusNotModified = 304,
kGTMHTTPFetcherStatusBadRequest = 400,
kGTMHTTPFetcherStatusUnauthorized = 401,
kGTMHTTPFetcherStatusForbidden = 403,
kGTMHTTPFetcherStatusPreconditionFailed = 412
};
// cookie storage methods
enum {
kGTMHTTPFetcherCookieStorageMethodStatic = 0,
kGTMHTTPFetcherCookieStorageMethodFetchHistory = 1,
kGTMHTTPFetcherCookieStorageMethodSystemDefault = 2,
kGTMHTTPFetcherCookieStorageMethodNone = 3
};
#ifdef __cplusplus
extern "C" {
#endif
void GTMAssertSelectorNilOrImplementedWithArgs(id obj, SEL sel, ...);
// Utility functions for applications self-identifying to servers via a
// user-agent header
// Make a proper app name without whitespace from the given string, removing
// whitespace and other characters that may be special parsed marks of
// the full user-agent string.
NSString *GTMCleanedUserAgentString(NSString *str);
// Make an identifier like "MacOSX/10.7.1" or "iPod_Touch/4.1"
NSString *GTMSystemVersionString(void);
// Make a generic name and version for the current application, like
// com.example.MyApp/1.2.3 relying on the bundle identifier and the
// CFBundleShortVersionString or CFBundleVersion. If no bundle ID
// is available, the process name preceded by "proc_" is used.
NSString *GTMApplicationIdentifier(NSBundle *bundle);
#ifdef __cplusplus
} // extern "C"
#endif
@class GTMHTTPFetcher;
@protocol GTMCookieStorageProtocol <NSObject>
// This protocol allows us to call into the service without requiring
// GTMCookieStorage sources in this project
//
// The public interface for cookie handling is the GTMCookieStorage class,
// accessible from a fetcher service object's fetchHistory or from the fetcher's
// +staticCookieStorage method.
- (NSArray *)cookiesForURL:(NSURL *)theURL;
- (void)setCookies:(NSArray *)newCookies;
@end
@protocol GTMHTTPFetchHistoryProtocol <NSObject>
// This protocol allows us to call the fetch history object without requiring
// GTMHTTPFetchHistory sources in this project
- (void)updateRequest:(NSMutableURLRequest *)request isHTTPGet:(BOOL)isHTTPGet;
- (BOOL)shouldCacheETaggedData;
- (NSData *)cachedDataForRequest:(NSURLRequest *)request;
- (id <GTMCookieStorageProtocol>)cookieStorage;
- (void)updateFetchHistoryWithRequest:(NSURLRequest *)request
response:(NSURLResponse *)response
downloadedData:(NSData *)downloadedData;
- (void)removeCachedDataForRequest:(NSURLRequest *)request;
@end
@protocol GTMHTTPFetcherServiceProtocol <NSObject>
// This protocol allows us to call into the service without requiring
// GTMHTTPFetcherService sources in this project
- (BOOL)fetcherShouldBeginFetching:(GTMHTTPFetcher *)fetcher;
- (void)fetcherDidStop:(GTMHTTPFetcher *)fetcher;
- (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request;
- (BOOL)isDelayingFetcher:(GTMHTTPFetcher *)fetcher;
@end
@protocol GTMFetcherAuthorizationProtocol <NSObject>
@required
// This protocol allows us to call the authorizer without requiring its sources
// in this project
- (void)authorizeRequest:(NSMutableURLRequest *)request
delegate:(id)delegate
didFinishSelector:(SEL)sel;
- (void)stopAuthorization;
- (BOOL)isAuthorizingRequest:(NSURLRequest *)request;
- (BOOL)isAuthorizedRequest:(NSURLRequest *)request;
- (NSString *)userEmail;
@optional
@property (assign) id <GTMHTTPFetcherServiceProtocol> fetcherService; // WEAK
- (BOOL)primeForRefresh;
@end
// GTMHTTPFetcher objects are used for async retrieval of an http get or post
//
// See additional comments at the beginning of this file
@interface GTMHTTPFetcher : NSObject {
@protected
NSMutableURLRequest *request_;
NSURLConnection *connection_;
NSMutableData *downloadedData_;
NSString *downloadPath_;
NSString *temporaryDownloadPath_;
NSFileHandle *downloadFileHandle_;
unsigned long long downloadedLength_;
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_;
SEL finishedSel_; // should by implemented by delegate
SEL sentDataSel_; // optional, set with setSentDataSelector
SEL receivedDataSel_; // optional, set with setReceivedDataSelector
#if NS_BLOCKS_AVAILABLE
void (^completionBlock_)(NSData *, NSError *);
void (^receivedDataBlock_)(NSData *);
void (^sentDataBlock_)(NSInteger, NSInteger, NSInteger);
BOOL (^retryBlock_)(BOOL, NSError *);
#elif !__LP64__
// placeholders: for 32-bit builds, keep the size of the object's ivar section
// the same with and without blocks
id completionPlaceholder_;
id receivedDataPlaceholder_;
id sentDataPlaceholder_;
id retryPlaceholder_;
#endif
BOOL hasConnectionEnded_; // set if the connection need not be cancelled
BOOL isCancellingChallenge_; // set only when cancelling an auth challenge
BOOL isStopNotificationNeeded_; // set when start notification has been sent
BOOL shouldFetchInBackground_;
#if GTM_BACKGROUND_FETCHING
NSUInteger backgroundTaskIdentifer_; // UIBackgroundTaskIdentifier
#endif
id userData_; // retained, if set by caller
NSMutableDictionary *properties_; // more data retained for caller
NSArray *runLoopModes_; // optional, for 10.5 and later
id <GTMHTTPFetchHistoryProtocol> fetchHistory_; // if supplied by the caller, used for Last-Modified-Since checks and cookies
NSInteger cookieStorageMethod_; // constant from above
id <GTMCookieStorageProtocol> cookieStorage_;
id <GTMFetcherAuthorizationProtocol> authorizer_;
// the service object that created and monitors this fetcher, if any
id <GTMHTTPFetcherServiceProtocol> service_;
NSString *serviceHost_;
NSInteger servicePriority_;
NSThread *thread_;
BOOL isRetryEnabled_; // user wants auto-retry
SEL retrySel_; // optional; set with setRetrySelector
NSTimer *retryTimer_;
NSUInteger retryCount_;
NSTimeInterval maxRetryInterval_; // default 600 seconds
NSTimeInterval minRetryInterval_; // random between 1 and 2 seconds
NSTimeInterval retryFactor_; // default interval multiplier is 2
NSTimeInterval lastRetryInterval_;
BOOL hasAttemptedAuthRefresh_;
NSString *comment_; // comment for log
NSString *log_;
}
// Create a fetcher
//
// fetcherWithRequest 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 *)fetcherWithRequest:(NSURLRequest *)request;
// Convenience methods that make a request, like +fetcherWithRequest
+ (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL;
+ (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString;
// Designated initializer
- (id)initWithRequest:(NSURLRequest *)request;
// Fetcher request
//
// The underlying request is mutable and may be modified by the caller
@property (retain) NSMutableURLRequest *mutableRequest;
// Setting the credential is optional; it is used if the connection receives
// an authentication challenge
@property (retain) NSURLCredential *credential;
// Setting the proxy credential is optional; it is used if the connection
// receives an authentication challenge from a proxy
@property (retain) NSURLCredential *proxyCredential;
// If post data or stream is not set, then a GET retrieval method is assumed
@property (retain) NSData *postData;
@property (retain) NSInputStream *postStream;
// The default cookie storage method is kGTMHTTPFetcherCookieStorageMethodStatic
// without a fetch history set, and kGTMHTTPFetcherCookieStorageMethodFetchHistory
// with a fetch history set
//
// Applications needing control of cookies across a sequence of fetches should
// create fetchers from a GTMHTTPFetcherService object (which encapsulates
// fetch history) for a well-defined cookie store
@property (assign) NSInteger cookieStorageMethod;
+ (id <GTMCookieStorageProtocol>)staticCookieStorage;
// Object to add authorization to the request, if needed
@property (retain) id <GTMFetcherAuthorizationProtocol> authorizer;
// The service object that created and monitors this fetcher, if any
@property (retain) id <GTMHTTPFetcherServiceProtocol> service;
// The host, if any, used to classify this fetcher in the fetcher service
@property (copy) NSString *serviceHost;
// The priority, if any, used for starting fetchers in the fetcher service
//
// Lower values are higher priority; the default is 0, and values may
// be negative or positive. This priority affects only the start order of
// fetchers that are being delayed by a fetcher service.
@property (assign) NSInteger servicePriority;
// The thread used to run this fetcher in the fetcher service
@property (retain) NSThread *thread;
// The delegate is retained during the connection
@property (retain) id delegate;
// On iOS 4 and later, the fetch may optionally continue while the app is in the
// background until finished or stopped by OS expiration
//
// The default value is NO
//
// For Mac OS X, background fetches are always supported, and this property
// is ignored
@property (assign) BOOL shouldFetchInBackground;
// The delegate's optional sentData selector may be used to monitor upload
// progress. It should have a signature like:
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher
// didSendBytes:(NSInteger)bytesSent
// totalBytesSent:(NSInteger)totalBytesSent
// totalBytesExpectedToSend:(NSInteger)totalBytesExpectedToSend;
//
// +doesSupportSentDataCallback indicates if this delegate method is supported
+ (BOOL)doesSupportSentDataCallback;
@property (assign) SEL sentDataSelector;
// The delegate's optional receivedData selector may be used to monitor download
// progress. It should have a signature like:
// - (void)myFetcher:(GTMHTTPFetcher *)fetcher
// receivedData:(NSData *)dataReceivedSoFar;
//
// The dataReceived argument will be nil when downloading to a path or to a
// file handle.
//
// Applications should not use this method to accumulate the received data;
// the callback method or block supplied to the beginFetch call will have
// the complete NSData received.
@property (assign) SEL receivedDataSelector;
#if NS_BLOCKS_AVAILABLE
// The full interface to the block is provided rather than just a typedef for
// its parameter list in order to get more useful code completion in the Xcode
// editor
@property (copy) void (^sentDataBlock)(NSInteger bytesSent, NSInteger totalBytesSent, NSInteger bytesExpectedToSend);
// The dataReceived argument will be nil when downloading to a path or to
// a file handle
@property (copy) void (^receivedDataBlock)(NSData *dataReceivedSoFar);
#endif
// retrying; see comments at the top of the file. Calling
// setRetryEnabled(YES) resets the min and max retry intervals.
@property (assign, getter=isRetryEnabled) BOOL retryEnabled;
// Retry selector or block 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.
@property (assign) SEL retrySelector;
#if NS_BLOCKS_AVAILABLE
@property (copy) BOOL (^retryBlock)(BOOL suggestedWillRetry, NSError *error);
#endif
// 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.
@property (assign) NSTimeInterval maxRetryInterval;
// 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.
@property (assign) NSTimeInterval minRetryInterval;
// Multiplier used to increase the interval between retries, typically 2.0.
// Clients should not need to call this.
@property (assign) double retryFactor;
// Number of retries attempted
@property (readonly) NSUInteger retryCount;
// interval delay to precede next retry
@property (readonly) NSTimeInterval nextRetryInterval;
// Begin fetching the request
//
// The delegate can optionally implement the finished selectors or pass NULL
// for it.
//
// Returns YES if the fetch is initiated. The delegate is retained between
// the beginFetch call until after the finish callback.
//
// An error is passed to the callback for server statuses 300 or
// higher, with the status stored as the error object's code.
//
// finishedSEL has a signature like:
// - (void)fetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error;
//
// If the application has specified a downloadPath or downloadFileHandle
// for the fetcher, the data parameter passed to the callback will be nil.
- (BOOL)beginFetchWithDelegate:(id)delegate
didFinishSelector:(SEL)finishedSEL;
#if NS_BLOCKS_AVAILABLE
- (BOOL)beginFetchWithCompletionHandler:(void (^)(NSData *data, NSError *error))handler;
#endif
// 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
@property (readonly) NSInteger statusCode;
// Return the http headers from the response
@property (retain, readonly) NSDictionary *responseHeaders;
// The response, once it's been received
@property (retain) NSURLResponse *response;
// Bytes downloaded so far
@property (readonly) unsigned long long downloadedLength;
// Buffer of currently-downloaded data
@property (readonly, retain) NSData *downloadedData;
// Path in which to non-atomically create a file for storing the downloaded data
//
// The path must be set before fetching begins. The download file handle
// will be created for the path, and can be used to monitor progress. If a file
// already exists at the path, it will be overwritten.
@property (copy) NSString *downloadPath;
// If downloadFileHandle is set, data received is immediately appended to
// the file handle rather than being accumulated in the downloadedData property
//
// The file handle supplied must allow writing and support seekToFileOffset:,
// and must be set before fetching begins. Setting a download path will
// override the file handle property.
@property (retain) NSFileHandle *downloadFileHandle;
// The optional fetchHistory object is used for a sequence of fetchers to
// remember ETags, cache ETagged data, and store cookies. Typically, this
// is set by a GTMFetcherService object when it creates a fetcher.
//
// Side effect: setting fetch history implicitly calls setCookieStorageMethod:
@property (retain) id <GTMHTTPFetchHistoryProtocol> fetchHistory;
// userData is retained for the convenience of the caller
@property (retain) id userData;
// Stored property values are retained for the convenience of the caller
@property (copy) NSMutableDictionary *properties;
- (void)setProperty:(id)obj forKey:(NSString *)key; // pass nil obj to remove property
- (id)propertyForKey:(NSString *)key;
- (void)addPropertiesFromDictionary:(NSDictionary *)dict;
// Comments are useful for logging
@property (copy) NSString *comment;
- (void)setCommentWithFormat:(id)format, ...;
// Log of request and response, if logging is enabled
@property (copy) NSString *log;
// Using the fetcher while a modal dialog is displayed requires setting the
// run-loop modes to include NSModalPanelRunLoopMode
@property (retain) NSArray *runLoopModes;
// 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;
// Spin the run loop, discarding events, until the fetch has completed
//
// This is only for use in testing or in tools without a user interface.
//
// Synchronous fetches should never be done by shipping apps; they are
// sufficient reason for rejection from the app store.
- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds;
#if STRIP_GTM_FETCH_LOGGING
// if logging is stripped, provide a stub for the main method
// for controlling logging
+ (void)setLoggingEnabled:(BOOL)flag;
#endif // STRIP_GTM_FETCH_LOGGING
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
/* Copyright (c) 2010 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 "GTMHTTPFetcher.h"
// GTM HTTP Logging
//
// All traffic using GTMHTTPFetcher can be easily logged. Call
//
// [GTMHTTPFetcher setLoggingEnabled: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.
//
// In the iPhone simulator, the default logs location is the user's home
// directory in ~/Library/Application Support. On the iPhone device, the
// default logs location is the application's documents directory on the device.
//
// Tip: use the Finder's "Sort By Date" to find the most recent logs.
//
// 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.
//
// Each fetcher may be given a comment to be inserted as a label in the logs,
// such as
// [fetcher setCommentWithFormat:@"retrieve item %@", itemName];
//
// Projects may define STRIP_GTM_FETCH_LOGGING to remove logging code.
#if !STRIP_GTM_FETCH_LOGGING
@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)setLoggingEnabled:(BOOL)flag;
+ (BOOL)isLoggingEnabled;
// client apps can turn off logging to a file if they want to only check
// the fetcher's log property
+ (void)setLoggingToFileEnabled:(BOOL)flag;
+ (BOOL)isLoggingToFileEnabled;
// 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;
// internal; called by fetcher
- (void)logFetchWithError:(NSError *)error;
- (BOOL)logCapturePostStream;
@end
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,118 @@
/* Copyright (c) 2010 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.
*/
//
// GTMHTTPFetcherService.h
//
// The fetcher service class maintains a history to be used by a sequence
// of fetchers objects generated by the service.
//
// Fetchers that do not need to share a history may be generated independently,
// like
//
// GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher fetcherWithRequest:request];
//
// Fetchers that should share cookies or an ETagged data cache should be
// generated by a common GTMHTTPFetcherService instance, like
//
// GTMHTTPFetcherService *myFetcherService = [[GTMHTTPFetcherService alloc] init];
// GTMHTTPFetcher* myFirstFetcher = [myFetcherService fetcherWithRequest:request1];
// GTMHTTPFetcher* mySecondFetcher = [myFetcherService fetcherWithRequest:request2];
#import "GTMHTTPFetcher.h"
#import "GTMHTTPFetchHistory.h"
@interface GTMHTTPFetcherService : NSObject<GTMHTTPFetcherServiceProtocol> {
@private
NSMutableDictionary *delayedHosts_;
NSMutableDictionary *runningHosts_;
NSUInteger maxRunningFetchersPerHost_;
GTMHTTPFetchHistory *fetchHistory_;
NSArray *runLoopModes_;
NSString *userAgent_;
NSTimeInterval timeout_;
NSURLCredential *credential_; // username & password
NSURLCredential *proxyCredential_; // credential supplied to proxy servers
NSInteger cookieStorageMethod_;
BOOL shouldFetchInBackground_;
id <GTMFetcherAuthorizationProtocol> authorizer_;
}
// Create a fetcher
//
// These methods will return an autoreleased fetcher, but if
// the fetcher is successfully created, the connection will 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 monitor
// or cancel it.
- (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request;
- (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL;
- (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString;
- (id)fetcherWithRequest:(NSURLRequest *)request
fetcherClass:(Class)fetcherClass;
// Queues of delayed and running fetchers. Each dictionary contains arrays
// of fetchers, keyed by host
//
// A max value of 0 means no fetchers should be delayed.
//
// The default limit is 10 simultaneous fetchers targeting each host.
@property (assign) NSUInteger maxRunningFetchersPerHost;
@property (retain, readonly) NSDictionary *delayedHosts;
@property (retain, readonly) NSDictionary *runningHosts;
- (BOOL)isDelayingFetcher:(GTMHTTPFetcher *)fetcher;
- (NSUInteger)numberOfFetchers; // running + delayed fetchers
- (NSUInteger)numberOfRunningFetchers;
- (NSUInteger)numberOfDelayedFetchers;
- (void)stopAllFetchers;
// Properties to be applied to each fetcher;
// see GTMHTTPFetcher.h for descriptions
@property (copy) NSString *userAgent;
@property (assign) NSTimeInterval timeout;
@property (retain) NSArray *runLoopModes;
@property (retain) NSURLCredential *credential;
@property (retain) NSURLCredential *proxyCredential;
@property (assign) BOOL shouldFetchInBackground;
// Fetch history
@property (retain) GTMHTTPFetchHistory *fetchHistory;
@property (assign) NSInteger cookieStorageMethod;
@property (assign) BOOL shouldRememberETags; // default: NO
@property (assign) BOOL shouldCacheETaggedData; // default: NO
- (void)clearETaggedDataCache;
- (void)clearHistory;
@property (nonatomic, retain) id <GTMFetcherAuthorizationProtocol> authorizer;
// Spin the run loop, discarding events, until all running and delayed fetchers
// have completed
//
// This is only for use in testing or in tools without a user interface.
//
// Synchronous fetches should never be done by shipping apps; they are
// sufficient reason for rejection from the app store.
- (void)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds;
@end

View File

@ -0,0 +1,441 @@
/* Copyright (c) 2010 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.
*/
//
// GTMHTTPFetcherService.m
//
#import "GTMHTTPFetcherService.h"
@interface GTMHTTPFetcher (ServiceMethods)
- (BOOL)beginFetchMayDelay:(BOOL)mayDelay
mayAuthorize:(BOOL)mayAuthorize;
@end
@interface GTMHTTPFetcherService ()
@property (retain, readwrite) NSDictionary *delayedHosts;
@property (retain, readwrite) NSDictionary *runningHosts;
- (void)detachAuthorizer;
@end
@implementation GTMHTTPFetcherService
@synthesize maxRunningFetchersPerHost = maxRunningFetchersPerHost_,
userAgent = userAgent_,
timeout = timeout_,
runLoopModes = runLoopModes_,
credential = credential_,
proxyCredential = proxyCredential_,
cookieStorageMethod = cookieStorageMethod_,
shouldFetchInBackground = shouldFetchInBackground_,
fetchHistory = fetchHistory_;
- (id)init {
self = [super init];
if (self) {
fetchHistory_ = [[GTMHTTPFetchHistory alloc] init];
delayedHosts_ = [[NSMutableDictionary alloc] init];
runningHosts_ = [[NSMutableDictionary alloc] init];
cookieStorageMethod_ = kGTMHTTPFetcherCookieStorageMethodFetchHistory;
maxRunningFetchersPerHost_ = 10;
}
return self;
}
- (void)dealloc {
[self detachAuthorizer];
[delayedHosts_ release];
[runningHosts_ release];
[fetchHistory_ release];
[userAgent_ release];
[runLoopModes_ release];
[credential_ release];
[proxyCredential_ release];
[authorizer_ release];
[super dealloc];
}
#pragma mark Generate a new fetcher
- (id)fetcherWithRequest:(NSURLRequest *)request
fetcherClass:(Class)fetcherClass {
GTMHTTPFetcher *fetcher = [fetcherClass fetcherWithRequest:request];
fetcher.fetchHistory = self.fetchHistory;
fetcher.runLoopModes = self.runLoopModes;
fetcher.cookieStorageMethod = self.cookieStorageMethod;
fetcher.credential = self.credential;
fetcher.proxyCredential = self.proxyCredential;
fetcher.shouldFetchInBackground = self.shouldFetchInBackground;
fetcher.authorizer = self.authorizer;
fetcher.service = self;
NSString *userAgent = self.userAgent;
if ([userAgent length] > 0
&& [request valueForHTTPHeaderField:@"User-Agent"] == nil) {
[fetcher.mutableRequest setValue:userAgent
forHTTPHeaderField:@"User-Agent"];
}
NSTimeInterval timeout = self.timeout;
if (timeout > 0.0) {
[fetcher.mutableRequest setTimeoutInterval:timeout];
}
return fetcher;
}
- (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request {
return [self fetcherWithRequest:request
fetcherClass:[GTMHTTPFetcher class]];
}
- (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL {
return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
}
- (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString {
return [self fetcherWithURL:[NSURL URLWithString:requestURLString]];
}
#pragma mark Queue Management
- (void)addRunningFetcher:(GTMHTTPFetcher *)fetcher
forHost:(NSString *)host {
// Add to the array of running fetchers for this host, creating the array
// if needed
NSMutableArray *runningForHost = [runningHosts_ objectForKey:host];
if (runningForHost == nil) {
runningForHost = [NSMutableArray arrayWithObject:fetcher];
[runningHosts_ setObject:runningForHost forKey:host];
} else {
[runningForHost addObject:fetcher];
}
}
- (void)addDelayedFetcher:(GTMHTTPFetcher *)fetcher
forHost:(NSString *)host {
// Add to the array of delayed fetchers for this host, creating the array
// if needed
NSMutableArray *delayedForHost = [delayedHosts_ objectForKey:host];
if (delayedForHost == nil) {
delayedForHost = [NSMutableArray arrayWithObject:fetcher];
[delayedHosts_ setObject:delayedForHost forKey:host];
} else {
[delayedForHost addObject:fetcher];
}
}
- (BOOL)isDelayingFetcher:(GTMHTTPFetcher *)fetcher {
BOOL isDelayed;
@synchronized(self) {
NSString *host = [[[fetcher mutableRequest] URL] host];
NSArray *delayedForHost = [delayedHosts_ objectForKey:host];
NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher];
isDelayed = (delayedForHost != nil) && (idx != NSNotFound);
}
return isDelayed;
}
- (BOOL)fetcherShouldBeginFetching:(GTMHTTPFetcher *)fetcher {
// Entry point from the fetcher
@synchronized(self) {
NSString *host = [[[fetcher mutableRequest] URL] host];
if ([host length] == 0) {
#if DEBUG
NSAssert1(0, @"%@ lacks host", fetcher);
#endif
return YES;
}
NSMutableArray *runningForHost = [runningHosts_ objectForKey:host];
if (runningForHost != nil
&& [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) {
#if DEBUG
NSAssert1(0, @"%@ was already running", fetcher);
#endif
return YES;
}
// We'll save the host that serves as the key for this fetcher's array
// to avoid any chance of the underlying request changing, stranding
// the fetcher in the wrong array
fetcher.serviceHost = host;
fetcher.thread = [NSThread currentThread];
if (maxRunningFetchersPerHost_ == 0
|| maxRunningFetchersPerHost_ > [runningForHost count]) {
[self addRunningFetcher:fetcher forHost:host];
return YES;
} else {
[self addDelayedFetcher:fetcher forHost:host];
return NO;
}
}
return YES;
}
// Fetcher start and stop methods, invoked on the appropriate thread for
// the fetcher
- (void)startFetcherOnCurrentThread:(GTMHTTPFetcher *)fetcher {
[fetcher beginFetchMayDelay:NO
mayAuthorize:YES];
}
- (void)startFetcher:(GTMHTTPFetcher *)fetcher {
NSThread *thread = [fetcher thread];
if ([thread isEqual:[NSThread currentThread]]) {
// Same thread
[self startFetcherOnCurrentThread:fetcher];
} else {
// Different thread
[self performSelector:@selector(startFetcherOnCurrentThread:)
onThread:thread
withObject:fetcher
waitUntilDone:NO];
}
}
- (void)stopFetcherOnCurrentThread:(GTMHTTPFetcher *)fetcher {
[fetcher stopFetching];
}
- (void)stopFetcher:(GTMHTTPFetcher *)fetcher {
NSThread *thread = [fetcher thread];
if ([thread isEqual:[NSThread currentThread]]) {
// Same thread
[self stopFetcherOnCurrentThread:fetcher];
} else {
// Different thread
[self performSelector:@selector(stopFetcherOnCurrentThread:)
onThread:thread
withObject:fetcher
waitUntilDone:NO];
}
}
- (void)fetcherDidStop:(GTMHTTPFetcher *)fetcher {
// Entry point from the fetcher
@synchronized(self) {
NSString *host = fetcher.serviceHost;
if (!host) {
// fetcher has been stopped previously
return;
}
NSMutableArray *runningForHost = [runningHosts_ objectForKey:host];
[runningForHost removeObject:fetcher];
NSMutableArray *delayedForHost = [delayedHosts_ objectForKey:host];
[delayedForHost removeObject:fetcher];
while ([delayedForHost count] > 0
&& [runningForHost count] < maxRunningFetchersPerHost_) {
// Start another delayed fetcher running, scanning for the minimum
// priority value, defaulting to FIFO for equal priorities
GTMHTTPFetcher *nextFetcher = nil;
for (GTMHTTPFetcher *delayedFetcher in delayedForHost) {
if (nextFetcher == nil
|| delayedFetcher.servicePriority < nextFetcher.servicePriority) {
nextFetcher = delayedFetcher;
}
}
[self addRunningFetcher:nextFetcher forHost:host];
runningForHost = [runningHosts_ objectForKey:host];
[delayedForHost removeObjectIdenticalTo:nextFetcher];
[self startFetcher:nextFetcher];
}
if ([runningForHost count] == 0) {
// None left; remove the empty array
[runningHosts_ removeObjectForKey:host];
}
if ([delayedForHost count] == 0) {
[delayedHosts_ removeObjectForKey:host];
}
// The fetcher is no longer in the running or the delayed array,
// so remove its host and thread properties
fetcher.serviceHost = nil;
fetcher.thread = nil;
}
}
- (NSUInteger)numberOfFetchers {
NSUInteger running = [self numberOfRunningFetchers];
NSUInteger delayed = [self numberOfDelayedFetchers];
return running + delayed;
}
- (NSUInteger)numberOfRunningFetchers {
NSUInteger sum = 0;
for (NSString *host in runningHosts_) {
NSArray *fetchers = [runningHosts_ objectForKey:host];
sum += [fetchers count];
}
return sum;
}
- (NSUInteger)numberOfDelayedFetchers {
NSUInteger sum = 0;
for (NSString *host in delayedHosts_) {
NSArray *fetchers = [delayedHosts_ objectForKey:host];
sum += [fetchers count];
}
return sum;
}
- (void)stopAllFetchers {
// Remove fetchers from the delayed list to avoid fetcherDidStop: from
// starting more fetchers running as a side effect of stopping one
NSArray *delayedForHosts = [delayedHosts_ allValues];
[delayedHosts_ removeAllObjects];
for (NSArray *delayedForHost in delayedForHosts) {
for (GTMHTTPFetcher *fetcher in delayedForHost) {
[self stopFetcher:fetcher];
}
}
NSArray *runningForHosts = [runningHosts_ allValues];
[runningHosts_ removeAllObjects];
for (NSArray *runningForHost in runningForHosts) {
for (GTMHTTPFetcher *fetcher in runningForHost) {
[self stopFetcher:fetcher];
}
}
}
#pragma mark Fetch History Settings
// Turn on data caching to receive a copy of previously-retrieved objects.
// Otherwise, fetches may return status 304 (No Change) rather than actual data
- (void)setShouldCacheETaggedData:(BOOL)flag {
self.fetchHistory.shouldCacheETaggedData = flag;
}
- (BOOL)shouldCacheETaggedData {
return self.fetchHistory.shouldCacheETaggedData;
}
- (void)setETaggedDataCacheCapacity:(NSUInteger)totalBytes {
self.fetchHistory.memoryCapacity = totalBytes;
}
- (NSUInteger)ETaggedDataCacheCapacity {
return self.fetchHistory.memoryCapacity;
}
- (void)setShouldRememberETags:(BOOL)flag {
self.fetchHistory.shouldRememberETags = flag;
}
- (BOOL)shouldRememberETags {
return self.fetchHistory.shouldRememberETags;
}
// reset the ETag cache to avoid getting a Not Modified status
// based on prior queries
- (void)clearETaggedDataCache {
[self.fetchHistory clearETaggedDataCache];
}
- (void)clearHistory {
[self clearETaggedDataCache];
[self.fetchHistory removeAllCookies];
}
#pragma mark Synchronous Wait for Unit Testing
- (void)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds {
NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
while ([self numberOfFetchers] > 0
&& [giveUpDate timeIntervalSinceNow] > 0) {
// Run the current run loop 1/1000 of a second to give the networking
// code a chance to work
NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
[[NSRunLoop currentRunLoop] runUntilDate:stopDate];
}
}
#pragma mark Accessors
- (NSDictionary *)runningHosts {
return runningHosts_;
}
- (void)setRunningHosts:(NSDictionary *)dict {
[runningHosts_ autorelease];
runningHosts_ = [dict mutableCopy];
}
- (NSDictionary *)delayedHosts {
return delayedHosts_;
}
- (void)setDelayedHosts:(NSDictionary *)dict {
[delayedHosts_ autorelease];
delayedHosts_ = [dict mutableCopy];
}
- (id <GTMFetcherAuthorizationProtocol>)authorizer {
return authorizer_;
}
- (void)setAuthorizer:(id <GTMFetcherAuthorizationProtocol>)obj {
if (obj != authorizer_) {
[self detachAuthorizer];
}
[authorizer_ autorelease];
authorizer_ = [obj retain];
// Use the fetcher service for the authorization fetches if the auth
// object supports fetcher services
if ([authorizer_ respondsToSelector:@selector(setFetcherService:)]) {
[authorizer_ setFetcherService:self];
}
}
- (void)detachAuthorizer {
// This method is called by the fetcher service's dealloc and setAuthorizer:
// methods; do not override.
//
// The fetcher service retains the authorizer, and the authorizer has a
// weak pointer to the fetcher service (a non-zeroing pointer for
// compatibility with iOS 4 and Mac OS X 10.5/10.6.)
//
// When this fetcher service no longer uses the authorizer, we want to remove
// the authorizer's dependence on the fetcher service. Authorizers can still
// function without a fetcher service.
if ([authorizer_ respondsToSelector:@selector(fetcherService)]) {
GTMHTTPFetcherService *authFS = [authorizer_ fetcherService];
if (authFS == self) {
[authorizer_ setFetcherService:nil];
}
}
}
@end

View File

@ -0,0 +1,504 @@
//
// GTMLogger.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.
//
// Key Abstractions
// ----------------
//
// This file declares multiple classes and protocols that are used by the
// GTMLogger logging system. The 4 main abstractions used in this file are the
// following:
//
// * logger (GTMLogger) - The main logging class that users interact with. It
// has methods for logging at different levels and uses a log writer, a log
// formatter, and a log filter to get the job done.
//
// * log writer (GTMLogWriter) - Writes a given string to some log file, where
// a "log file" can be a physical file on disk, a POST over HTTP to some URL,
// or even some in-memory structure (e.g., a ring buffer).
//
// * log formatter (GTMLogFormatter) - Given a format string and arguments as
// a va_list, returns a single formatted NSString. A "formatted string" could
// be a string with the date prepended, a string with values in a CSV format,
// or even a string of XML.
//
// * log filter (GTMLogFilter) - Given a formatted log message as an NSString
// and the level at which the message is to be logged, this class will decide
// whether the given message should be logged or not. This is a flexible way
// to filter out messages logged at a certain level, messages that contain
// certain text, or filter nothing out at all. This gives the caller the
// flexibility to dynamically enable debug logging in Release builds.
//
// This file also declares some classes to handle the common log writer, log
// formatter, and log filter cases. Callers can also create their own writers,
// formatters, and filters and they can even build them on top of the ones
// declared here. Keep in mind that your custom writer/formatter/filter may be
// called from multiple threads, so it must be thread-safe.
#import <Foundation/Foundation.h>
#import "GTMDefines.h"
// Predeclaration of used protocols that are declared later in this file.
@protocol GTMLogWriter, GTMLogFormatter, GTMLogFilter;
// GTMLogger
//
// GTMLogger is the primary user-facing class for an object-oriented logging
// system. It is built on the concept of log formatters (GTMLogFormatter), log
// writers (GTMLogWriter), and log filters (GTMLogFilter). When a message is
// sent to a GTMLogger to log a message, the message is formatted using the log
// formatter, then the log filter is consulted to see if the message should be
// logged, and if so, the message is sent to the log writer to be written out.
//
// GTMLogger is intended to be a flexible and thread-safe logging solution. Its
// flexibility comes from the fact that GTMLogger instances can be customized
// with user defined formatters, filters, and writers. And these writers,
// filters, and formatters can be combined, stacked, and customized in arbitrary
// ways to suit the needs at hand. For example, multiple writers can be used at
// the same time, and a GTMLogger instance can even be used as another
// GTMLogger's writer. This allows for arbitrarily deep logging trees.
//
// A standard GTMLogger uses a writer that sends messages to standard out, a
// formatter that smacks a timestamp and a few other bits of interesting
// information on the message, and a filter that filters out debug messages from
// release builds. Using the standard log settings, a log message will look like
// the following:
//
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] foo=<Foo: 0x123>
//
// The output contains the date and time of the log message, the name of the
// process followed by its process ID/thread ID, the log level at which the
// message was logged (in the previous example the level was 1:
// kGTMLoggerLevelDebug), and finally, the user-specified log message itself (in
// this case, the log message was @"foo=%@", foo).
//
// Multiple instances of GTMLogger can be created, each configured their own
// way. Though GTMLogger is not a singleton (in the GoF sense), it does provide
// access to a shared (i.e., globally accessible) GTMLogger instance. This makes
// it convenient for all code in a process to use the same GTMLogger instance.
// The shared GTMLogger instance can also be configured in an arbitrary, and
// these configuration changes will affect all code that logs through the shared
// instance.
//
// Log Levels
// ----------
// GTMLogger has 3 different log levels: Debug, Info, and Error. GTMLogger
// doesn't take any special action based on the log level; it simply forwards
// this information on to formatters, filters, and writers, each of which may
// optionally take action based on the level. Since log level filtering is
// performed at runtime, log messages are typically not filtered out at compile
// time. The exception to this rule is that calls to the GTMLoggerDebug() macro
// *ARE* filtered out of non-DEBUG builds. This is to be backwards compatible
// with behavior that many developers are currently used to. Note that this
// means that GTMLoggerDebug(@"hi") will be compiled out of Release builds, but
// [[GTMLogger sharedLogger] logDebug:@"hi"] will NOT be compiled out.
//
// Standard loggers are created with the GTMLogLevelFilter log filter, which
// filters out certain log messages based on log level, and some other settings.
//
// In addition to the -logDebug:, -logInfo:, and -logError: methods defined on
// GTMLogger itself, there are also C macros that make usage of the shared
// GTMLogger instance very convenient. These macros are:
//
// GTMLoggerDebug(...)
// GTMLoggerInfo(...)
// GTMLoggerError(...)
//
// Again, a notable feature of these macros is that GTMLogDebug() calls *will be
// compiled out of non-DEBUG builds*.
//
// Standard Loggers
// ----------------
// GTMLogger has the concept of "standard loggers". A standard logger is simply
// a logger that is pre-configured with some standard/common writer, formatter,
// and filter combination. Standard loggers are created using the creation
// methods beginning with "standard". The alternative to a standard logger is a
// regular logger, which will send messages to stdout, with no special
// formatting, and no filtering.
//
// How do I use GTMLogger?
// ----------------------
// The typical way you will want to use GTMLogger is to simply use the
// GTMLogger*() macros for logging from code. That way we can easily make
// changes to the GTMLogger class and simply update the macros accordingly. Only
// your application startup code (perhaps, somewhere in main()) should use the
// GTMLogger class directly in order to configure the shared logger, which all
// of the code using the macros will be using. Again, this is just the typical
// situation.
//
// To be complete, there are cases where you may want to use GTMLogger directly,
// or even create separate GTMLogger instances for some reason. That's fine,
// too.
//
// Examples
// --------
// The following show some common GTMLogger use cases.
//
// 1. You want to log something as simply as possible. Also, this call will only
// appear in debug builds. In non-DEBUG builds it will be completely removed.
//
// GTMLoggerDebug(@"foo = %@", foo);
//
// 2. The previous example is similar to the following. The major difference is
// that the previous call (example 1) will be compiled out of Release builds
// but this statement will not be compiled out.
//
// [[GTMLogger sharedLogger] logDebug:@"foo = %@", foo];
//
// 3. Send all logging output from the shared logger to a file. We do this by
// creating an NSFileHandle for writing associated with a file, and setting
// that file handle as the logger's writer.
//
// NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log"
// create:YES];
// [[GTMLogger sharedLogger] setWriter:f];
// GTMLoggerError(@"hi"); // This will be sent to /tmp/f.log
//
// 4. Create a new GTMLogger that will log to a file. This example differs from
// the previous one because here we create a new GTMLogger that is different
// from the shared logger.
//
// GTMLogger *logger = [GTMLogger standardLoggerWithPath:@"/tmp/temp.log"];
// [logger logInfo:@"hi temp log file"];
//
// 5. Create a logger that writes to stdout and does NOT do any formatting to
// the log message. This might be useful, for example, when writing a help
// screen for a command-line tool to standard output.
//
// GTMLogger *logger = [GTMLogger logger];
// [logger logInfo:@"%@ version 0.1 usage", progName];
//
// 6. Send log output to stdout AND to a log file. The trick here is that
// NSArrays function as composite log writers, which means when an array is
// set as the log writer, it forwards all logging messages to all of its
// contained GTMLogWriters.
//
// // Create array of GTMLogWriters
// NSArray *writers = [NSArray arrayWithObjects:
// [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" create:YES],
// [NSFileHandle fileHandleWithStandardOutput], nil];
//
// GTMLogger *logger = [GTMLogger standardLogger];
// [logger setWriter:writers];
// [logger logInfo:@"hi"]; // Output goes to stdout and /tmp/f.log
//
// For futher details on log writers, formatters, and filters, see the
// documentation below.
//
// NOTE: GTMLogger is application level logging. By default it does nothing
// with _GTMDevLog/_GTMDevAssert (see GTMDefines.h). An application can choose
// to bridge _GTMDevLog/_GTMDevAssert to GTMLogger by providing macro
// definitions in its prefix header (see GTMDefines.h for how one would do
// that).
//
@interface GTMLogger : NSObject {
@private
id<GTMLogWriter> writer_;
id<GTMLogFormatter> formatter_;
id<GTMLogFilter> filter_;
}
//
// Accessors for the shared logger instance
//
// Returns a shared/global standard GTMLogger instance. Callers should typically
// use this method to get a GTMLogger instance, unless they explicitly want
// their own instance to configure for their own needs. This is the only method
// that returns a shared instance; all the rest return new GTMLogger instances.
+ (id)sharedLogger;
// Sets the shared logger instance to |logger|. Future calls to +sharedLogger
// will return |logger| instead.
+ (void)setSharedLogger:(GTMLogger *)logger;
//
// Creation methods
//
// Returns a new autoreleased GTMLogger instance that will log to stdout, using
// the GTMLogStandardFormatter, and the GTMLogLevelFilter filter.
+ (id)standardLogger;
// Same as +standardLogger, but logs to stderr.
+ (id)standardLoggerWithStderr;
// Same as +standardLogger but levels >= kGTMLoggerLevelError are routed to
// stderr, everything else goes to stdout.
+ (id)standardLoggerWithStdoutAndStderr;
// Returns a new standard GTMLogger instance with a log writer that will
// write to the file at |path|, and will use the GTMLogStandardFormatter and
// GTMLogLevelFilter classes. If |path| does not exist, it will be created.
+ (id)standardLoggerWithPath:(NSString *)path;
// Returns an autoreleased GTMLogger instance that will use the specified
// |writer|, |formatter|, and |filter|.
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
formatter:(id<GTMLogFormatter>)formatter
filter:(id<GTMLogFilter>)filter;
// Returns an autoreleased GTMLogger instance that logs to stdout, with the
// basic formatter, and no filter. The returned logger differs from the logger
// returned by +standardLogger because this one does not do any filtering and
// does not do any special log formatting; this is the difference between a
// "regular" logger and a "standard" logger.
+ (id)logger;
// Designated initializer. This method returns a GTMLogger initialized with the
// specified |writer|, |formatter|, and |filter|. See the setter methods below
// for what values will be used if nil is passed for a parameter.
- (id)initWithWriter:(id<GTMLogWriter>)writer
formatter:(id<GTMLogFormatter>)formatter
filter:(id<GTMLogFilter>)filter;
//
// Logging methods
//
// Logs a message at the debug level (kGTMLoggerLevelDebug).
- (void)logDebug:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2);
// Logs a message at the info level (kGTMLoggerLevelInfo).
- (void)logInfo:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2);
// Logs a message at the error level (kGTMLoggerLevelError).
- (void)logError:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2);
// Logs a message at the assert level (kGTMLoggerLevelAssert).
- (void)logAssert:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2);
//
// Accessors
//
// Accessor methods for the log writer. If the log writer is set to nil,
// [NSFileHandle fileHandleWithStandardOutput] is used.
- (id<GTMLogWriter>)writer;
- (void)setWriter:(id<GTMLogWriter>)writer;
// Accessor methods for the log formatter. If the log formatter is set to nil,
// GTMLogBasicFormatter is used. This formatter will format log messages in a
// plain printf style.
- (id<GTMLogFormatter>)formatter;
- (void)setFormatter:(id<GTMLogFormatter>)formatter;
// Accessor methods for the log filter. If the log filter is set to nil,
// GTMLogNoFilter is used, which allows all log messages through.
- (id<GTMLogFilter>)filter;
- (void)setFilter:(id<GTMLogFilter>)filter;
@end // GTMLogger
// Helper functions that are used by the convenience GTMLogger*() macros that
// enable the logging of function names.
@interface GTMLogger (GTMLoggerMacroHelpers)
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ...
NS_FORMAT_FUNCTION(2, 3);
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ...
NS_FORMAT_FUNCTION(2, 3);
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ...
NS_FORMAT_FUNCTION(2, 3);
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ...
NS_FORMAT_FUNCTION(2, 3);
@end // GTMLoggerMacroHelpers
// The convenience macros are only defined if they haven't already been defined.
#ifndef GTMLoggerInfo
// Convenience macros that log to the shared GTMLogger instance. These macros
// are how users should typically log to GTMLogger. Notice that GTMLoggerDebug()
// calls will be compiled out of non-Debug builds.
#define GTMLoggerDebug(...) \
[[GTMLogger sharedLogger] logFuncDebug:__func__ msg:__VA_ARGS__]
#define GTMLoggerInfo(...) \
[[GTMLogger sharedLogger] logFuncInfo:__func__ msg:__VA_ARGS__]
#define GTMLoggerError(...) \
[[GTMLogger sharedLogger] logFuncError:__func__ msg:__VA_ARGS__]
#define GTMLoggerAssert(...) \
[[GTMLogger sharedLogger] logFuncAssert:__func__ msg:__VA_ARGS__]
// If we're not in a debug build, remove the GTMLoggerDebug statements. This
// makes calls to GTMLoggerDebug "compile out" of Release builds
#ifndef DEBUG
#undef GTMLoggerDebug
#define GTMLoggerDebug(...) do {} while(0)
#endif
#endif // !defined(GTMLoggerInfo)
// Log levels.
typedef enum {
kGTMLoggerLevelUnknown,
kGTMLoggerLevelDebug,
kGTMLoggerLevelInfo,
kGTMLoggerLevelError,
kGTMLoggerLevelAssert,
} GTMLoggerLevel;
//
// Log Writers
//
// Protocol to be implemented by a GTMLogWriter instance.
@protocol GTMLogWriter <NSObject>
// Writes the given log message to where the log writer is configured to write.
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level;
@end // GTMLogWriter
// Simple category on NSFileHandle that makes NSFileHandles valid log writers.
// This is convenient because something like, say, +fileHandleWithStandardError
// now becomes a valid log writer. Log messages are written to the file handle
// with a newline appended.
@interface NSFileHandle (GTMFileHandleLogWriter) <GTMLogWriter>
// Opens the file at |path| in append mode, and creates the file with |mode|
// if it didn't previously exist.
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode;
@end // NSFileHandle
// This category makes NSArray a GTMLogWriter that can be composed of other
// GTMLogWriters. This is the classic Composite GoF design pattern. When the
// GTMLogWriter -logMessage:level: message is sent to the array, the array
// forwards the message to all of its elements that implement the GTMLogWriter
// protocol.
//
// This is useful in situations where you would like to send log output to
// multiple log writers at the same time. Simply create an NSArray of the log
// writers you wish to use, then set the array as the "writer" for your
// GTMLogger instance.
@interface NSArray (GTMArrayCompositeLogWriter) <GTMLogWriter>
@end // GTMArrayCompositeLogWriter
// This category adapts the GTMLogger interface so that it can be used as a log
// writer; it's an "adapter" in the GoF Adapter pattern sense.
//
// This is useful when you want to configure a logger to log to a specific
// writer with a specific formatter and/or filter. But you want to also compose
// that with a different log writer that may have its own formatter and/or
// filter.
@interface GTMLogger (GTMLoggerLogWriter) <GTMLogWriter>
@end // GTMLoggerLogWriter
//
// Log Formatters
//
// Protocol to be implemented by a GTMLogFormatter instance.
@protocol GTMLogFormatter <NSObject>
// Returns a formatted string using the format specified in |fmt| and the va
// args specified in |args|.
- (NSString *)stringForFunc:(NSString *)func
withFormat:(NSString *)fmt
valist:(va_list)args
level:(GTMLoggerLevel)level NS_FORMAT_FUNCTION(2, 0);
@end // GTMLogFormatter
// A basic log formatter that formats a string the same way that NSLog (or
// printf) would. It does not do anything fancy, nor does it add any data of its
// own.
@interface GTMLogBasicFormatter : NSObject <GTMLogFormatter>
// Helper method for prettying C99 __func__ and GCC __PRETTY_FUNCTION__
- (NSString *)prettyNameForFunc:(NSString *)func;
@end // GTMLogBasicFormatter
// A log formatter that formats the log string like the basic formatter, but
// also prepends a timestamp and some basic process info to the message, as
// shown in the following sample output.
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] log mesage here
@interface GTMLogStandardFormatter : GTMLogBasicFormatter {
@private
NSDateFormatter *dateFormatter_; // yyyy-MM-dd HH:mm:ss.SSS
NSString *pname_;
pid_t pid_;
}
@end // GTMLogStandardFormatter
//
// Log Filters
//
// Protocol to be imlemented by a GTMLogFilter instance.
@protocol GTMLogFilter <NSObject>
// Returns YES if |msg| at |level| should be filtered out; NO otherwise.
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level;
@end // GTMLogFilter
// A log filter that filters messages at the kGTMLoggerLevelDebug level out of
// non-debug builds. Messages at the kGTMLoggerLevelInfo level are also filtered
// out of non-debug builds unless GTMVerboseLogging is set in the environment or
// the processes's defaults. Messages at the kGTMLoggerLevelError level are
// never filtered.
@interface GTMLogLevelFilter : NSObject <GTMLogFilter>
@end // GTMLogLevelFilter
// A simple log filter that does NOT filter anything out;
// -filterAllowsMessage:level will always return YES. This can be a convenient
// way to enable debug-level logging in release builds (if you so desire).
@interface GTMLogNoFilter : NSObject <GTMLogFilter>
@end // GTMLogNoFilter
// Base class for custom level filters. Not for direct use, use the minimum
// or maximum level subclasses below.
@interface GTMLogAllowedLevelFilter : NSObject <GTMLogFilter> {
@private
NSIndexSet *allowedLevels_;
}
@end
// A log filter that allows you to set a minimum log level. Messages below this
// level will be filtered.
@interface GTMLogMininumLevelFilter : GTMLogAllowedLevelFilter
// Designated initializer, logs at levels < |level| will be filtered.
- (id)initWithMinimumLevel:(GTMLoggerLevel)level;
@end
// A log filter that allows you to set a maximum log level. Messages whose level
// exceeds this level will be filtered. This is really only useful if you have
// a composite GTMLogger that is sending the other messages elsewhere.
@interface GTMLogMaximumLevelFilter : GTMLogAllowedLevelFilter
// Designated initializer, logs at levels > |level| will be filtered.
- (id)initWithMaximumLevel:(GTMLoggerLevel)level;
@end
// For subclasses only
@interface GTMLogger (PrivateMethods)
- (void)logInternalFunc:(const char *)func
format:(NSString *)fmt
valist:(va_list)args
level:(GTMLoggerLevel)level NS_FORMAT_FUNCTION(2, 0);
@end

View File

@ -0,0 +1,598 @@
//
// GTMLogger.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 "GTMLogger.h"
#import "GTMGarbageCollection.h"
#import <fcntl.h>
#import <unistd.h>
#import <stdlib.h>
#import <pthread.h>
// Reference to the shared GTMLogger instance. This is not a singleton, it's
// just an easy reference to one shared instance.
static GTMLogger *gSharedLogger = nil;
@implementation GTMLogger
// Returns a pointer to the shared logger instance. If none exists, a standard
// logger is created and returned.
+ (id)sharedLogger {
@synchronized(self) {
if (gSharedLogger == nil) {
gSharedLogger = [[self standardLogger] retain];
}
}
return [[gSharedLogger retain] autorelease];
}
+ (void)setSharedLogger:(GTMLogger *)logger {
@synchronized(self) {
[gSharedLogger autorelease];
gSharedLogger = [logger retain];
}
}
+ (id)standardLogger {
// Don't trust NSFileHandle not to throw
@try {
id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput];
id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init]
autorelease];
id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease];
return [[[self alloc] initWithWriter:writer
formatter:fr
filter:filter] autorelease];
}
@catch (id e) {
// Ignored
}
return nil;
}
+ (id)standardLoggerWithStderr {
// Don't trust NSFileHandle not to throw
@try {
id me = [self standardLogger];
[me setWriter:[NSFileHandle fileHandleWithStandardError]];
return me;
}
@catch (id e) {
// Ignored
}
return nil;
}
+ (id)standardLoggerWithStdoutAndStderr {
// We're going to take advantage of the GTMLogger to GTMLogWriter adaptor
// and create a composite logger that an outer "standard" logger can use
// as a writer. Our inner loggers should apply no formatting since the main
// logger does that and we want the caller to be able to change formatters
// or add writers without knowing the inner structure of our composite.
// Don't trust NSFileHandle not to throw
@try {
GTMLogBasicFormatter *formatter = [[[GTMLogBasicFormatter alloc] init]
autorelease];
GTMLogger *stdoutLogger =
[self loggerWithWriter:[NSFileHandle fileHandleWithStandardOutput]
formatter:formatter
filter:[[[GTMLogMaximumLevelFilter alloc]
initWithMaximumLevel:kGTMLoggerLevelInfo]
autorelease]];
GTMLogger *stderrLogger =
[self loggerWithWriter:[NSFileHandle fileHandleWithStandardError]
formatter:formatter
filter:[[[GTMLogMininumLevelFilter alloc]
initWithMinimumLevel:kGTMLoggerLevelError]
autorelease]];
GTMLogger *compositeWriter =
[self loggerWithWriter:[NSArray arrayWithObjects:
stdoutLogger, stderrLogger, nil]
formatter:formatter
filter:[[[GTMLogNoFilter alloc] init] autorelease]];
GTMLogger *outerLogger = [self standardLogger];
[outerLogger setWriter:compositeWriter];
return outerLogger;
}
@catch (id e) {
// Ignored
}
return nil;
}
+ (id)standardLoggerWithPath:(NSString *)path {
@try {
NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644];
if (fh == nil) return nil;
id me = [self standardLogger];
[me setWriter:fh];
return me;
}
@catch (id e) {
// Ignored
}
return nil;
}
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
formatter:(id<GTMLogFormatter>)formatter
filter:(id<GTMLogFilter>)filter {
return [[[self alloc] initWithWriter:writer
formatter:formatter
filter:filter] autorelease];
}
+ (id)logger {
return [[[self alloc] init] autorelease];
}
- (id)init {
return [self initWithWriter:nil formatter:nil filter:nil];
}
- (id)initWithWriter:(id<GTMLogWriter>)writer
formatter:(id<GTMLogFormatter>)formatter
filter:(id<GTMLogFilter>)filter {
if ((self = [super init])) {
[self setWriter:writer];
[self setFormatter:formatter];
[self setFilter:filter];
}
return self;
}
- (void)dealloc {
// Unlikely, but |writer_| may be an NSFileHandle, which can throw
@try {
[formatter_ release];
[filter_ release];
[writer_ release];
}
@catch (id e) {
// Ignored
}
[super dealloc];
}
- (id<GTMLogWriter>)writer {
return [[writer_ retain] autorelease];
}
- (void)setWriter:(id<GTMLogWriter>)writer {
@synchronized(self) {
[writer_ autorelease];
writer_ = nil;
if (writer == nil) {
// Try to use stdout, but don't trust NSFileHandle
@try {
writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain];
}
@catch (id e) {
// Leave |writer_| nil
}
} else {
writer_ = [writer retain];
}
}
}
- (id<GTMLogFormatter>)formatter {
return [[formatter_ retain] autorelease];
}
- (void)setFormatter:(id<GTMLogFormatter>)formatter {
@synchronized(self) {
[formatter_ autorelease];
formatter_ = nil;
if (formatter == nil) {
@try {
formatter_ = [[GTMLogBasicFormatter alloc] init];
}
@catch (id e) {
// Leave |formatter_| nil
}
} else {
formatter_ = [formatter retain];
}
}
}
- (id<GTMLogFilter>)filter {
return [[filter_ retain] autorelease];
}
- (void)setFilter:(id<GTMLogFilter>)filter {
@synchronized(self) {
[filter_ autorelease];
filter_ = nil;
if (filter == nil) {
@try {
filter_ = [[GTMLogNoFilter alloc] init];
}
@catch (id e) {
// Leave |filter_| nil
}
} else {
filter_ = [filter retain];
}
}
}
- (void)logDebug:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelDebug];
va_end(args);
}
- (void)logInfo:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelInfo];
va_end(args);
}
- (void)logError:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelError];
va_end(args);
}
- (void)logAssert:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelAssert];
va_end(args);
}
@end // GTMLogger
@implementation GTMLogger (GTMLoggerMacroHelpers)
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelDebug];
va_end(args);
}
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelInfo];
va_end(args);
}
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelError];
va_end(args);
}
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... {
va_list args;
va_start(args, fmt);
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelAssert];
va_end(args);
}
@end // GTMLoggerMacroHelpers
@implementation GTMLogger (PrivateMethods)
- (void)logInternalFunc:(const char *)func
format:(NSString *)fmt
valist:(va_list)args
level:(GTMLoggerLevel)level {
// Primary point where logging happens, logging should never throw, catch
// everything.
@try {
NSString *fname = func ? [NSString stringWithUTF8String:func] : nil;
NSString *msg = [formatter_ stringForFunc:fname
withFormat:fmt
valist:args
level:level];
if (msg && [filter_ filterAllowsMessage:msg level:level])
[writer_ logMessage:msg level:level];
}
@catch (id e) {
// Ignored
}
}
@end // PrivateMethods
@implementation NSFileHandle (GTMFileHandleLogWriter)
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode {
int fd = -1;
if (path) {
int flags = O_WRONLY | O_APPEND | O_CREAT;
fd = open([path fileSystemRepresentation], flags, mode);
}
if (fd == -1) return nil;
return [[[self alloc] initWithFileDescriptor:fd
closeOnDealloc:YES] autorelease];
}
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
@synchronized(self) {
// Closed pipes should not generate exceptions in our caller. Catch here
// as well [GTMLogger logInternalFunc:...] so that an exception in this
// writer does not prevent other writers from having a chance.
@try {
NSString *line = [NSString stringWithFormat:@"%@\n", msg];
[self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
}
@catch (id e) {
// Ignored
}
}
}
@end // GTMFileHandleLogWriter
@implementation NSArray (GTMArrayCompositeLogWriter)
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
@synchronized(self) {
id<GTMLogWriter> child = nil;
GTM_FOREACH_OBJECT(child, self) {
if ([child conformsToProtocol:@protocol(GTMLogWriter)])
[child logMessage:msg level:level];
}
}
}
@end // GTMArrayCompositeLogWriter
@implementation GTMLogger (GTMLoggerLogWriter)
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
switch (level) {
case kGTMLoggerLevelDebug:
[self logDebug:@"%@", msg];
break;
case kGTMLoggerLevelInfo:
[self logInfo:@"%@", msg];
break;
case kGTMLoggerLevelError:
[self logError:@"%@", msg];
break;
case kGTMLoggerLevelAssert:
[self logAssert:@"%@", msg];
break;
default:
// Ignore the message.
break;
}
}
@end // GTMLoggerLogWriter
@implementation GTMLogBasicFormatter
- (NSString *)prettyNameForFunc:(NSString *)func {
NSString *name = [func stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *function = @"(unknown)";
if ([name length]) {
if (// Objective C __func__ and __PRETTY_FUNCTION__
[name hasPrefix:@"-["] || [name hasPrefix:@"+["] ||
// C++ __PRETTY_FUNCTION__ and other preadorned formats
[name hasSuffix:@")"]) {
function = name;
} else {
// Assume C99 __func__
function = [NSString stringWithFormat:@"%@()", name];
}
}
return function;
}
- (NSString *)stringForFunc:(NSString *)func
withFormat:(NSString *)fmt
valist:(va_list)args
level:(GTMLoggerLevel)level {
// Performance note: We may want to do a quick check here to see if |fmt|
// contains a '%', and if not, simply return 'fmt'.
if (!(fmt && args)) return nil;
return [[[NSString alloc] initWithFormat:fmt arguments:args] autorelease];
}
@end // GTMLogBasicFormatter
@implementation GTMLogStandardFormatter
- (id)init {
if ((self = [super init])) {
dateFormatter_ = [[NSDateFormatter alloc] init];
[dateFormatter_ setFormatterBehavior:NSDateFormatterBehavior10_4];
[dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
pname_ = [[[NSProcessInfo processInfo] processName] copy];
pid_ = [[NSProcessInfo processInfo] processIdentifier];
if (!(dateFormatter_ && pname_)) {
[self release];
return nil;
}
}
return self;
}
- (void)dealloc {
[dateFormatter_ release];
[pname_ release];
[super dealloc];
}
- (NSString *)stringForFunc:(NSString *)func
withFormat:(NSString *)fmt
valist:(va_list)args
level:(GTMLoggerLevel)level {
NSString *tstamp = nil;
@synchronized (dateFormatter_) {
tstamp = [dateFormatter_ stringFromDate:[NSDate date]];
}
return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@",
tstamp, pname_, pid_, pthread_self(),
level, [self prettyNameForFunc:func],
// |super| has guard for nil |fmt| and |args|
[super stringForFunc:func withFormat:fmt valist:args level:level]];
}
@end // GTMLogStandardFormatter
@implementation GTMLogLevelFilter
// Check the environment and the user preferences for the GTMVerboseLogging key
// to see if verbose logging has been enabled. The environment variable will
// override the defaults setting, so check the environment first.
// COV_NF_START
static BOOL IsVerboseLoggingEnabled(void) {
static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging";
NSString *value = [[[NSProcessInfo processInfo] environment]
objectForKey:kVerboseLoggingKey];
if (value) {
// Emulate [NSString boolValue] for pre-10.5
value = [value stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([[value uppercaseString] hasPrefix:@"Y"] ||
[[value uppercaseString] hasPrefix:@"T"] ||
[value intValue]) {
return YES;
} else {
return NO;
}
}
return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey];
}
// COV_NF_END
// In DEBUG builds, log everything. If we're not in a debug build we'll assume
// that we're in a Release build.
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
#if DEBUG
return YES;
#endif
BOOL allow = YES;
switch (level) {
case kGTMLoggerLevelDebug:
allow = NO;
break;
case kGTMLoggerLevelInfo:
allow = IsVerboseLoggingEnabled();
break;
case kGTMLoggerLevelError:
allow = YES;
break;
case kGTMLoggerLevelAssert:
allow = YES;
break;
default:
allow = YES;
break;
}
return allow;
}
@end // GTMLogLevelFilter
@implementation GTMLogNoFilter
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
return YES; // Allow everything through
}
@end // GTMLogNoFilter
@implementation GTMLogAllowedLevelFilter
// Private designated initializer
- (id)initWithAllowedLevels:(NSIndexSet *)levels {
self = [super init];
if (self != nil) {
allowedLevels_ = [levels retain];
// Cap min/max level
if (!allowedLevels_ ||
// NSIndexSet is unsigned so only check the high bound, but need to
// check both first and last index because NSIndexSet appears to allow
// wraparound.
([allowedLevels_ firstIndex] > kGTMLoggerLevelAssert) ||
([allowedLevels_ lastIndex] > kGTMLoggerLevelAssert)) {
[self release];
return nil;
}
}
return self;
}
- (id)init {
// Allow all levels in default init
return [self initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(kGTMLoggerLevelUnknown,
(kGTMLoggerLevelAssert - kGTMLoggerLevelUnknown + 1))]];
}
- (void)dealloc {
[allowedLevels_ release];
[super dealloc];
}
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
return [allowedLevels_ containsIndex:level];
}
@end // GTMLogAllowedLevelFilter
@implementation GTMLogMininumLevelFilter
- (id)initWithMinimumLevel:(GTMLoggerLevel)level {
return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(level,
(kGTMLoggerLevelAssert - level + 1))]];
}
@end // GTMLogMininumLevelFilter
@implementation GTMLogMaximumLevelFilter
- (id)initWithMaximumLevel:(GTMLoggerLevel)level {
return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(kGTMLoggerLevelUnknown, level + 1)]];
}
@end // GTMLogMaximumLevelFilter

View File

@ -0,0 +1,88 @@
//
// GTMMethodCheck.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 <stdio.h>
#import <sysexits.h>
/// A macro for enforcing debug time checks to make sure all required methods are linked in
//
// When using categories, it can be very easy to forget to include the
// implementation of a category.
// Let's say you had a class foo that depended on method bar of class baz, and
// method bar was implemented as a member of a category.
// You could add the following code:
// @implementation foo
// GTM_METHOD_CHECK(baz, bar)
// @end
// and the code would check to make sure baz was implemented just before main
// was called. This works for both dynamic libraries, and executables.
//
// Classes (or one of their superclasses) being checked must conform to the
// NSObject protocol. We will check this, and spit out a warning if a class does
// not conform to NSObject.
//
// This is not compiled into release builds.
#ifdef DEBUG
#ifdef __cplusplus
extern "C" {
#endif
// If you get an error for GTMMethodCheckMethodChecker not being defined,
// you need to link in GTMMethodCheck.m. We keep it hidden so that we can have
// it living in several separate images without conflict.
// Functions with the ((constructor)) attribute are called after all +loads
// have been called. See "Initializing Objective-C Classes" in
// http://developer.apple.com/documentation/DeveloperTools/Conceptual/DynamicLibraries/Articles/DynamicLibraryDesignGuidelines.html#//apple_ref/doc/uid/TP40002013-DontLinkElementID_20
__attribute__ ((constructor, visibility("hidden"))) void GTMMethodCheckMethodChecker(void);
#ifdef __cplusplus
};
#endif
// This is the "magic".
// A) we need a multi layer define here so that the stupid preprocessor
// expands __LINE__ out the way we want it. We need LINE so that each of
// out GTM_METHOD_CHECKs generates a unique class method for the class.
#define GTM_METHOD_CHECK(class, method) GTM_METHOD_CHECK_INNER(class, method, __LINE__)
#define GTM_METHOD_CHECK_INNER(class, method, line) GTM_METHOD_CHECK_INNER_INNER(class, method, line)
// B) Create up a class method called xxGMethodCheckMethod+class+line that the
// GTMMethodCheckMethodChecker function can look for and call. We
// look for GTMMethodCheckMethodChecker to enforce linkage of
// GTMMethodCheck.m.
#define GTM_METHOD_CHECK_INNER_INNER(class, method, line) \
+ (void)xxGTMMethodCheckMethod ## class ## line { \
void (*addr)() = GTMMethodCheckMethodChecker; \
if (addr && ![class instancesRespondToSelector:@selector(method)] \
&& ![class respondsToSelector:@selector(method)]) { \
fprintf(stderr, "%s:%d: error: We need method '%s' to be linked in for class '%s'\n", \
__FILE__, line, #method, #class); \
exit(EX_SOFTWARE); \
} \
}
#else // !DEBUG
// Do nothing in release.
#define GTM_METHOD_CHECK(class, method)
#endif // DEBUG

View File

@ -0,0 +1,161 @@
//
// GTMMethodCheck.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.
//
// Don't want any of this in release builds
#ifdef DEBUG
#import "GTMDefines.h"
#import "GTMMethodCheck.h"
#import "GTMObjC2Runtime.h"
#import <dlfcn.h>
// Checks to see if the cls passed in (or one of it's superclasses) conforms
// to NSObject protocol. Inheriting from NSObject is the easiest way to do this
// but not all classes (i.e. NSProxy) inherit from NSObject. Also, some classes
// inherit from Object instead of NSObject which is fine, and we'll count as
// conforming to NSObject for our needs.
static BOOL ConformsToNSObjectProtocol(Class cls) {
// If we get nil, obviously doesn't conform.
if (!cls) return NO;
const char *className = class_getName(cls);
if (!className) return NO;
// We're going to assume that all Apple classes will work
// (and aren't being checked)
// Note to apple: why doesn't obj-c have real namespaces instead of two
// letter hacks? If you name your own classes starting with NS this won't
// work for you.
// Some classes (like _NSZombie) start with _NS.
// On Leopard we have to look for CFObject as well.
// On iPhone we check Object as well
if ((strncmp(className, "NS", 2) == 0)
|| (strncmp(className, "_NS", 3) == 0)
|| (strncmp(className, "__NS", 4) == 0)
|| (strcmp(className, "CFObject") == 0)
|| (strcmp(className, "__IncompleteProtocol") == 0)
#if GTM_IPHONE_SDK
|| (strcmp(className, "Object") == 0)
#endif
) {
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));
}
void GTMMethodCheckMethodChecker(void) {
// Run through all the classes looking for class methods that are
// prefixed with xxGMMethodCheckMethod. If it finds one, it calls it.
// See GTMMethodCheck.h to see what it does.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int numClasses = 0;
int newNumClasses = objc_getClassList(NULL, 0);
int i;
Class *classes = NULL;
while (numClasses < newNumClasses) {
numClasses = newNumClasses;
classes = realloc(classes, sizeof(Class) * numClasses);
_GTMDevAssert(classes, @"Unable to allocate memory for classes");
newNumClasses = objc_getClassList(classes, numClasses);
}
for (i = 0; i < numClasses && classes; ++i) {
Class cls = classes[i];
// Since we are directly calling objc_msgSend, we need to conform to
// @protocol(NSObject), or else we will tumble into a _objc_msgForward
// recursive loop when we try and call a function by name.
if (!ConformsToNSObjectProtocol(cls)) {
// COV_NF_START
_GTMDevLog(@"GTMMethodCheckMethodChecker: Class %s does not conform to "
"@protocol(NSObject), so won't be checked",
class_getName(cls));
continue;
// COV_NF_END
}
// Since we are looking for a class method (+xxGMMethodCheckMethod...)
// we need to query the isa pointer to see what methods it support, but
// send the method (if it's supported) to the class itself.
unsigned int count;
Class metaClass = objc_getMetaClass(class_getName(cls));
Method *methods = class_copyMethodList(metaClass, &count);
unsigned int j;
for (j = 0; j < count; ++j) {
SEL selector = method_getName(methods[j]);
const char *name = sel_getName(selector);
if (strstr(name, "xxGTMMethodCheckMethod") == name) {
// Check to make sure that the method we are checking comes
// from the same image that we are in. Since GTMMethodCheckMethodChecker
// is not exported, we should always find the copy in our local
// image. We compare the address of it's image with the address of
// the image which implements the method we want to check. If
// they match we continue. This does two things:
// a) minimizes the amount of calls we make to the xxxGTMMethodCheck
// methods. They should only be called once.
// b) prevents initializers for various classes being called too early
Dl_info methodCheckerInfo;
if (!dladdr(GTMMethodCheckMethodChecker,
&methodCheckerInfo)) {
// COV_NF_START
// Don't know how to force this case in a unittest.
// Certainly hope we never see it.
_GTMDevLog(@"GTMMethodCheckMethodChecker: Unable to get dladdr info "
"for GTMMethodCheckMethodChecker while introspecting +[%s %s]]",
class_getName(cls), name);
continue;
// COV_NF_END
}
Dl_info methodInfo;
if (!dladdr(method_getImplementation(methods[j]),
&methodInfo)) {
// COV_NF_START
// Don't know how to force this case in a unittest
// Certainly hope we never see it.
_GTMDevLog(@"GTMMethodCheckMethodChecker: Unable to get dladdr info "
"for %s while introspecting +[%s %s]]", name,
class_getName(cls), name);
continue;
// COV_NF_END
}
if (methodCheckerInfo.dli_fbase == methodInfo.dli_fbase) {
objc_msgSend(cls, selector);
}
}
}
free(methods);
}
free(classes);
[pool drain];
}
#endif // DEBUG

View File

@ -0,0 +1,36 @@
//
// GTMNSDictionary+URLArguments.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>
/// Utility for building a URL or POST argument string.
@interface NSDictionary (GTMNSDictionaryURLArgumentsAdditions)
/// Returns a dictionary of the decoded key-value pairs in a http arguments
/// string of the form key1=value1&key2=value2&...&keyN=valueN.
/// Keys and values will be unescaped automatically.
/// Only the first value for a repeated key is returned.
+ (NSDictionary *)gtm_dictionaryWithHttpArgumentsString:(NSString *)argString;
/// Gets a string representation of the dictionary in the form
/// key1=value1&key2=value2&...&keyN=valueN, suitable for use as either
/// URL arguments (after a '?') or POST body. Keys and values will be escaped
/// automatically, so should be unescaped in the dictionary.
- (NSString *)gtm_httpArgumentsString;
@end

View File

@ -0,0 +1,71 @@
//
// GTMNSDictionary+URLArguments.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 "GTMNSDictionary+URLArguments.h"
#import "GTMNSString+URLArguments.h"
#import "GTMMethodCheck.h"
#import "GTMDefines.h"
@implementation NSDictionary (GTMNSDictionaryURLArgumentsAdditions)
GTM_METHOD_CHECK(NSString, gtm_stringByEscapingForURLArgument);
GTM_METHOD_CHECK(NSString, gtm_stringByUnescapingFromURLArgument);
+ (NSDictionary *)gtm_dictionaryWithHttpArgumentsString:(NSString *)argString {
NSMutableDictionary* ret = [NSMutableDictionary dictionary];
NSArray* components = [argString componentsSeparatedByString:@"&"];
NSString* component;
// Use reverse order so that the first occurrence of a key replaces
// those subsequent.
GTM_FOREACH_ENUMEREE(component, [components reverseObjectEnumerator]) {
if ([component length] == 0)
continue;
NSRange pos = [component rangeOfString:@"="];
NSString *key;
NSString *val;
if (pos.location == NSNotFound) {
key = [component gtm_stringByUnescapingFromURLArgument];
val = @"";
} else {
key = [[component substringToIndex:pos.location]
gtm_stringByUnescapingFromURLArgument];
val = [[component substringFromIndex:pos.location + pos.length]
gtm_stringByUnescapingFromURLArgument];
}
// gtm_stringByUnescapingFromURLArgument returns nil on invalid UTF8
// and NSMutableDictionary raises an exception when passed nil values.
if (!key) key = @"";
if (!val) val = @"";
[ret setObject:val forKey:key];
}
return ret;
}
- (NSString *)gtm_httpArgumentsString {
NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:[self count]];
NSString* key;
GTM_FOREACH_KEY(key, self) {
[arguments addObject:[NSString stringWithFormat:@"%@=%@",
[key gtm_stringByEscapingForURLArgument],
[[[self objectForKey:key] description] gtm_stringByEscapingForURLArgument]]];
}
return [arguments componentsJoinedByString:@"&"];
}
@end

View File

@ -0,0 +1,41 @@
//
// GTMNSString+URLArguments.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>
/// Utilities for encoding and decoding URL arguments.
@interface NSString (GTMNSStringURLArgumentsAdditions)
/// Returns a string that is escaped properly to be a URL argument.
//
/// This differs from stringByAddingPercentEscapesUsingEncoding: in that it
/// will escape all the reserved characters (per RFC 3986
/// <http://www.ietf.org/rfc/rfc3986.txt>) which
/// stringByAddingPercentEscapesUsingEncoding would leave.
///
/// This will also escape '%', so this should not be used on a string that has
/// already been escaped unless double-escaping is the desired result.
- (NSString*)gtm_stringByEscapingForURLArgument;
/// Returns the unescaped version of a URL argument
//
/// This has the same behavior as stringByReplacingPercentEscapesUsingEncoding:,
/// except that it will also convert '+' to space.
- (NSString*)gtm_stringByUnescapingFromURLArgument;
@end

View File

@ -0,0 +1,45 @@
//
// GTMNSString+URLArguments.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 "GTMNSString+URLArguments.h"
#import "GTMGarbageCollection.h"
@implementation NSString (GTMNSStringURLArgumentsAdditions)
- (NSString*)gtm_stringByEscapingForURLArgument {
// Encode all the reserved characters, per RFC 3986
// (<http://www.ietf.org/rfc/rfc3986.txt>)
CFStringRef escaped =
CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)self,
NULL,
(CFStringRef)@"!*'();:@&=+$,/?%#[]",
kCFStringEncodingUTF8);
return GTMCFAutorelease(escaped);
}
- (NSString*)gtm_stringByUnescapingFromURLArgument {
NSMutableString *resultString = [NSMutableString stringWithString:self];
[resultString replaceOccurrencesOfString:@"+"
withString:@" "
options:NSLiteralSearch
range:NSMakeRange(0, [resultString length])];
return [resultString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
@end

View File

@ -0,0 +1,325 @@
/* Copyright (c) 2011 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.
*/
#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
// This class implements the OAuth 2 protocol for authorizing requests.
// http://tools.ietf.org/html/draft-ietf-oauth-v2
#import <Foundation/Foundation.h>
// GTMHTTPFetcher.h brings in GTLDefines/GDataDefines
#import "GTMHTTPFetcher.h"
#undef _EXTERN
#undef _INITIALIZE_AS
#ifdef GTMOAUTH2AUTHENTICATION_DEFINE_GLOBALS
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
#if defined(__cplusplus)
#define _EXTERN extern "C"
#else
#define _EXTERN extern
#endif
#define _INITIALIZE_AS(x)
#endif
// Until all OAuth 2 providers are up to the same spec, we'll provide a crude
// way here to override the "Bearer" string in the Authorization header
#ifndef GTM_OAUTH2_BEARER
#define GTM_OAUTH2_BEARER "Bearer"
#endif
// Service provider name allows stored authorization to be associated with
// the authorizing service
_EXTERN NSString* const kGTMOAuth2ServiceProviderGoogle _INITIALIZE_AS(@"Google");
//
// GTMOAuth2SignIn constants, included here for use by clients
//
_EXTERN NSString* const kGTMOAuth2ErrorDomain _INITIALIZE_AS(@"com.google.GTMOAuth2");
// Error userInfo keys
_EXTERN NSString* const kGTMOAuth2ErrorMessageKey _INITIALIZE_AS(@"error");
_EXTERN NSString* const kGTMOAuth2ErrorRequestKey _INITIALIZE_AS(@"request");
_EXTERN NSString* const kGTMOAuth2ErrorJSONKey _INITIALIZE_AS(@"json");
enum {
// Error code indicating that the window was prematurely closed
kGTMOAuth2ErrorWindowClosed = -1000,
kGTMOAuth2ErrorAuthorizationFailed = -1001,
kGTMOAuth2ErrorTokenExpired = -1002,
kGTMOAuth2ErrorTokenUnavailable = -1003,
kGTMOAuth2ErrorUnauthorizableRequest = -1004
};
// Notifications for token fetches
_EXTERN NSString* const kGTMOAuth2FetchStarted _INITIALIZE_AS(@"kGTMOAuth2FetchStarted");
_EXTERN NSString* const kGTMOAuth2FetchStopped _INITIALIZE_AS(@"kGTMOAuth2FetchStopped");
_EXTERN NSString* const kGTMOAuth2FetcherKey _INITIALIZE_AS(@"fetcher");
_EXTERN NSString* const kGTMOAuth2FetchTypeKey _INITIALIZE_AS(@"FetchType");
_EXTERN NSString* const kGTMOAuth2FetchTypeToken _INITIALIZE_AS(@"token");
_EXTERN NSString* const kGTMOAuth2FetchTypeRefresh _INITIALIZE_AS(@"refresh");
_EXTERN NSString* const kGTMOAuth2FetchTypeAssertion _INITIALIZE_AS(@"assertion");
_EXTERN NSString* const kGTMOAuth2FetchTypeUserInfo _INITIALIZE_AS(@"userInfo");
// Token-issuance errors
_EXTERN NSString* const kGTMOAuth2ErrorKey _INITIALIZE_AS(@"error");
_EXTERN NSString* const kGTMOAuth2ErrorInvalidRequest _INITIALIZE_AS(@"invalid_request");
_EXTERN NSString* const kGTMOAuth2ErrorInvalidClient _INITIALIZE_AS(@"invalid_client");
_EXTERN NSString* const kGTMOAuth2ErrorInvalidGrant _INITIALIZE_AS(@"invalid_grant");
_EXTERN NSString* const kGTMOAuth2ErrorUnauthorizedClient _INITIALIZE_AS(@"unauthorized_client");
_EXTERN NSString* const kGTMOAuth2ErrorUnsupportedGrantType _INITIALIZE_AS(@"unsupported_grant_type");
_EXTERN NSString* const kGTMOAuth2ErrorInvalidScope _INITIALIZE_AS(@"invalid_scope");
// Notification that sign-in has completed, and token fetches will begin (useful
// for displaying interstitial messages after the window has closed)
_EXTERN NSString* const kGTMOAuth2UserSignedIn _INITIALIZE_AS(@"kGTMOAuth2UserSignedIn");
// Notification for token changes
_EXTERN NSString* const kGTMOAuth2AccessTokenRefreshed _INITIALIZE_AS(@"kGTMOAuth2AccessTokenRefreshed");
_EXTERN NSString* const kGTMOAuth2RefreshTokenChanged _INITIALIZE_AS(@"kGTMOAuth2RefreshTokenChanged");
// Notification for WebView loading
_EXTERN NSString* const kGTMOAuth2WebViewStartedLoading _INITIALIZE_AS(@"kGTMOAuth2WebViewStartedLoading");
_EXTERN NSString* const kGTMOAuth2WebViewStoppedLoading _INITIALIZE_AS(@"kGTMOAuth2WebViewStoppedLoading");
_EXTERN NSString* const kGTMOAuth2WebViewKey _INITIALIZE_AS(@"kGTMOAuth2WebViewKey");
_EXTERN NSString* const kGTMOAuth2WebViewStopKindKey _INITIALIZE_AS(@"kGTMOAuth2WebViewStopKindKey");
_EXTERN NSString* const kGTMOAuth2WebViewFinished _INITIALIZE_AS(@"finished");
_EXTERN NSString* const kGTMOAuth2WebViewFailed _INITIALIZE_AS(@"failed");
_EXTERN NSString* const kGTMOAuth2WebViewCancelled _INITIALIZE_AS(@"cancelled");
// Notification for network loss during html sign-in display
_EXTERN NSString* const kGTMOAuth2NetworkLost _INITIALIZE_AS(@"kGTMOAuthNetworkLost");
_EXTERN NSString* const kGTMOAuth2NetworkFound _INITIALIZE_AS(@"kGTMOAuthNetworkFound");
@interface GTMOAuth2Authentication : NSObject <GTMFetcherAuthorizationProtocol> {
@private
NSString *clientID_;
NSString *clientSecret_;
NSString *redirectURI_;
NSMutableDictionary *parameters_;
// authorization parameters
NSURL *tokenURL_;
NSDate *expirationDate_;
NSDictionary *additionalTokenRequestParameters_;
// queue of requests for authorization waiting for a valid access token
GTMHTTPFetcher *refreshFetcher_;
NSMutableArray *authorizationQueue_;
id <GTMHTTPFetcherServiceProtocol> fetcherService_; // WEAK
Class parserClass_;
BOOL shouldAuthorizeAllRequests_;
// arbitrary data retained for the user
id userData_;
NSMutableDictionary *properties_;
}
// OAuth2 standard protocol parameters
//
// These should be the plain strings; any needed escaping will be provided by
// the library.
// Request properties
@property (copy) NSString *clientID;
@property (copy) NSString *clientSecret;
@property (copy) NSString *redirectURI;
@property (retain) NSString *scope;
@property (retain) NSString *tokenType;
@property (retain) NSString *assertion;
// Apps may optionally add parameters here to be provided to the token
// endpoint on token requests and refreshes
@property (retain) NSDictionary *additionalTokenRequestParameters;
// Response properties
@property (retain) NSMutableDictionary *parameters;
@property (retain) NSString *accessToken;
@property (retain) NSString *refreshToken;
@property (retain) NSNumber *expiresIn;
@property (retain) NSString *code;
@property (retain) NSString *errorString;
// URL for obtaining access tokens
@property (copy) NSURL *tokenURL;
// Calculated expiration date (expiresIn seconds added to the
// time the access token was received.)
@property (copy) NSDate *expirationDate;
// Service identifier, like "Google"; not used for authentication
//
// The provider name is just for allowing stored authorization to be associated
// with the authorizing service.
@property (copy) NSString *serviceProvider;
// User email and verified status; not used for authentication
//
// The verified string can be checked with -boolValue. If the result is false,
// then the email address is listed with the account on the server, but the
// address has not been confirmed as belonging to the owner of the account.
@property (retain) NSString *userEmail;
@property (retain) NSString *userEmailIsVerified;
// Property indicating if this auth has a refresh token so is suitable for
// authorizing a request. This does not guarantee that the token is valid.
@property (readonly) BOOL canAuthorize;
// Property indicating if this object will authorize plain http request
// (as well as any non-https requests.) Default is NO, only requests with the
// scheme https are authorized, since security may be compromised if tokens
// are sent over the wire using an unencrypted protocol like http.
@property (assign) BOOL shouldAuthorizeAllRequests;
// userData is retained for the convenience of the caller
@property (retain) id userData;
// Stored property values are retained for the convenience of the caller
@property (retain) NSDictionary *properties;
// Property for the optional fetcher service instance to be used to create
// fetchers
//
// Fetcher service objects retain authorizations, so this is weak to avoid
// circular retains.
@property (assign) id <GTMHTTPFetcherServiceProtocol> fetcherService; // WEAK
// Alternative JSON parsing class; this should implement the
// GTMOAuth2ParserClass informal protocol. If this property is
// not set, the class SBJSON must be available in the runtime.
@property (assign) Class parserClass;
// Convenience method for creating an authentication object
+ (id)authenticationWithServiceProvider:(NSString *)serviceProvider
tokenURL:(NSURL *)tokenURL
redirectURI:(NSString *)redirectURI
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret;
// Clear out any authentication values, prepare for a new request fetch
- (void)reset;
// Main authorization entry points
//
// These will refresh the access token, if necessary, add the access token to
// the request, then invoke the callback.
//
// The request argument may be nil to just force a refresh of the access token,
// if needed.
//
// NOTE: To avoid accidental leaks of bearer tokens, the request must
// be for a URL with the scheme https unless the shouldAuthorizeAllRequests
// property is set.
// The finish selector should have a signature matching
// - (void)authentication:(GTMOAuth2Authentication *)auth
// request:(NSMutableURLRequest *)request
// finishedWithError:(NSError *)error;
- (void)authorizeRequest:(NSMutableURLRequest *)request
delegate:(id)delegate
didFinishSelector:(SEL)sel;
#if NS_BLOCKS_AVAILABLE
- (void)authorizeRequest:(NSMutableURLRequest *)request
completionHandler:(void (^)(NSError *error))handler;
#endif
// Synchronous entry point; authorizing this way cannot refresh an expired
// access token
- (BOOL)authorizeRequest:(NSMutableURLRequest *)request;
// If the authentication is waiting for a refresh to complete, spin the run
// loop, discarding events, until the fetch has completed
//
// This is only for use in testing or in tools without a user interface.
- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds;
//////////////////////////////////////////////////////////////////////////////
//
// Internal properties and methods for use by GTMOAuth2SignIn
//
// Pending fetcher to get a new access token, if any
@property (retain) GTMHTTPFetcher *refreshFetcher;
// Check if a request is queued up to be authorized
- (BOOL)isAuthorizingRequest:(NSURLRequest *)request;
// Check if a request appears to be authorized
- (BOOL)isAuthorizedRequest:(NSURLRequest *)request;
// Stop any pending refresh fetch
- (void)stopAuthorization;
// OAuth fetch user-agent header value
- (NSString *)userAgent;
// Parse and set token and token secret from response data
- (void)setKeysForResponseString:(NSString *)str;
- (void)setKeysForResponseDictionary:(NSDictionary *)dict;
// Persistent token string for keychain storage
//
// We'll use the format "refresh_token=foo&serviceProvider=bar" so we can
// easily alter what portions of the auth data are stored
//
// Use these methods for serialization
- (NSString *)persistenceResponseString;
- (void)setKeysForPersistenceResponseString:(NSString *)str;
// method to begin fetching an access token, used by the sign-in object
- (GTMHTTPFetcher *)beginTokenFetchWithDelegate:(id)delegate
didFinishSelector:(SEL)finishedSel;
// Entry point to post a notification about a fetcher currently used for
// obtaining or refreshing a token; the sign-in object will also use this
// to indicate when the user's email address is being fetched.
//
// Fetch type constants are above under "notifications for token fetches"
- (void)notifyFetchIsRunning:(BOOL)isStarting
fetcher:(GTMHTTPFetcher *)fetcher
type:(NSString *)fetchType;
// Arbitrary key-value properties retained for the user
- (void)setProperty:(id)obj forKey:(NSString *)key;
- (id)propertyForKey:(NSString *)key;
//
// Utilities
//
+ (NSString *)encodedOAuthValueForString:(NSString *)str;
+ (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict;
+ (NSDictionary *)dictionaryWithResponseString:(NSString *)responseStr;
+ (NSString *)scopeWithStrings:(NSString *)firsStr, ... NS_REQUIRES_NIL_TERMINATION;
@end
#endif // GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,174 @@
/* Copyright (c) 2011 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 sign-in object opens and closes the web view window as needed for
// users to sign in. For signing in to Google, it also obtains
// the authenticated user's email address.
//
// Typically, this will be managed for the application by
// GTMOAuth2ViewControllerTouch or GTMOAuth2WindowController, so this
// class's interface is interesting only if
// you are creating your own window controller for sign-in.
//
//
// Delegate methods implemented by the window controller
//
// The window controller implements two methods for use by the sign-in object,
// the webRequestSelector and the finishedSelector:
//
// webRequestSelector has a signature matching
// - (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request
//
// The web request selector will be invoked with a request to be displayed, or
// nil to close the window when the final callback request has been encountered.
//
//
// finishedSelector has a signature matching
// - (void)signin:(GTMOAuth2SignIn *)signin finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error
//
// The finished selector will be invoked when sign-in has completed, except
// when explicitly canceled by calling cancelSigningIn
//
#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
// GTMHTTPFetcher brings in GTLDefines/GDataDefines
#import "GTMHTTPFetcher.h"
#import "GTMOAuth2Authentication.h"
@interface GTMOAuth2SignIn : NSObject {
@private
GTMOAuth2Authentication *auth_;
// the endpoint for displaying the sign-in page
NSURL *authorizationURL_;
NSDictionary *additionalAuthorizationParameters_;
id delegate_;
SEL webRequestSelector_;
SEL finishedSelector_;
BOOL hasHandledCallback_;
GTMHTTPFetcher *pendingFetcher_;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
BOOL shouldFetchGoogleUserEmail_;
BOOL shouldFetchGoogleUserProfile_;
NSDictionary *userProfile_;
#endif
SCNetworkReachabilityRef reachabilityRef_;
NSTimer *networkLossTimer_;
NSTimeInterval networkLossTimeoutInterval_;
BOOL hasNotifiedNetworkLoss_;
id userData_;
}
@property (nonatomic, retain) GTMOAuth2Authentication *authentication;
@property (nonatomic, retain) NSURL *authorizationURL;
@property (nonatomic, retain) NSDictionary *additionalAuthorizationParameters;
// The delegate is released when signing in finishes or is cancelled
@property (nonatomic, retain) id delegate;
@property (nonatomic, assign) SEL webRequestSelector;
@property (nonatomic, assign) SEL finishedSelector;
@property (nonatomic, retain) id userData;
// By default, signing in to Google will fetch the user's email, but will not
// fetch the user's profile.
//
// The email is saved in the auth object.
// The profile is available immediately after sign-in.
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
@property (nonatomic, assign) BOOL shouldFetchGoogleUserEmail;
@property (nonatomic, assign) BOOL shouldFetchGoogleUserProfile;
@property (nonatomic, retain, readonly) NSDictionary *userProfile;
#endif
// The default timeout for an unreachable network during display of the
// sign-in page is 30 seconds; set this to 0 to have no timeout
@property (nonatomic, assign) NSTimeInterval networkLossTimeoutInterval;
// The delegate is retained until sign-in has completed or been canceled
//
// designated initializer
- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
delegate:(id)delegate
webRequestSelector:(SEL)webRequestSelector
finishedSelector:(SEL)finishedSelector;
// A default authentication object for signing in to Google services
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (GTMOAuth2Authentication *)standardGoogleAuthenticationForScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret;
#endif
#pragma mark Methods used by the Window Controller
// Start the sequence of fetches and sign-in window display for sign-in
- (BOOL)startSigningIn;
// Stop any pending fetches, and close the window (but don't call the
// delegate's finishedSelector)
- (void)cancelSigningIn;
// Window controllers must tell the sign-in object about any redirect
// requested by the web view, and any changes in the webview window title
//
// If these return YES then the event was handled by the
// sign-in object (typically by closing the window) and should be ignored by
// the window controller's web view
- (BOOL)requestRedirectedToRequest:(NSURLRequest *)redirectedRequest;
- (BOOL)titleChanged:(NSString *)title;
- (BOOL)cookiesChanged:(NSHTTPCookieStorage *)cookieStorage;
- (BOOL)loadFailedWithError:(NSError *)error;
// Window controllers must tell the sign-in object if the window was closed
// prematurely by the user (but not by the sign-in object); this calls the
// delegate's finishedSelector
- (void)windowWasClosed;
#pragma mark -
// Revocation of an authorized token from Google
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth;
#endif
#pragma mark -
// Standard authentication values
+ (NSString *)nativeClientRedirectURI;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (NSURL *)googleAuthorizationURL;
+ (NSURL *)googleTokenURL;
+ (NSURL *)googleUserInfoURL;
#endif
@end
#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES

View File

@ -0,0 +1,814 @@
/* Copyright (c) 2011 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.
*/
#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
#define GTMOAUTH2SIGNIN_DEFINE_GLOBALS 1
#import "GTMOAuth2SignIn.h"
// we'll default to timing out if the network becomes unreachable for more
// than 30 seconds when the sign-in page is displayed
static const NSTimeInterval kDefaultNetworkLossTimeoutInterval = 30.0;
// URI indicating an installed app is signing in. This is described at
//
// http://code.google.com/apis/accounts/docs/OAuth2.html#IA
//
NSString *const kOOBString = @"urn:ietf:wg:oauth:2.0:oob";
@interface GTMOAuth2Authentication (InternalMethods)
- (NSDictionary *)dictionaryWithJSONData:(NSData *)data;
@end
@interface GTMOAuth2SignIn ()
@property (assign) BOOL hasHandledCallback;
@property (retain) GTMHTTPFetcher *pendingFetcher;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
@property (nonatomic, retain, readwrite) NSDictionary *userProfile;
#endif
- (void)invokeFinalCallbackWithError:(NSError *)error;
- (BOOL)startWebRequest;
+ (NSMutableURLRequest *)mutableURLRequestWithURL:(NSURL *)oldURL
paramString:(NSString *)paramStr;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
- (void)fetchGoogleUserInfo;
#endif
- (void)finishSignInWithError:(NSError *)error;
- (void)handleCallbackReached;
- (void)auth:(GTMOAuth2Authentication *)auth
finishedWithFetcher:(GTMHTTPFetcher *)fetcher
error:(NSError *)error;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
- (void)infoFetcher:(GTMHTTPFetcher *)fetcher
finishedWithData:(NSData *)data
error:(NSError *)error;
#endif
- (void)closeTheWindow;
- (void)startReachabilityCheck;
- (void)stopReachabilityCheck;
- (void)reachabilityTarget:(SCNetworkReachabilityRef)reachabilityRef
changedFlags:(SCNetworkConnectionFlags)flags;
- (void)reachabilityTimerFired:(NSTimer *)timer;
@end
@implementation GTMOAuth2SignIn
@synthesize authentication = auth_;
@synthesize authorizationURL = authorizationURL_;
@synthesize additionalAuthorizationParameters = additionalAuthorizationParameters_;
@synthesize delegate = delegate_;
@synthesize webRequestSelector = webRequestSelector_;
@synthesize finishedSelector = finishedSelector_;
@synthesize hasHandledCallback = hasHandledCallback_;
@synthesize pendingFetcher = pendingFetcher_;
@synthesize userData = userData_;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
@synthesize shouldFetchGoogleUserEmail = shouldFetchGoogleUserEmail_;
@synthesize shouldFetchGoogleUserProfile = shouldFetchGoogleUserProfile_;
@synthesize userProfile = userProfile_;
#endif
@synthesize networkLossTimeoutInterval = networkLossTimeoutInterval_;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (NSURL *)googleAuthorizationURL {
NSString *str = @"https://accounts.google.com/o/oauth2/auth";
return [NSURL URLWithString:str];
}
+ (NSURL *)googleTokenURL {
NSString *str = @"https://accounts.google.com/o/oauth2/token";
return [NSURL URLWithString:str];
}
+ (NSURL *)googleRevocationURL {
NSString *urlStr = @"https://accounts.google.com/o/oauth2/revoke";
return [NSURL URLWithString:urlStr];
}
+ (NSURL *)googleUserInfoURL {
NSString *urlStr = @"https://www.googleapis.com/oauth2/v1/userinfo";
return [NSURL URLWithString:urlStr];
}
#endif
+ (NSString *)nativeClientRedirectURI {
return kOOBString;
}
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (GTMOAuth2Authentication *)standardGoogleAuthenticationForScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret {
NSString *redirectURI = [self nativeClientRedirectURI];
NSURL *tokenURL = [self googleTokenURL];
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2Authentication authenticationWithServiceProvider:kGTMOAuth2ServiceProviderGoogle
tokenURL:tokenURL
redirectURI:redirectURI
clientID:clientID
clientSecret:clientSecret];
auth.scope = scope;
return auth;
}
#endif
- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
delegate:(id)delegate
webRequestSelector:(SEL)webRequestSelector
finishedSelector:(SEL)finishedSelector {
// check the selectors on debug builds
GTMAssertSelectorNilOrImplementedWithArgs(delegate, webRequestSelector,
@encode(GTMOAuth2SignIn *), @encode(NSURLRequest *), 0);
GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector,
@encode(GTMOAuth2SignIn *), @encode(GTMOAuth2Authentication *),
@encode(NSError *), 0);
// designated initializer
self = [super init];
if (self) {
auth_ = [auth retain];
authorizationURL_ = [authorizationURL retain];
delegate_ = [delegate retain];
webRequestSelector_ = webRequestSelector;
finishedSelector_ = finishedSelector;
// for Google authentication, we want to automatically fetch user info
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
NSString *host = [authorizationURL host];
if ([host hasSuffix:@".google.com"]) {
shouldFetchGoogleUserEmail_ = YES;
}
#endif
// default timeout for a lost internet connection while the server
// UI is displayed is 30 seconds
networkLossTimeoutInterval_ = kDefaultNetworkLossTimeoutInterval;
}
return self;
}
- (void)dealloc {
[self stopReachabilityCheck];
[auth_ release];
[authorizationURL_ release];
[additionalAuthorizationParameters_ release];
[delegate_ release];
[pendingFetcher_ release];
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
[userProfile_ release];
#endif
[userData_ release];
[super dealloc];
}
#pragma mark Sign-in Sequence Methods
// stop any pending fetches, and close the window (but don't call the
// delegate's finishedSelector)
- (void)cancelSigningIn {
[self.pendingFetcher stopFetching];
self.pendingFetcher = nil;
[self.authentication stopAuthorization];
[self closeTheWindow];
[delegate_ autorelease];
delegate_ = nil;
}
//
// This is the entry point to begin the sequence
// - display the authentication web page, and monitor redirects
// - exchange the code for an access token and a refresh token
// - for Google sign-in, fetch the user's email address
// - tell the delegate we're finished
//
- (BOOL)startSigningIn {
// For signing in to Google, append the scope for obtaining the authenticated
// user email and profile, as appropriate
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
GTMOAuth2Authentication *auth = self.authentication;
if (self.shouldFetchGoogleUserEmail) {
NSString *const emailScope = @"https://www.googleapis.com/auth/userinfo.email";
NSString *scope = auth.scope;
if ([scope rangeOfString:emailScope].location == NSNotFound) {
scope = [GTMOAuth2Authentication scopeWithStrings:scope, emailScope, nil];
auth.scope = scope;
}
}
if (self.shouldFetchGoogleUserProfile) {
NSString *const profileScope = @"https://www.googleapis.com/auth/userinfo.profile";
NSString *scope = auth.scope;
if ([scope rangeOfString:profileScope].location == NSNotFound) {
scope = [GTMOAuth2Authentication scopeWithStrings:scope, profileScope, nil];
auth.scope = scope;
}
}
#endif
// start the authorization
return [self startWebRequest];
}
- (NSMutableDictionary *)parametersForWebRequest {
GTMOAuth2Authentication *auth = self.authentication;
NSString *clientID = auth.clientID;
NSString *redirectURI = auth.redirectURI;
BOOL hasClientID = ([clientID length] > 0);
BOOL hasRedirect = ([redirectURI length] > 0
|| redirectURI == [[self class] nativeClientRedirectURI]);
if (!hasClientID || !hasRedirect) {
#if DEBUG
NSAssert(hasClientID, @"GTMOAuth2SignIn: clientID needed");
NSAssert(hasRedirect, @"GTMOAuth2SignIn: redirectURI needed");
#endif
return NO;
}
// invoke the UI controller's web request selector to display
// the authorization page
// add params to the authorization URL
NSString *scope = auth.scope;
if ([scope length] == 0) scope = nil;
NSMutableDictionary *paramsDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
@"code", @"response_type",
clientID, @"client_id",
scope, @"scope", // scope may be nil
nil];
if (redirectURI) {
[paramsDict setObject:redirectURI forKey:@"redirect_uri"];
}
return paramsDict;
}
- (BOOL)startWebRequest {
NSMutableDictionary *paramsDict = [self parametersForWebRequest];
NSDictionary *additionalParams = self.additionalAuthorizationParameters;
if (additionalParams) {
[paramsDict addEntriesFromDictionary:additionalParams];
}
NSString *paramStr = [GTMOAuth2Authentication encodedQueryParametersForDictionary:paramsDict];
NSURL *authorizationURL = self.authorizationURL;
NSMutableURLRequest *request;
request = [[self class] mutableURLRequestWithURL:authorizationURL
paramString:paramStr];
[delegate_ performSelector:self.webRequestSelector
withObject:self
withObject:request];
// at this point, we're waiting on the server-driven html UI, so
// we want notification if we lose connectivity to the web server
[self startReachabilityCheck];
return YES;
}
// utility for making a request from an old URL with some additional parameters
+ (NSMutableURLRequest *)mutableURLRequestWithURL:(NSURL *)oldURL
paramString:(NSString *)paramStr {
NSString *query = [oldURL query];
if ([query length] > 0) {
query = [query stringByAppendingFormat:@"&%@", paramStr];
} else {
query = paramStr;
}
NSString *portStr = @"";
NSString *oldPort = [[oldURL port] stringValue];
if ([oldPort length] > 0) {
portStr = [@":" stringByAppendingString:oldPort];
}
NSString *qMark = [query length] > 0 ? @"?" : @"";
NSString *newURLStr = [NSString stringWithFormat:@"%@://%@%@%@%@%@",
[oldURL scheme], [oldURL host], portStr,
[oldURL path], qMark, query];
NSURL *newURL = [NSURL URLWithString:newURLStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:newURL];
return request;
}
// entry point for the window controller to tell us that the window
// prematurely closed
- (void)windowWasClosed {
[self stopReachabilityCheck];
NSError *error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
code:kGTMOAuth2ErrorWindowClosed
userInfo:nil];
[self invokeFinalCallbackWithError:error];
}
// internal method to tell the window controller to close the window
- (void)closeTheWindow {
[self stopReachabilityCheck];
// a nil request means the window should be closed
[delegate_ performSelector:self.webRequestSelector
withObject:self
withObject:nil];
}
// entry point for the window controller to tell us what web page has been
// requested
//
// When the request is for the callback URL, this method invokes
// handleCallbackReached and returns YES
- (BOOL)requestRedirectedToRequest:(NSURLRequest *)redirectedRequest {
// for Google's installed app sign-in protocol, we'll look for the
// end-of-sign-in indicator in the titleChanged: method below
NSString *redirectURI = self.authentication.redirectURI;
if (redirectURI == nil) return NO;
// when we're searching for the window title string, then we can ignore
// redirects
NSString *standardURI = [[self class] nativeClientRedirectURI];
if (standardURI != nil && [redirectURI isEqual:standardURI]) return NO;
// compare the redirectURI, which tells us when the web sign-in is done,
// to the actual redirection
NSURL *redirectURL = [NSURL URLWithString:redirectURI];
NSURL *requestURL = [redirectedRequest URL];
// avoid comparing to nil host and path values (such as when redirected to
// "about:blank")
NSString *requestHost = [requestURL host];
NSString *requestPath = [requestURL path];
BOOL isCallback;
if (requestHost && requestPath) {
isCallback = [[redirectURL host] isEqual:[requestURL host]]
&& [[redirectURL path] isEqual:[requestURL path]];
} else if (requestURL) {
// handle "about:blank"
isCallback = [redirectURL isEqual:requestURL];
} else {
isCallback = NO;
}
if (!isCallback) {
// tell the caller that this request is nothing interesting
return NO;
}
// we've reached the callback URL
// try to get the access code
if (!self.hasHandledCallback) {
NSString *responseStr = [[redirectedRequest URL] query];
[self.authentication setKeysForResponseString:responseStr];
#if DEBUG
NSAssert([self.authentication.code length] > 0
|| [self.authentication.errorString length] > 0,
@"response lacks auth code or error");
#endif
[self handleCallbackReached];
}
// tell the delegate that we did handle this request
return YES;
}
// entry point for the window controller to tell us when a new page title has
// been loadded
//
// When the title indicates sign-in has completed, this method invokes
// handleCallbackReached and returns YES
- (BOOL)titleChanged:(NSString *)title {
// return YES if the OAuth flow ending title was detected
// right now we're just looking for a parameter list following the last space
// in the title string, but hopefully we'll eventually get something better
// from the server to search for
NSRange paramsRange = [title rangeOfString:@" "
options:NSBackwardsSearch];
NSUInteger spaceIndex = paramsRange.location;
if (spaceIndex != NSNotFound) {
NSString *responseStr = [title substringFromIndex:(spaceIndex + 1)];
NSDictionary *dict = [GTMOAuth2Authentication dictionaryWithResponseString:responseStr];
NSString *code = [dict objectForKey:@"code"];
NSString *error = [dict objectForKey:@"error"];
if ([code length] > 0 || [error length] > 0) {
if (!self.hasHandledCallback) {
[self.authentication setKeysForResponseDictionary:dict];
[self handleCallbackReached];
}
return YES;
}
}
return NO;
}
- (BOOL)cookiesChanged:(NSHTTPCookieStorage *)cookieStorage {
// We're ignoring these.
return NO;
};
// entry point for the window controller to tell us when a load has failed
// in the webview
//
// if the initial authorization URL fails, bail out so the user doesn't
// see an empty webview
- (BOOL)loadFailedWithError:(NSError *)error {
NSURL *authorizationURL = self.authorizationURL;
NSURL *failedURL = [[error userInfo] valueForKey:@"NSErrorFailingURLKey"]; // NSURLErrorFailingURLErrorKey defined in 10.6
BOOL isAuthURL = [[failedURL host] isEqual:[authorizationURL host]]
&& [[failedURL path] isEqual:[authorizationURL path]];
if (isAuthURL) {
// We can assume that we have no pending fetchers, since we only
// handle failure to load the initial authorization URL
[self closeTheWindow];
[self invokeFinalCallbackWithError:error];
return YES;
}
return NO;
}
- (void)handleCallbackReached {
// the callback page was requested, or the authenticate code was loaded
// into a page's title, so exchange the auth code for access & refresh tokens
// and tell the window to close
// avoid duplicate signals that the callback point has been reached
self.hasHandledCallback = YES;
[self closeTheWindow];
NSError *error = nil;
GTMOAuth2Authentication *auth = self.authentication;
NSString *code = auth.code;
if ([code length] > 0) {
// exchange the code for a token
SEL sel = @selector(auth:finishedWithFetcher:error:);
GTMHTTPFetcher *fetcher = [auth beginTokenFetchWithDelegate:self
didFinishSelector:sel];
if (fetcher == nil) {
error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
code:-1
userInfo:nil];
} else {
self.pendingFetcher = fetcher;
}
// notify the app so it can put up a post-sign in, pre-token exchange UI
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:kGTMOAuth2UserSignedIn
object:self
userInfo:nil];
} else {
// the callback lacked an auth code
NSString *errStr = auth.errorString;
NSDictionary *userInfo = nil;
if ([errStr length] > 0) {
userInfo = [NSDictionary dictionaryWithObject:errStr
forKey:kGTMOAuth2ErrorMessageKey];
}
error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
code:kGTMOAuth2ErrorAuthorizationFailed
userInfo:userInfo];
}
if (error) {
[self finishSignInWithError:error];
}
}
- (void)auth:(GTMOAuth2Authentication *)auth
finishedWithFetcher:(GTMHTTPFetcher *)fetcher
error:(NSError *)error {
self.pendingFetcher = nil;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
if (error == nil
&& (self.shouldFetchGoogleUserEmail || self.shouldFetchGoogleUserProfile)
&& [self.authentication.serviceProvider isEqual:kGTMOAuth2ServiceProviderGoogle]) {
// fetch the user's information from the Google server
[self fetchGoogleUserInfo];
} else {
// we're not authorizing with Google, so we're done
[self finishSignInWithError:error];
}
#else
[self finishSignInWithError:error];
#endif
}
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
- (void)fetchGoogleUserInfo {
// fetch the user's email address
NSURL *infoURL = [[self class] googleUserInfoURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:infoURL];
GTMOAuth2Authentication *auth = self.authentication;
NSString *userAgent = [auth userAgent];
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
[request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"];
// we can do a synchronous authorization since this method is called
// only immediately after a fresh access token has been obtained
[auth authorizeRequest:request];
GTMHTTPFetcher *fetcher;
id <GTMHTTPFetcherServiceProtocol> fetcherService = auth.fetcherService;
if (fetcherService) {
fetcher = [fetcherService fetcherWithRequest:request];
} else {
fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
}
fetcher.retryEnabled = YES;
fetcher.maxRetryInterval = 15.0;
fetcher.comment = @"user info";
[fetcher beginFetchWithDelegate:self
didFinishSelector:@selector(infoFetcher:finishedWithData:error:)];
self.pendingFetcher = fetcher;
[auth notifyFetchIsRunning:YES
fetcher:fetcher
type:kGTMOAuth2FetchTypeUserInfo];
}
- (void)infoFetcher:(GTMHTTPFetcher *)fetcher
finishedWithData:(NSData *)data
error:(NSError *)error {
GTMOAuth2Authentication *auth = self.authentication;
[auth notifyFetchIsRunning:NO
fetcher:fetcher
type:nil];
self.pendingFetcher = nil;
if (error) {
#if DEBUG
if (data) {
NSString *dataStr = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"infoFetcher error: %@\n%@", error, dataStr);
}
#endif
} else {
// We have the authenticated user's info
if (data) {
NSDictionary *profileDict = [auth dictionaryWithJSONData:data];
if (profileDict) {
self.userProfile = profileDict;
// Save the email into the auth object
NSString *email = [profileDict objectForKey:@"email"];
[auth setUserEmail:email];
NSNumber *verified = [profileDict objectForKey:@"verified_email"];
[auth setUserEmailIsVerified:[verified stringValue]];
}
}
}
[self finishSignInWithError:error];
}
#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
- (void)finishSignInWithError:(NSError *)error {
[self invokeFinalCallbackWithError:error];
}
// convenience method for making the final call to our delegate
- (void)invokeFinalCallbackWithError:(NSError *)error {
if (delegate_ && finishedSelector_) {
GTMOAuth2Authentication *auth = self.authentication;
NSMethodSignature *sig = [delegate_ methodSignatureForSelector:finishedSelector_];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:finishedSelector_];
[invocation setTarget:delegate_];
[invocation setArgument:&self atIndex:2];
[invocation setArgument:&auth atIndex:3];
[invocation setArgument:&error atIndex:4];
[invocation invoke];
}
// we'll no longer send messages to the delegate
//
// we want to autorelease it rather than assign to the property in case
// the delegate is below us in the call stack
[delegate_ autorelease];
delegate_ = nil;
}
#pragma mark Reachability monitoring
static void ReachabilityCallBack(SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags,
void *info) {
// pass the flags to the signIn object
GTMOAuth2SignIn *signIn = (GTMOAuth2SignIn *)info;
[signIn reachabilityTarget:target
changedFlags:flags];
}
- (void)startReachabilityCheck {
// the user may set the timeout to 0 to skip the reachability checking
// during display of the sign-in page
if (networkLossTimeoutInterval_ <= 0.0 || reachabilityRef_ != NULL) {
return;
}
// create a reachability target from the authorization URL, add our callback,
// and schedule it on the run loop so we'll be notified if the network drops
NSURL *url = self.authorizationURL;
const char* host = [[url host] UTF8String];
reachabilityRef_ = SCNetworkReachabilityCreateWithName(kCFAllocatorSystemDefault,
host);
if (reachabilityRef_) {
BOOL isScheduled = NO;
SCNetworkReachabilityContext ctx = { 0, self, NULL, NULL, NULL };
if (SCNetworkReachabilitySetCallback(reachabilityRef_,
ReachabilityCallBack, &ctx)) {
if (SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef_,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode)) {
isScheduled = YES;
}
}
if (!isScheduled) {
CFRelease(reachabilityRef_);
reachabilityRef_ = NULL;
}
}
}
- (void)destroyUnreachabilityTimer {
[networkLossTimer_ invalidate];
[networkLossTimer_ autorelease];
networkLossTimer_ = nil;
}
- (void)reachabilityTarget:(SCNetworkReachabilityRef)reachabilityRef
changedFlags:(SCNetworkConnectionFlags)flags {
BOOL isConnected = (flags & kSCNetworkFlagsReachable) != 0
&& (flags & kSCNetworkFlagsConnectionRequired) == 0;
if (isConnected) {
// server is again reachable
[self destroyUnreachabilityTimer];
if (hasNotifiedNetworkLoss_) {
// tell the user that the network has been found
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:kGTMOAuth2NetworkFound
object:self
userInfo:nil];
hasNotifiedNetworkLoss_ = NO;
}
} else {
// the server has become unreachable; start the timer, if necessary
if (networkLossTimer_ == nil
&& networkLossTimeoutInterval_ > 0
&& !hasNotifiedNetworkLoss_) {
SEL sel = @selector(reachabilityTimerFired:);
networkLossTimer_ = [[NSTimer scheduledTimerWithTimeInterval:networkLossTimeoutInterval_
target:self
selector:sel
userInfo:nil
repeats:NO] retain];
}
}
}
- (void)reachabilityTimerFired:(NSTimer *)timer {
// the user may call [[notification object] cancelSigningIn] to
// dismiss the sign-in
if (!hasNotifiedNetworkLoss_) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:kGTMOAuth2NetworkLost
object:self
userInfo:nil];
hasNotifiedNetworkLoss_ = YES;
}
[self destroyUnreachabilityTimer];
}
- (void)stopReachabilityCheck {
[self destroyUnreachabilityTimer];
if (reachabilityRef_) {
SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef_,
CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode);
SCNetworkReachabilitySetCallback(reachabilityRef_, NULL, NULL);
CFRelease(reachabilityRef_);
reachabilityRef_ = NULL;
}
}
#pragma mark Token Revocation
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth {
if (auth.canAuthorize
&& [auth.serviceProvider isEqual:kGTMOAuth2ServiceProviderGoogle]) {
// create a signed revocation request for this authentication object
NSURL *url = [self googleRevocationURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
NSString *token = auth.refreshToken;
NSString *encoded = [GTMOAuth2Authentication encodedOAuthValueForString:token];
NSString *body = [@"token=" stringByAppendingString:encoded];
[request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:@"POST"];
NSString *userAgent = [auth userAgent];
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
// there's nothing to be done if revocation succeeds or fails
GTMHTTPFetcher *fetcher;
id <GTMHTTPFetcherServiceProtocol> fetcherService = auth.fetcherService;
if (fetcherService) {
fetcher = [fetcherService fetcherWithRequest:request];
} else {
fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
}
fetcher.comment = @"revoke token";
// Use a completion handler fetch for better debugging, but only if we're
// guaranteed that blocks are available in the runtime
#if (!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)) || \
(TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000))
// Blocks are available
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
#if DEBUG
if (error) {
NSString *errStr = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"revoke error: %@", errStr);
}
#endif // DEBUG
}];
#else
// Blocks may not be available
[fetcher beginFetchWithDelegate:nil didFinishSelector:NULL];
#endif
}
[auth reset];
}
#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
@end
#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES

View File

@ -0,0 +1,344 @@
/* Copyright (c) 2011 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.
*/
//
// GTMOAuth2ViewControllerTouch.h
//
// This view controller for iPhone handles sign-in via OAuth to Google or
// other services.
//
// This controller is not reusable; create a new instance of this controller
// every time the user will sign in.
//
#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#import "GTMOAuth2Authentication.h"
#undef _EXTERN
#undef _INITIALIZE_AS
#ifdef GTMOAUTH2VIEWCONTROLLERTOUCH_DEFINE_GLOBALS
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
#define _EXTERN extern
#define _INITIALIZE_AS(x)
#endif
_EXTERN NSString* const kGTMOAuth2KeychainErrorDomain _INITIALIZE_AS(@"com.google.GTMOAuthKeychain");
@class GTMOAuth2SignIn;
@class GTMOAuth2ViewControllerTouch;
@interface GTMOAuth2ViewControllerTouch : UIViewController<UINavigationControllerDelegate, UIWebViewDelegate> {
@private
UIButton *backButton_;
UIButton *forwardButton_;
UIView *navButtonsView_;
UIBarButtonItem *rightBarButtonItem_;
UIWebView *webView_;
// The object responsible for the sign-in networking sequence; it holds
// onto the authentication object as well.
GTMOAuth2SignIn *signIn_;
// the page request to load when awakeFromNib occurs
NSURLRequest *request_;
// The user we're calling back
//
// The delegate is retained only until the callback is invoked
// or the sign-in is canceled
id delegate_;
SEL finishedSelector_;
#if NS_BLOCKS_AVAILABLE
void (^completionBlock_)(GTMOAuth2ViewControllerTouch *, GTMOAuth2Authentication *, NSError *);
#endif
NSString *keychainItemName_;
CFTypeRef keychainItemAccessibility_;
// if non-nil, the html string to be displayed immediately upon opening
// of the web view
NSString *initialHTMLString_;
// if non-nil, the URL for which cookies will be deleted when the
// browser view is dismissed
NSURL *browserCookiesURL_;
id userData_;
NSMutableDictionary *properties_;
// We delegate the decision to our owning NavigationController (if any).
// But, the NavigationController will call us back, and ask us.
// BOOL keeps us from infinite looping.
BOOL isInsideShouldAutorotateToInterfaceOrientation_;
// YES, when view first shown in this signIn session.
BOOL isViewShown_;
// YES, after the view has fully transitioned in.
BOOL didViewAppear_;
// YES between sends of start and stop notifications
BOOL hasNotifiedWebViewStartedLoading_;
// To prevent us from calling our delegate's selector more than once.
BOOL hasCalledFinished_;
// Set in a webView callback.
BOOL hasDoneFinalRedirect_;
// Set during the pop initiated by the sign-in object; otherwise,
// viewWillDisappear indicates that some external change of the view
// has stopped the sign-in.
BOOL didDismissSelf_;
}
// the application and service name to use for saving the auth tokens
// to the keychain
@property (nonatomic, copy) NSString *keychainItemName;
// the keychain item accessibility is a system constant for use
// with kSecAttrAccessible.
//
// Since it's a system constant, we do not need to retain it.
@property (nonatomic, assign) CFTypeRef keychainItemAccessibility;
// optional html string displayed immediately upon opening the web view
//
// This string is visible just until the sign-in web page loads, and
// may be used for a "Loading..." type of message or to set the
// initial view color
@property (nonatomic, copy) NSString *initialHTMLString;
// the underlying object to hold authentication tokens and authorize http
// requests
@property (nonatomic, retain, readonly) GTMOAuth2Authentication *authentication;
// the underlying object which performs the sign-in networking sequence
@property (nonatomic, retain, readonly) GTMOAuth2SignIn *signIn;
// user interface elements
@property (nonatomic, retain) IBOutlet UIButton *backButton;
@property (nonatomic, retain) IBOutlet UIButton *forwardButton;
@property (nonatomic, retain) IBOutlet UIView *navButtonsView;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *rightBarButtonItem;
@property (nonatomic, retain) IBOutlet UIWebView *webView;
// the default timeout for an unreachable network during display of the
// sign-in page is 10 seconds; set this to 0 to have no timeout
@property (nonatomic, assign) NSTimeInterval networkLossTimeoutInterval;
// if set, cookies are deleted for this URL when the view is hidden
//
// For Google sign-ins, this is set by default to https://google.com/accounts
// but it may be explicitly set to nil to disable clearing of browser cookies
@property (nonatomic, retain) NSURL *browserCookiesURL;
// userData is retained for the convenience of the caller
@property (nonatomic, retain) id userData;
// Stored property values are retained for the convenience of the caller
- (void)setProperty:(id)obj forKey:(NSString *)key;
- (id)propertyForKey:(NSString *)key;
@property (nonatomic, retain) NSDictionary *properties;
// Method for creating a controller to authenticate to Google services
//
// scope is the requested scope of authorization
// (like "http://www.google.com/m8/feeds")
//
// keychain item name is used for storing the token on the keychain,
// keychainItemName should be like "My Application: Google Latitude"
// (or set to nil if no persistent keychain storage is desired)
//
// the delegate is retained only until the finished selector is invoked
// or the sign-in is canceled
//
// If you don't like the default nibName and bundle, you can change them
// using the UIViewController properties once you've made one of these.
//
// finishedSelector is called after authentication completes. It should follow
// this signature.
//
// - (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
// finishedWithAuth:(GTMOAuth2Authentication *)auth
// error:(NSError *)error;
//
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (id)controllerWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector;
- (id)initWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector;
#if NS_BLOCKS_AVAILABLE
+ (id)controllerWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler;
- (id)initWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler;
#endif
#endif
// Create a controller for authenticating to non-Google services, taking
// explicit endpoint URLs and an authentication object
+ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName // may be nil
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector;
// This is the designated initializer
- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector;
#if NS_BLOCKS_AVAILABLE
+ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName // may be nil
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler;
- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler;
#endif
// Override default in UIViewController. If we have a navigationController, ask
// it. else default result (i.e., Portrait mode only).
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
// subclasses may override authNibName to specify a custom name
+ (NSString *)authNibName;
// subclasses may override authNibBundle to specify a custom bundle
+ (NSBundle *)authNibBundle;
// apps may replace the sign-in class with their own subclass of it
+ (Class)signInClass;
+ (void)setSignInClass:(Class)theClass;
- (void)cancelSigningIn;
// revocation of an authorized token from Google
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth;
#endif
//
// Keychain
//
// create an authentication object for Google services from the access
// token and secret stored in the keychain; if no token is available, return
// an unauthorized auth object
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret;
#endif
// add tokens from the keychain, if available, to the authentication object
//
// returns YES if the authentication object was authorized from the keychain
+ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
authentication:(GTMOAuth2Authentication *)auth;
// method for deleting the stored access token and secret, useful for "signing
// out"
+ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName;
// method for saving the stored access token and secret
+ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
accessibility:(CFTypeRef)accessibility
authentication:(GTMOAuth2Authentication *)auth;
// older version, defaults to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
+ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
authentication:(GTMOAuth2Authentication *)auth;
@end
// To function, GTMOAuth2ViewControllerTouch needs a certain amount of access
// to the iPhone's keychain. To keep things simple, its keychain access is
// broken out into a helper class. We declare it here in case you'd like to use
// it too, to store passwords.
enum {
kGTMOAuth2KeychainErrorBadArguments = -1301,
kGTMOAuth2KeychainErrorNoPassword = -1302
};
@interface GTMOAuth2Keychain : NSObject
+ (GTMOAuth2Keychain *)defaultKeychain;
// OK to pass nil for the error parameter.
- (NSString *)passwordForService:(NSString *)service
account:(NSString *)account
error:(NSError **)error;
// OK to pass nil for the error parameter.
- (BOOL)removePasswordForService:(NSString *)service
account:(NSString *)account
error:(NSError **)error;
// OK to pass nil for the error parameter.
//
// accessibility should be one of the constants for kSecAttrAccessible
// such as kSecAttrAccessibleWhenUnlocked
- (BOOL)setPassword:(NSString *)password
forService:(NSString *)service
accessibility:(CFTypeRef)accessibility
account:(NSString *)account
error:(NSError **)error;
// For unit tests: allow setting a mock object
+ (void)setDefaultKeychain:(GTMOAuth2Keychain *)keychain;
@end
#endif // TARGET_OS_IPHONE
#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES

View File

@ -0,0 +1,970 @@
/* Copyright (c) 2011 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.
*/
//
// GTMOAuth2ViewControllerTouch.m
//
#import <Foundation/Foundation.h>
#import <Security/Security.h>
#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
#if TARGET_OS_IPHONE
#define GTMOAUTH2VIEWCONTROLLERTOUCH_DEFINE_GLOBALS 1
#import "GTMOAuth2ViewControllerTouch.h"
#import "GTMOAuth2SignIn.h"
#import "GTMOAuth2Authentication.h"
static NSString * const kGTMOAuth2AccountName = @"OAuth";
static GTMOAuth2Keychain* sDefaultKeychain = nil;
@interface GTMOAuth2ViewControllerTouch()
@property (nonatomic, copy) NSURLRequest *request;
- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request;
- (void)signIn:(GTMOAuth2SignIn *)signIn
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error;
- (BOOL)isNavigationBarTranslucent;
- (void)moveWebViewFromUnderNavigationBar;
- (void)popView;
- (void)clearBrowserCookies;
@end
@implementation GTMOAuth2ViewControllerTouch
// IBOutlets
@synthesize request = request_,
backButton = backButton_,
forwardButton = forwardButton_,
navButtonsView = navButtonsView_,
rightBarButtonItem = rightBarButtonItem_,
webView = webView_;
@synthesize keychainItemName = keychainItemName_,
keychainItemAccessibility = keychainItemAccessibility_,
initialHTMLString = initialHTMLString_,
browserCookiesURL = browserCookiesURL_,
signIn = signIn_,
userData = userData_,
properties = properties_;
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (id)controllerWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector {
return [[[self alloc] initWithScope:scope
clientID:clientID
clientSecret:clientSecret
keychainItemName:keychainItemName
delegate:delegate
finishedSelector:finishedSelector] autorelease];
}
- (id)initWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector {
// convenient entry point for Google authentication
Class signInClass = [[self class] signInClass];
GTMOAuth2Authentication *auth;
auth = [signInClass standardGoogleAuthenticationForScope:scope
clientID:clientID
clientSecret:clientSecret];
NSURL *authorizationURL = [signInClass googleAuthorizationURL];
return [self initWithAuthentication:auth
authorizationURL:authorizationURL
keychainItemName:keychainItemName
delegate:delegate
finishedSelector:finishedSelector];
}
#if NS_BLOCKS_AVAILABLE
+ (id)controllerWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler {
return [[[self alloc] initWithScope:scope
clientID:clientID
clientSecret:clientSecret
keychainItemName:keychainItemName
completionHandler:handler] autorelease];
}
- (id)initWithScope:(NSString *)scope
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret
keychainItemName:(NSString *)keychainItemName
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler {
// convenient entry point for Google authentication
Class signInClass = [[self class] signInClass];
GTMOAuth2Authentication *auth;
auth = [signInClass standardGoogleAuthenticationForScope:scope
clientID:clientID
clientSecret:clientSecret];
NSURL *authorizationURL = [signInClass googleAuthorizationURL];
self = [self initWithAuthentication:auth
authorizationURL:authorizationURL
keychainItemName:keychainItemName
delegate:nil
finishedSelector:NULL];
if (self) {
completionBlock_ = [handler copy];
}
return self;
}
#endif // NS_BLOCKS_AVAILABLE
#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector {
return [[[self alloc] initWithAuthentication:auth
authorizationURL:authorizationURL
keychainItemName:keychainItemName
delegate:delegate
finishedSelector:finishedSelector] autorelease];
}
- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName
delegate:(id)delegate
finishedSelector:(SEL)finishedSelector {
NSString *nibName = [[self class] authNibName];
NSBundle *nibBundle = [[self class] authNibBundle];
self = [super initWithNibName:nibName bundle:nibBundle];
if (self != nil) {
delegate_ = [delegate retain];
finishedSelector_ = finishedSelector;
Class signInClass = [[self class] signInClass];
// use the supplied auth and OAuth endpoint URLs
signIn_ = [[signInClass alloc] initWithAuthentication:auth
authorizationURL:authorizationURL
delegate:self
webRequestSelector:@selector(signIn:displayRequest:)
finishedSelector:@selector(signIn:finishedWithAuth:error:)];
// if the user is signing in to a Google service, we'll delete the
// Google authentication browser cookies upon completion
//
// for other service domains, or to disable clearing of the cookies,
// set the browserCookiesURL property explicitly
NSString *authorizationHost = [signIn_.authorizationURL host];
if ([authorizationHost hasSuffix:@".google.com"]) {
NSString *urlStr = [NSString stringWithFormat:@"https://%@/",
authorizationHost];
NSURL *cookiesURL = [NSURL URLWithString:urlStr];
[self setBrowserCookiesURL:cookiesURL];
}
[self setKeychainItemName:keychainItemName];
}
return self;
}
#if NS_BLOCKS_AVAILABLE
+ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler {
return [[[self alloc] initWithAuthentication:auth
authorizationURL:authorizationURL
keychainItemName:keychainItemName
completionHandler:handler] autorelease];
}
- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
authorizationURL:(NSURL *)authorizationURL
keychainItemName:(NSString *)keychainItemName
completionHandler:(void (^)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error))handler {
// fall back to the non-blocks init
self = [self initWithAuthentication:auth
authorizationURL:authorizationURL
keychainItemName:keychainItemName
delegate:nil
finishedSelector:NULL];
if (self) {
completionBlock_ = [handler copy];
}
return self;
}
#endif
- (void)dealloc {
[webView_ setDelegate:nil];
[backButton_ release];
[forwardButton_ release];
[navButtonsView_ release];
[rightBarButtonItem_ release];
[webView_ release];
[signIn_ release];
[request_ release];
[delegate_ release];
#if NS_BLOCKS_AVAILABLE
[completionBlock_ release];
#endif
[keychainItemName_ release];
[initialHTMLString_ release];
[browserCookiesURL_ release];
[userData_ release];
[properties_ release];
[super dealloc];
}
+ (NSString *)authNibName {
// subclasses may override this to specify a custom nib name
return @"GTMOAuth2ViewTouch";
}
+ (NSBundle *)authNibBundle {
// subclasses may override this to specify a custom nib bundle
return nil;
}
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret {
Class signInClass = [self signInClass];
NSURL *tokenURL = [signInClass googleTokenURL];
NSString *redirectURI = [signInClass nativeClientRedirectURI];
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2Authentication authenticationWithServiceProvider:kGTMOAuth2ServiceProviderGoogle
tokenURL:tokenURL
redirectURI:redirectURI
clientID:clientID
clientSecret:clientSecret];
[[self class] authorizeFromKeychainForName:keychainItemName
authentication:auth];
return auth;
}
#endif
+ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
authentication:(GTMOAuth2Authentication *)newAuth {
newAuth.accessToken = nil;
BOOL didGetTokens = NO;
GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
NSString *password = [keychain passwordForService:keychainItemName
account:kGTMOAuth2AccountName
error:nil];
if (password != nil) {
[newAuth setKeysForResponseString:password];
didGetTokens = YES;
}
return didGetTokens;
}
+ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName {
GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
return [keychain removePasswordForService:keychainItemName
account:kGTMOAuth2AccountName
error:nil];
}
+ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
authentication:(GTMOAuth2Authentication *)auth {
return [self saveParamsToKeychainForName:keychainItemName
accessibility:NULL
authentication:auth];
}
+ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
accessibility:(CFTypeRef)accessibility
authentication:(GTMOAuth2Authentication *)auth {
[self removeAuthFromKeychainForName:keychainItemName];
// don't save unless we have a token that can really authorize requests
if (![auth canAuthorize]) return NO;
if (accessibility == NULL
&& &kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly != NULL) {
accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
}
// make a response string containing the values we want to save
NSString *password = [auth persistenceResponseString];
GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
return [keychain setPassword:password
forService:keychainItemName
accessibility:accessibility
account:kGTMOAuth2AccountName
error:nil];
}
- (void)loadView {
NSString *nibPath = nil;
NSBundle *nibBundle = [self nibBundle];
if (nibBundle == nil) {
nibBundle = [NSBundle mainBundle];
}
NSString *nibName = self.nibName;
if (nibName != nil) {
nibPath = [nibBundle pathForResource:nibName ofType:@"nib"];
}
if (nibPath != nil && [[NSFileManager defaultManager] fileExistsAtPath:nibPath]) {
[super loadView];
} else {
// One of the requirements of loadView is that a valid view object is set to
// self.view upon completion. Otherwise, subclasses that attempt to
// access self.view after calling [super loadView] will enter an infinite
// loop due to the fact that UIViewController's -view accessor calls
// loadView when self.view is nil.
self.view = [[[UIView alloc] init] autorelease];
#if DEBUG
NSLog(@"missing %@.nib", nibName);
#endif
}
}
- (void)viewDidLoad {
// the app may prefer some html other than blank white to be displayed
// before the sign-in web page loads
NSString *html = self.initialHTMLString;
if ([html length] > 0) {
[[self webView] loadHTMLString:html baseURL:nil];
}
rightBarButtonItem_.customView = navButtonsView_;
self.navigationItem.rightBarButtonItem = rightBarButtonItem_;
}
- (void)popView {
if (self.navigationController.topViewController == self) {
if (!self.view.isHidden) {
// Set the flag to our viewWillDisappear method so it knows
// this is a disappearance initiated by the sign-in object,
// not the user cancelling via the navigation controller
didDismissSelf_ = YES;
[self.navigationController popViewControllerAnimated:YES];
self.view.hidden = YES;
}
}
}
- (void)notifyWithName:(NSString *)name
webView:(UIWebView *)webView
kind:(NSString *)kind {
BOOL isStarting = [name isEqual:kGTMOAuth2WebViewStartedLoading];
if (hasNotifiedWebViewStartedLoading_ == isStarting) {
// Duplicate notification
//
// UIWebView's delegate methods are so unbalanced that there's little
// point trying to keep a count, as it could easily end up stuck greater
// than zero.
//
// We don't really have a way to track the starts and stops of
// subframe loads, too, as the webView in the notification is always
// for the topmost request.
return;
}
hasNotifiedWebViewStartedLoading_ = isStarting;
// Notification for webview load starting and stopping
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
webView, kGTMOAuth2WebViewKey,
kind, kGTMOAuth2WebViewStopKindKey, // kind may be nil
nil];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName:name
object:self
userInfo:dict];
}
- (void)cancelSigningIn {
// The application has explicitly asked us to cancel signing in
// (so no further callback is required)
hasCalledFinished_ = YES;
[delegate_ autorelease];
delegate_ = nil;
#if NS_BLOCKS_AVAILABLE
[completionBlock_ autorelease];
completionBlock_ = nil;
#endif
// The sign-in object's cancel method will close the window
[signIn_ cancelSigningIn];
hasDoneFinalRedirect_ = YES;
}
static Class gSignInClass = Nil;
+ (Class)signInClass {
if (gSignInClass == Nil) {
gSignInClass = [GTMOAuth2SignIn class];
}
return gSignInClass;
}
+ (void)setSignInClass:(Class)theClass {
gSignInClass = theClass;
}
#pragma mark Token Revocation
#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth {
[[self signInClass] revokeTokenForGoogleAuthentication:auth];
}
#endif
#pragma mark Browser Cookies
- (GTMOAuth2Authentication *)authentication {
return self.signIn.authentication;
}
- (void)clearBrowserCookies {
// if browserCookiesURL is non-nil, then get cookies for that URL
// and delete them from the common application cookie storage
NSURL *cookiesURL = [self browserCookiesURL];
if (cookiesURL) {
NSHTTPCookieStorage *cookieStorage;
cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookies = [cookieStorage cookiesForURL:cookiesURL];
for (NSHTTPCookie *cookie in cookies) {
[cookieStorage deleteCookie:cookie];
}
}
}
#pragma mark Accessors
- (void)setNetworkLossTimeoutInterval:(NSTimeInterval)val {
signIn_.networkLossTimeoutInterval = val;
}
- (NSTimeInterval)networkLossTimeoutInterval {
return signIn_.networkLossTimeoutInterval;
}
- (BOOL)shouldUseKeychain {
NSString *name = self.keychainItemName;
return ([name length] > 0);
}
#pragma mark User Properties
- (void)setProperty:(id)obj forKey:(NSString *)key {
if (obj == nil) {
// User passed in nil, so delete the property
[properties_ removeObjectForKey:key];
} else {
// Be sure the property dictionary exists
if (properties_ == nil) {
[self setProperties:[NSMutableDictionary dictionary]];
}
[properties_ setObject:obj forKey:key];
}
}
- (id)propertyForKey:(NSString *)key {
id obj = [properties_ objectForKey:key];
// Be sure the returned pointer has the life of the autorelease pool,
// in case self is released immediately
return [[obj retain] autorelease];
}
#pragma mark SignIn callbacks
- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request {
// this is the signIn object's webRequest method, telling the controller
// to either display the request in the webview, or close the window
//
// All web requests and all window closing goes through this routine
#if DEBUG
if (self.navigationController) {
if (self.navigationController.topViewController != self && request != nil) {
NSLog(@"Unexpected: Request to show, when already on top. request %@", [request URL]);
} else if(self.navigationController.topViewController != self && request == nil) {
NSLog(@"Unexpected: Request to pop, when not on top. request nil");
}
}
#endif
if (request != nil) {
const NSTimeInterval kJanuary2011 = 1293840000;
BOOL isDateValid = ([[NSDate date] timeIntervalSince1970] > kJanuary2011);
if (isDateValid) {
// Display the request.
self.request = request;
BOOL shouldWaitForHTML = ([self.initialHTMLString length] > 0);
if (shouldWaitForHTML) {
[self.webView performSelector:@selector(loadRequest:)
withObject:request
afterDelay:0.05];
} else {
[self.webView loadRequest:request];
}
} else {
// clock date is invalid, so signing in would fail with an unhelpful error
// from the server. Warn the user in an html string showing a watch icon,
// question mark, and the system date and time. Hopefully this will clue
// in brighter users, or at least give them a clue when they report the
// problem to developers.
//
// Even better is for apps to check the system clock and show some more
// helpful, localized instructions for users; this is really a fallback.
NSString *html = @"<html><body><div align=center><font size='7'>"
@"&#x231A; ?<br><i>System Clock Incorrect</i><br>%@"
@"</font></div></body></html>";
NSString *errHTML = [NSString stringWithFormat:html, [NSDate date]];
[[self webView] loadHTMLString:errHTML baseURL:nil];
}
} else {
// request was nil.
[self popView];
}
}
- (void)signIn:(GTMOAuth2SignIn *)signIn
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error {
if (!hasCalledFinished_) {
hasCalledFinished_ = YES;
if (error == nil) {
if (self.shouldUseKeychain) {
NSString *keychainItemName = self.keychainItemName;
if (auth.canAuthorize) {
// save the auth params in the keychain
CFTypeRef accessibility = self.keychainItemAccessibility;
[[self class] saveParamsToKeychainForName:keychainItemName
accessibility:accessibility
authentication:auth];
} else {
// remove the auth params from the keychain
[[self class] removeAuthFromKeychainForName:keychainItemName];
}
}
}
if (delegate_ && finishedSelector_) {
SEL sel = finishedSelector_;
NSMethodSignature *sig = [delegate_ methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:sel];
[invocation setTarget:delegate_];
[invocation setArgument:&self atIndex:2];
[invocation setArgument:&auth atIndex:3];
[invocation setArgument:&error atIndex:4];
[invocation invoke];
}
[delegate_ autorelease];
delegate_ = nil;
#if NS_BLOCKS_AVAILABLE
if (completionBlock_) {
completionBlock_(self, auth, error);
// release the block here to avoid a retain loop on the controller
[completionBlock_ autorelease];
completionBlock_ = nil;
}
#endif
}
}
- (void)moveWebViewFromUnderNavigationBar {
CGRect dontCare;
CGRect webFrame = self.view.bounds;
UINavigationBar *navigationBar = self.navigationController.navigationBar;
CGRectDivide(webFrame, &dontCare, &webFrame,
navigationBar.frame.size.height, CGRectMinYEdge);
[self.webView setFrame:webFrame];
}
// isTranslucent is defined in iPhoneOS 3.0 on.
- (BOOL)isNavigationBarTranslucent {
UINavigationBar *navigationBar = [[self navigationController] navigationBar];
BOOL isTranslucent =
([navigationBar respondsToSelector:@selector(isTranslucent)] &&
[navigationBar isTranslucent]);
return isTranslucent;
}
#pragma mark -
#pragma mark Protocol implementations
- (void)viewWillAppear:(BOOL)animated {
if (!isViewShown_) {
isViewShown_ = YES;
if ([self isNavigationBarTranslucent]) {
[self moveWebViewFromUnderNavigationBar];
}
if (![signIn_ startSigningIn]) {
// Can't start signing in. We must pop our view.
// UIWebview needs time to stabilize. Animations need time to complete.
// We remove ourself from the view stack after that.
[self performSelector:@selector(popView)
withObject:nil
afterDelay:0.5
inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
}
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
didViewAppear_ = YES;
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
if (!didDismissSelf_) {
// We won't receive further webview delegate messages, so be sure the
// started loading notification is balanced, if necessary
[self notifyWithName:kGTMOAuth2WebViewStoppedLoading
webView:self.webView
kind:kGTMOAuth2WebViewCancelled];
// We are not popping ourselves, so presumably we are being popped by the
// navigation controller; tell the sign-in object to close up shop
//
// this will indirectly call our signIn:finishedWithAuth:error: method
// for us
[signIn_ windowWasClosed];
}
// prevent the next sign-in from showing in the WebView that the user is
// already signed in
[self clearBrowserCookies];
[super viewWillDisappear:animated];
}
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
if (!hasDoneFinalRedirect_) {
hasDoneFinalRedirect_ = [signIn_ requestRedirectedToRequest:request];
if (hasDoneFinalRedirect_) {
// signIn has told the view to close
return NO;
}
}
return YES;
}
- (void)updateUI {
[backButton_ setEnabled:[[self webView] canGoBack]];
[forwardButton_ setEnabled:[[self webView] canGoForward]];
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
[self notifyWithName:kGTMOAuth2WebViewStartedLoading
webView:webView
kind:nil];
[self updateUI];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self notifyWithName:kGTMOAuth2WebViewStoppedLoading
webView:webView
kind:kGTMOAuth2WebViewFinished];
NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
if ([title length] > 0) {
[signIn_ titleChanged:title];
} else {
#if DEBUG
// Verify that Javascript is enabled
NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"1+1"];
NSAssert([result integerValue] == 2, @"GTMOAuth2: Javascript is required");
#endif
}
[signIn_ cookiesChanged:[NSHTTPCookieStorage sharedHTTPCookieStorage]];
[self updateUI];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[self notifyWithName:kGTMOAuth2WebViewStoppedLoading
webView:webView
kind:kGTMOAuth2WebViewFailed];
// Tell the sign-in object that a load failed; if it was the authorization
// URL, it will pop the view and return an error to the delegate.
if (didViewAppear_) {
BOOL isUserInterruption = ([error code] == NSURLErrorCancelled
&& [[error domain] isEqual:NSURLErrorDomain]);
if (isUserInterruption) {
// Ignore this error:
// Users report that this error occurs when clicking too quickly on the
// accept button, before the page has completely loaded. Ignoring
// this error seems to provide a better experience than does immediately
// cancelling sign-in.
//
// This error also occurs whenever UIWebView is sent the stopLoading
// message, so if we ever send that message intentionally, we need to
// revisit this bypass.
return;
}
[signIn_ loadFailedWithError:error];
} else {
// UIWebview needs time to stabilize. Animations need time to complete.
[signIn_ performSelector:@selector(loadFailedWithError:)
withObject:error
afterDelay:0.5
inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
BOOL value = YES;
if (!isInsideShouldAutorotateToInterfaceOrientation_) {
isInsideShouldAutorotateToInterfaceOrientation_ = YES;
UIViewController *navigationController = [self navigationController];
if (navigationController != nil) {
value = [navigationController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
} else {
value = [super shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
isInsideShouldAutorotateToInterfaceOrientation_ = NO;
}
return value;
}
@end
#pragma mark Common Code
@implementation GTMOAuth2Keychain
+ (GTMOAuth2Keychain *)defaultKeychain {
if (sDefaultKeychain == nil) {
sDefaultKeychain = [[self alloc] init];
}
return sDefaultKeychain;
}
// For unit tests: allow setting a mock object
+ (void)setDefaultKeychain:(GTMOAuth2Keychain *)keychain {
if (sDefaultKeychain != keychain) {
[sDefaultKeychain release];
sDefaultKeychain = [keychain retain];
}
}
- (NSString *)keyForService:(NSString *)service account:(NSString *)account {
return [NSString stringWithFormat:@"com.google.GTMOAuth.%@%@", service, account];
}
// The Keychain API isn't available on the iPhone simulator in SDKs before 3.0,
// so, on early simulators we use a fake API, that just writes, unencrypted, to
// NSUserDefaults.
#if TARGET_IPHONE_SIMULATOR && __IPHONE_OS_VERSION_MAX_ALLOWED < 30000
#pragma mark Simulator
// Simulator - just simulated, not secure.
- (NSString *)passwordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
NSString *result = nil;
if (0 < [service length] && 0 < [account length]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *key = [self keyForService:service account:account];
result = [defaults stringForKey:key];
if (result == nil && error != NULL) {
*error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
code:kGTMOAuth2KeychainErrorNoPassword
userInfo:nil];
}
} else if (error != NULL) {
*error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
code:kGTMOAuth2KeychainErrorBadArguments
userInfo:nil];
}
return result;
}
// Simulator - just simulated, not secure.
- (BOOL)removePasswordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
BOOL didSucceed = NO;
if (0 < [service length] && 0 < [account length]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *key = [self keyForService:service account:account];
[defaults removeObjectForKey:key];
[defaults synchronize];
} else if (error != NULL) {
*error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
code:kGTMOAuth2KeychainErrorBadArguments
userInfo:nil];
}
return didSucceed;
}
// Simulator - just simulated, not secure.
- (BOOL)setPassword:(NSString *)password
forService:(NSString *)service
accessibility:(CFTypeRef)accessibility
account:(NSString *)account
error:(NSError **)error {
BOOL didSucceed = NO;
if (0 < [password length] && 0 < [service length] && 0 < [account length]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *key = [self keyForService:service account:account];
[defaults setObject:password forKey:key];
[defaults synchronize];
didSucceed = YES;
} else if (error != NULL) {
*error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
code:kGTMOAuth2KeychainErrorBadArguments
userInfo:nil];
}
return didSucceed;
}
#else // ! TARGET_IPHONE_SIMULATOR
#pragma mark Device
+ (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword, (id)kSecClass,
@"OAuth", (id)kSecAttrGeneric,
account, (id)kSecAttrAccount,
service, (id)kSecAttrService,
nil];
return query;
}
- (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
return [[self class] keychainQueryForService:service account:account];
}
// iPhone
- (NSString *)passwordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
NSString *result = nil;
if (0 < [service length] && 0 < [account length]) {
CFDataRef passwordData = NULL;
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
status = SecItemCopyMatching((CFDictionaryRef)keychainQuery,
(CFTypeRef *)&passwordData);
if (status == noErr && 0 < [(NSData *)passwordData length]) {
result = [[[NSString alloc] initWithData:(NSData *)passwordData
encoding:NSUTF8StringEncoding] autorelease];
}
if (passwordData != NULL) {
CFRelease(passwordData);
}
}
if (status != noErr && error != NULL) {
*error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
code:status
userInfo:nil];
}
return result;
}
// iPhone
- (BOOL)removePasswordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
if (0 < [service length] && 0 < [account length]) {
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
status = SecItemDelete((CFDictionaryRef)keychainQuery);
}
if (status != noErr && error != NULL) {
*error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
code:status
userInfo:nil];
}
return status == noErr;
}
// iPhone
- (BOOL)setPassword:(NSString *)password
forService:(NSString *)service
accessibility:(CFTypeRef)accessibility
account:(NSString *)account
error:(NSError **)error {
OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
if (0 < [service length] && 0 < [account length]) {
[self removePasswordForService:service account:account error:nil];
if (0 < [password length]) {
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[keychainQuery setObject:passwordData forKey:(id)kSecValueData];
if (accessibility != NULL && &kSecAttrAccessible != NULL) {
[keychainQuery setObject:(id)accessibility
forKey:(id)kSecAttrAccessible];
}
status = SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
}
if (status != noErr && error != NULL) {
*error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
code:status
userInfo:nil];
}
return status == noErr;
}
#endif // ! TARGET_IPHONE_SIMULATOR
@end
#endif // TARGET_OS_IPHONE
#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES

View File

@ -0,0 +1,456 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">768</int>
<string key="IBDocument.SystemVersion">10J869</string>
<string key="IBDocument.InterfaceBuilderVersion">851</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">141</string>
</object>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="4"/>
<integer value="15"/>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBProxyObject" id="372490531">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="975951072">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUINavigationItem" id="1047805472">
<string key="IBUITitle">OAuth</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIBarButtonItem" id="961671599">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIStyle">1</int>
</object>
<object class="IBUIView" id="808907889">
<reference key="NSNextResponder"/>
<int key="NSvFlags">292</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIButton" id="453250804">
<reference key="NSNextResponder" ref="808907889"/>
<int key="NSvFlags">292</int>
<string key="NSFrameSize">{30, 30}</string>
<reference key="NSSuperview" ref="808907889"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentHorizontalAlignment">0</int>
<int key="IBUIContentVerticalAlignment">0</int>
<object class="NSFont" key="IBUIFont" id="530402572">
<string key="NSName">Helvetica-Bold</string>
<double key="NSSize">24</double>
<int key="NSfFlags">16</int>
</object>
<string key="IBUITitleShadowOffset">{0, -2}</string>
<string key="IBUINormalTitle">◀</string>
<object class="NSColor" key="IBUIHighlightedTitleColor" id="193465259">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
<object class="NSColor" key="IBUIDisabledTitleColor">
<int key="NSColorSpace">2</int>
<bytes key="NSRGB">MC41OTYwNzg0NiAwLjY4NjI3NDUzIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
</object>
<reference key="IBUINormalTitleColor" ref="193465259"/>
<object class="NSColor" key="IBUINormalTitleShadowColor" id="999379443">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC41AA</bytes>
</object>
</object>
<object class="IBUIButton" id="981703116">
<reference key="NSNextResponder" ref="808907889"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{30, 0}, {30, 30}}</string>
<reference key="NSSuperview" ref="808907889"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentHorizontalAlignment">0</int>
<int key="IBUIContentVerticalAlignment">0</int>
<reference key="IBUIFont" ref="530402572"/>
<string key="IBUITitleShadowOffset">{0, -2}</string>
<string key="IBUINormalTitle">▶</string>
<reference key="IBUIHighlightedTitleColor" ref="193465259"/>
<object class="NSColor" key="IBUIDisabledTitleColor">
<int key="NSColorSpace">2</int>
<bytes key="NSRGB">MC41ODQzMTM3NSAwLjY3NDUwOTgyIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
</object>
<reference key="IBUINormalTitleColor" ref="193465259"/>
<reference key="IBUINormalTitleShadowColor" ref="999379443"/>
</object>
</object>
<string key="NSFrameSize">{60, 30}</string>
<reference key="NSSuperview"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MSAwAA</bytes>
</object>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
<int key="interfaceOrientation">3</int>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIView" id="426018584">
<reference key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBUIWebView" id="663477729">
<reference key="NSNextResponder" ref="426018584"/>
<int key="NSvFlags">274</int>
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview" ref="426018584"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MSAxIDEAA</bytes>
</object>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIDataDetectorTypes">1</int>
<bool key="IBUIDetectsPhoneNumbers">YES</bool>
</object>
</object>
<string key="NSFrameSize">{320, 460}</string>
<reference key="NSSuperview"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
<object class="NSColorSpace" key="NSCustomColorSpace">
<int key="NSID">2</int>
</object>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="663477729"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">9</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">rightBarButtonItem</string>
<reference key="source" ref="1047805472"/>
<reference key="destination" ref="961671599"/>
</object>
<int key="connectionID">14</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">goBack</string>
<reference key="source" ref="453250804"/>
<reference key="destination" ref="663477729"/>
<int key="IBEventType">7</int>
</object>
<int key="connectionID">18</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">goForward</string>
<reference key="source" ref="981703116"/>
<reference key="destination" ref="663477729"/>
<int key="IBEventType">7</int>
</object>
<int key="connectionID">19</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">rightBarButtonItem</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="961671599"/>
</object>
<int key="connectionID">20</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">navButtonsView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="808907889"/>
</object>
<int key="connectionID">22</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">backButton</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="453250804"/>
</object>
<int key="connectionID">25</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">forwardButton</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="981703116"/>
</object>
<int key="connectionID">26</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="426018584"/>
</object>
<int key="connectionID">28</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">webView</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="663477729"/>
</object>
<int key="connectionID">29</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<reference key="object" ref="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="372490531"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="975951072"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">6</int>
<reference key="object" ref="1047805472"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">10</int>
<reference key="object" ref="961671599"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">15</int>
<reference key="object" ref="808907889"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="453250804"/>
<reference ref="981703116"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">16</int>
<reference key="object" ref="453250804"/>
<reference key="parent" ref="808907889"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">17</int>
<reference key="object" ref="981703116"/>
<reference key="parent" ref="808907889"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">27</int>
<reference key="object" ref="426018584"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="663477729"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="663477729"/>
<reference key="parent" ref="426018584"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.CustomClassName</string>
<string>-2.CustomClassName</string>
<string>10.IBPluginDependency</string>
<string>15.IBEditorWindowLastContentRect</string>
<string>15.IBPluginDependency</string>
<string>16.IBPluginDependency</string>
<string>17.IBPluginDependency</string>
<string>27.IBEditorWindowLastContentRect</string>
<string>27.IBPluginDependency</string>
<string>4.IBPluginDependency</string>
<string>6.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>GTMOAuth2ViewControllerTouch</string>
<string>UIResponder</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>{{34, 1031}, {60, 30}}</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>{{214, 696}, {320, 460}}</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">29</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">GTMOAuth2ViewControllerTouch</string>
<string key="superclassName">UIViewController</string>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>backButton</string>
<string>delegate_</string>
<string>forwardButton</string>
<string>navButtonsView</string>
<string>rightBarButtonItem</string>
<string>userData_</string>
<string>webView</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>UIButton</string>
<string>id</string>
<string>UIButton</string>
<string>UIView</string>
<string>UIBarButtonItem</string>
<string>id</string>
<string>UIWebView</string>
</object>
</object>
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>backButton</string>
<string>delegate_</string>
<string>forwardButton</string>
<string>navButtonsView</string>
<string>rightBarButtonItem</string>
<string>userData_</string>
<string>webView</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBToOneOutletInfo">
<string key="name">backButton</string>
<string key="candidateClassName">UIButton</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">delegate_</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">forwardButton</string>
<string key="candidateClassName">UIButton</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">navButtonsView</string>
<string key="candidateClassName">UIView</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">rightBarButtonItem</string>
<string key="candidateClassName">UIBarButtonItem</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">userData_</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">webView</string>
<string key="candidateClassName">UIWebView</string>
</object>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">Touch/GTMOAuth2ViewControllerTouch.h</string>
</object>
</object>
</object>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<integer value="768" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
<integer value="800" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
<integer value="3000" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<string key="IBDocument.LastKnownRelativeProjectPath">../GTMOAuth2.xcodeproj</string>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">141</string>
</data>
</archive>

View File

@ -0,0 +1,113 @@
//
// GTMObjC2Runtime.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 <objc/objc-api.h>
#import <objc/objc-auto.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
// certain parts of the objc2 runtime in terms of the objc1 runtime. It is not
// a complete implementation as I've only implemented the routines I know we
// use. Feel free to add more as necessary.
// These functions are not documented because they conform to the documentation
// for the ObjC2 Runtime.
#if OBJC_API_VERSION >= 2 // Only have optional and req'd keywords in ObjC2.
#define AT_OPTIONAL @optional
#define AT_REQUIRED @required
#else
#define AT_OPTIONAL
#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/message.h>
#import <objc/runtime.h>
#else
#import <objc/objc-runtime.h>
#import <objc/Object.h>
#endif
#import <libkern/OSAtomic.h>
#if GTM_MACOS_SDK && (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
#import "objc/Protocol.h"
OBJC_EXPORT Class object_getClass(id obj);
OBJC_EXPORT const char *class_getName(Class cls);
OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol);
OBJC_EXPORT BOOL class_respondsToSelector(Class cls, SEL sel);
OBJC_EXPORT Class class_getSuperclass(Class cls);
OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount);
OBJC_EXPORT SEL method_getName(Method m);
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2);
OBJC_EXPORT IMP method_getImplementation(Method method);
OBJC_EXPORT IMP method_setImplementation(Method method, IMP imp);
OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protocol *p,
SEL aSel,
BOOL isRequiredMethod,
BOOL isInstanceMethod);
OBJC_EXPORT BOOL sel_isEqual(SEL lhs, SEL rhs);
// If building for 10.4 but using the 10.5 SDK, don't include these.
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
// atomics
// On Leopard these are GC aware
// Intentionally did not include the non-barrier versions, because I couldn't
// come up with a case personally where you wouldn't want to use the
// barrier versions.
GTM_INLINE bool OSAtomicCompareAndSwapPtrBarrier(void *predicate,
void *replacement,
void * volatile *theValue) {
#if defined(__LP64__) && __LP64__
return OSAtomicCompareAndSwap64Barrier((int64_t)predicate,
(int64_t)replacement,
(int64_t *)theValue);
#else // defined(__LP64__) && __LP64__
return OSAtomicCompareAndSwap32Barrier((int32_t)predicate,
(int32_t)replacement,
(int32_t *)theValue);
#endif // defined(__LP64__) && __LP64__
}
#endif // MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
#endif // GTM_MACOS_SDK && (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
#if GTM_MACOS_SDK && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5)
GTM_INLINE BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate,
id replacement,
volatile id *objectLocation) {
return OSAtomicCompareAndSwapPtrBarrier(predicate,
replacement,
(void * volatile *)objectLocation);
}
GTM_INLINE BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate,
id replacement,
volatile id *objectLocation) {
return OSAtomicCompareAndSwapPtrBarrier(predicate,
replacement,
(void * volatile *)objectLocation);
}
#endif // GTM_MACOS_SDK && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5)

View File

@ -0,0 +1,163 @@
//
// GTMObjC2Runtime.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 "GTMObjC2Runtime.h"
#if GTM_MACOS_SDK && (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
#import <stdlib.h>
#import <string.h>
Class object_getClass(id obj) {
if (!obj) return NULL;
return obj->isa;
}
const char *class_getName(Class cls) {
if (!cls) return "nil";
return cls->name;
}
BOOL class_conformsToProtocol(Class cls, Protocol *protocol) {
// We intentionally don't check cls as it crashes on Leopard so we want
// to crash on Tiger as well.
// I logged
// Radar 5572978 class_conformsToProtocol crashes when arg1 is passed as nil
// because it seems odd that this API won't accept nil for cls considering
// all the other apis will accept nil args.
// If this does get fixed, remember to enable the unit tests.
if (!protocol) return NO;
struct objc_protocol_list *protos;
for (protos = cls->protocols; protos != NULL; protos = protos->next) {
for (long i = 0; i < protos->count; i++) {
if ([protos->list[i] conformsTo:protocol]) {
return YES;
}
}
}
return NO;
}
Class class_getSuperclass(Class cls) {
if (!cls) return NULL;
return cls->super_class;
}
BOOL class_respondsToSelector(Class cls, SEL sel) {
return class_getInstanceMethod(cls, sel) != nil;
}
Method *class_copyMethodList(Class cls, unsigned int *outCount) {
if (!cls) return NULL;
unsigned int count = 0;
void *iterator = NULL;
struct objc_method_list *mlist;
Method *methods = NULL;
if (outCount) *outCount = 0;
while ( (mlist = class_nextMethodList(cls, &iterator)) ) {
if (mlist->method_count == 0) continue;
methods = (Method *)realloc(methods,
sizeof(Method) * (count + mlist->method_count + 1));
if (!methods) {
//Memory alloc failed, so what can we do?
return NULL; // COV_NF_LINE
}
for (int i = 0; i < mlist->method_count; i++) {
methods[i + count] = &mlist->method_list[i];
}
count += mlist->method_count;
}
// List must be NULL terminated
if (methods) {
methods[count] = NULL;
}
if (outCount) *outCount = count;
return methods;
}
SEL method_getName(Method method) {
if (!method) return NULL;
return method->method_name;
}
IMP method_getImplementation(Method method) {
if (!method) return NULL;
return method->method_imp;
}
IMP method_setImplementation(Method method, IMP imp) {
// We intentionally don't test method for nil.
// Leopard fails here, so should we.
// I logged this as Radar:
// 5572981 method_setImplementation crashes if you pass nil for the
// method arg (arg 1)
// because it seems odd that this API won't accept nil for method considering
// all the other apis will accept nil args.
// If this does get fixed, remember to enable the unit tests.
// This method works differently on SnowLeopard than
// on Leopard. If you pass in a nil for IMP on SnowLeopard
// it doesn't change anything. On Leopard it will. Since
// attempting to change a sel to nil is probably an error
// we follow the SnowLeopard way of doing things.
IMP oldImp = NULL;
if (imp) {
oldImp = method->method_imp;
method->method_imp = imp;
}
return oldImp;
}
void method_exchangeImplementations(Method m1, Method m2) {
if (m1 == m2) return;
if (!m1 || !m2) return;
IMP imp2 = method_getImplementation(m2);
IMP imp1 = method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);
}
struct objc_method_description protocol_getMethodDescription(Protocol *p,
SEL aSel,
BOOL isRequiredMethod,
BOOL isInstanceMethod) {
struct objc_method_description *descPtr = NULL;
// No such thing as required in ObjC1.
if (isInstanceMethod) {
descPtr = [p descriptionForInstanceMethod:aSel];
} else {
descPtr = [p descriptionForClassMethod:aSel];
}
struct objc_method_description desc;
if (descPtr) {
desc = *descPtr;
} else {
bzero(&desc, sizeof(desc));
}
return desc;
}
BOOL sel_isEqual(SEL lhs, SEL rhs) {
// Apple (informally) promises this will work in the future:
// http://twitter.com/#!/gparker/status/2400099786
return (lhs == rhs) ? YES : NO;
}
#endif // GTM_MACOS_SDK && (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)

39
External/google-plus-ios-sdk/README vendored Normal file
View File

@ -0,0 +1,39 @@
This Google+ iOS SDK allows users to sign in with Google+, share with Google+,
and write moments to Google+ history from third-party apps. The SDK contains the
following files:
README -- This file.
Changelog -- The versions and changes of the SDK.
lib/ -- Header files and libraries.
GooglePlusShare.h -- Header file to include for sharing with Google+.
GooglePlusSignIn.h -- Header file to include for signing into Google+.
GooglePlusSignInButton.h -- Header file to include for showing a button to
sign in with Google+.
libGooglePlus.a -- Static library built for iOS device to link into your app.
libGooglePlusUniversal.a -- Static library built for both iOS device and
simulator to link into your app.
OpenSource/ -- Google open source files used by the SDK. Add all files in this
directory into your project if you're not already using them.
Also see comments for the subdirectory below.
GTL/ -- Google open source files only used by the sample app. Include them
into your project if you're going to use the same functionality,
e.g. Add Moments.
Resources/ -- Resources that can be used in your app.
For |GooglePlusSignInButton|, the google_plus_sign_in*.png images
are required.
google_plus_share.png -- 82x24 Google+ share button image.
google_plus_share_large.png -- 112x32 Google+ share button image.
google_plus_share@2x.png -- 164x48 Google+ share button image.
google_plus_share_large@2x.png -- 224x64 Google+ share button image.
google_plus_sign_in.png -- 120x32 Google+ sign-in button image.
google_plus_sign_in_wide.png --220x32 Wide Google+ sign-in button image.
google_plus_sign_in@2x.png -- 240x64 Google+ sign-in button image.
google_plus_sign_in_wide@2x.png -- 440x64 Wide Google+ sign-in button image.
SampleCode/ -- Sample code for your reference only.
Do not include this in your project.
GooglePlusSample.xcodeproj/ -- The Xcode project.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFiles</key>
<array>
<string>Icon.png</string>
<string>Icon@2x.png</string>
</array>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>Icon.png</string>
<string>Icon@2x.png</string>
</array>
</dict>
</dict>
<key>CFBundleIdentifier</key>
<string>com.google.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.google.GooglePlusSample</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.google.GooglePlusSample</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,14 @@
//
// Prefix header for all source files of the 'GooglePlusSample' target in the 'GooglePlusSample' project
//
#import <Availability.h>
#ifndef __IPHONE_4_0
#warning "This project uses features only available in iOS SDK 4.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#endif

View File

@ -0,0 +1,648 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
0043C79F1580045B000DF02E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0043C79E1580045B000DF02E /* UIKit.framework */; };
0043C7A11580045B000DF02E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0043C7A01580045B000DF02E /* Foundation.framework */; };
0043C7A31580045B000DF02E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0043C7A21580045B000DF02E /* CoreGraphics.framework */; };
00F70E83158006DC0077799E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 00F70E82158006DC0077799E /* main.m */; };
00F70E99158007D90077799E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00F70E98158007D90077799E /* Security.framework */; };
00F70E9B158008040077799E /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00F70E9A158008040077799E /* SystemConfiguration.framework */; };
0C52D6F8158BAB1F001510E6 /* button_background.png in Resources */ = {isa = PBXBuildFile; fileRef = 0C52D6F7158BAB1F001510E6 /* button_background.png */; };
D973B402158ABC1F0083A4B5 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D973B401158ABC1F0083A4B5 /* MessageUI.framework */; };
D98254A815990D8D0060CA47 /* Icon_2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D98254A615990D8D0060CA47 /* Icon_2x.png */; };
D98254A915990D8D0060CA47 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = D98254A715990D8D0060CA47 /* Icon.png */; };
D98254F2159937730060CA47 /* GTLPlusPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = D98254F1159937730060CA47 /* GTLPlusPerson.m */; };
D9EE743D158A8BD400EC1D05 /* google_plus_share_large.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE7435158A8BD400EC1D05 /* google_plus_share_large.png */; };
D9EE743E158A8BD400EC1D05 /* google_plus_share_large@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE7436158A8BD400EC1D05 /* google_plus_share_large@2x.png */; };
D9EE743F158A8BD400EC1D05 /* google_plus_share.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE7437158A8BD400EC1D05 /* google_plus_share.png */; };
D9EE7440158A8BD400EC1D05 /* google_plus_share@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE7438158A8BD400EC1D05 /* google_plus_share@2x.png */; };
D9EE748D158A8C0E00EC1D05 /* GTLBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7449158A8C0E00EC1D05 /* GTLBase64.m */; };
D9EE748E158A8C0E00EC1D05 /* GTLBatchQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE744B158A8C0E00EC1D05 /* GTLBatchQuery.m */; };
D9EE748F158A8C0E00EC1D05 /* GTLBatchResult.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE744D158A8C0E00EC1D05 /* GTLBatchResult.m */; };
D9EE7490158A8C0E00EC1D05 /* GTLDateTime.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE744F158A8C0E00EC1D05 /* GTLDateTime.m */; };
D9EE7491158A8C0E00EC1D05 /* GTLErrorObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7452158A8C0E00EC1D05 /* GTLErrorObject.m */; };
D9EE7492158A8C0E00EC1D05 /* GTLFramework.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7454158A8C0E00EC1D05 /* GTLFramework.m */; };
D9EE7493158A8C0E00EC1D05 /* GTLJSONParser.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7456158A8C0E00EC1D05 /* GTLJSONParser.m */; };
D9EE7494158A8C0E00EC1D05 /* GTLObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7458158A8C0E00EC1D05 /* GTLObject.m */; };
D9EE7495158A8C0E00EC1D05 /* GTLPlusConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE745C158A8C0E00EC1D05 /* GTLPlusConstants.m */; };
D9EE7496158A8C0E00EC1D05 /* GTLPlusItemScope.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE745E158A8C0E00EC1D05 /* GTLPlusItemScope.m */; };
D9EE7497158A8C0E00EC1D05 /* GTLPlusMoment.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7460158A8C0E00EC1D05 /* GTLPlusMoment.m */; };
D9EE7499158A8C0E00EC1D05 /* GTLQueryPlus.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7464158A8C0E00EC1D05 /* GTLQueryPlus.m */; };
D9EE749A158A8C0E00EC1D05 /* GTLServicePlus.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7466158A8C0E00EC1D05 /* GTLServicePlus.m */; };
D9EE749B158A8C0E00EC1D05 /* GTLQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7468158A8C0E00EC1D05 /* GTLQuery.m */; };
D9EE749C158A8C0E00EC1D05 /* GTLRuntimeCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE746A158A8C0E00EC1D05 /* GTLRuntimeCommon.m */; };
D9EE749D158A8C0E00EC1D05 /* GTLService.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE746C158A8C0E00EC1D05 /* GTLService.m */; };
D9EE749E158A8C0E00EC1D05 /* GTLUploadParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE746F158A8C0E00EC1D05 /* GTLUploadParameters.m */; };
D9EE749F158A8C0E00EC1D05 /* GTLUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7471158A8C0E00EC1D05 /* GTLUtilities.m */; };
D9EE74A0158A8C0E00EC1D05 /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7475158A8C0E00EC1D05 /* GTMHTTPFetcher.m */; };
D9EE74A1158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7477158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.m */; };
D9EE74A2158A8C0E00EC1D05 /* GTMHTTPFetcherService.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7479158A8C0E00EC1D05 /* GTMHTTPFetcherService.m */; };
D9EE74A3158A8C0E00EC1D05 /* GTMHTTPFetchHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE747B158A8C0E00EC1D05 /* GTMHTTPFetchHistory.m */; };
D9EE74A4158A8C0E00EC1D05 /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE747D158A8C0E00EC1D05 /* GTMLogger.m */; };
D9EE74A5158A8C0E00EC1D05 /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE747F158A8C0E00EC1D05 /* GTMMethodCheck.m */; };
D9EE74A6158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7481158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.m */; };
D9EE74A7158A8C0E00EC1D05 /* GTMNSString+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7483158A8C0E00EC1D05 /* GTMNSString+URLArguments.m */; };
D9EE74A8158A8C0E00EC1D05 /* GTMOAuth2Authentication.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7485158A8C0E00EC1D05 /* GTMOAuth2Authentication.m */; };
D9EE74A9158A8C0E00EC1D05 /* GTMOAuth2SignIn.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7487158A8C0E00EC1D05 /* GTMOAuth2SignIn.m */; };
D9EE74AA158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE7489158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.m */; };
D9EE74AB158A8C0E00EC1D05 /* GTMOAuth2ViewTouch.xib in Resources */ = {isa = PBXBuildFile; fileRef = D9EE748A158A8C0E00EC1D05 /* GTMOAuth2ViewTouch.xib */; };
D9EE74AC158A8C0E00EC1D05 /* GTMObjC2Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE748C158A8C0E00EC1D05 /* GTMObjC2Runtime.m */; };
D9EE74B0158A8D1E00EC1D05 /* libGooglePlusUniversal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D9EE74AE158A8D1E00EC1D05 /* libGooglePlusUniversal.a */; };
D9EE74C2158A8E0500EC1D05 /* GooglePlusSampleAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE74B4158A8E0500EC1D05 /* GooglePlusSampleAppDelegate.m */; };
D9EE74C3158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE74B6158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.m */; };
D9EE74C4158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74B7158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.xib */; };
D9EE74C5158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE74B9158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.m */; };
D9EE74C6158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74BA158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.xib */; };
D9EE74C7158A8E0500EC1D05 /* GooglePlusSampleShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE74BC158A8E0500EC1D05 /* GooglePlusSampleShareViewController.m */; };
D9EE74C8158A8E0500EC1D05 /* GooglePlusSampleShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74BD158A8E0500EC1D05 /* GooglePlusSampleShareViewController.xib */; };
D9EE74C9158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D9EE74BF158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.m */; };
D9EE74CA158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74C0158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.xib */; };
D9EE74CD158A8E2900EC1D05 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74CB158A8E2900EC1D05 /* InfoPlist.strings */; };
D9EE74E4158A9A7D00EC1D05 /* google_plus_sign_in_wide.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74E0158A9A7D00EC1D05 /* google_plus_sign_in_wide.png */; };
D9EE74E5158A9A7D00EC1D05 /* google_plus_sign_in_wide@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74E1158A9A7D00EC1D05 /* google_plus_sign_in_wide@2x.png */; };
D9EE74E6158A9A7D00EC1D05 /* google_plus_sign_in.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74E2158A9A7D00EC1D05 /* google_plus_sign_in.png */; };
D9EE74E7158A9A7D00EC1D05 /* google_plus_sign_in@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D9EE74E3158A9A7D00EC1D05 /* google_plus_sign_in@2x.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0043C79A1580045B000DF02E /* GooglePlusSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GooglePlusSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
0043C79E1580045B000DF02E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
0043C7A01580045B000DF02E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
0043C7A21580045B000DF02E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
00F70E82158006DC0077799E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
00F70E98158007D90077799E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
00F70E9A158008040077799E /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
0C52D6F7158BAB1F001510E6 /* button_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = button_background.png; path = Resources/button_background.png; sourceTree = SOURCE_ROOT; };
D973B401158ABC1F0083A4B5 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
D98254A615990D8D0060CA47 /* Icon_2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon_2x.png; path = Resources/Icon_2x.png; sourceTree = SOURCE_ROOT; };
D98254A715990D8D0060CA47 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon.png; path = Resources/Icon.png; sourceTree = SOURCE_ROOT; };
D98254F0159937730060CA47 /* GTLPlusPerson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusPerson.h; sourceTree = "<group>"; };
D98254F1159937730060CA47 /* GTLPlusPerson.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusPerson.m; sourceTree = "<group>"; };
D9EE7431158A8BAE00EC1D05 /* GooglePlusShare.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GooglePlusShare.h; path = ../lib/GooglePlusShare.h; sourceTree = "<group>"; };
D9EE7432158A8BAE00EC1D05 /* GooglePlusSignIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GooglePlusSignIn.h; path = ../lib/GooglePlusSignIn.h; sourceTree = "<group>"; };
D9EE7433158A8BAE00EC1D05 /* GooglePlusSignInButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GooglePlusSignInButton.h; path = ../lib/GooglePlusSignInButton.h; sourceTree = "<group>"; };
D9EE7435158A8BD400EC1D05 /* google_plus_share_large.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = google_plus_share_large.png; path = ../Resources/google_plus_share_large.png; sourceTree = "<group>"; };
D9EE7436158A8BD400EC1D05 /* google_plus_share_large@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "google_plus_share_large@2x.png"; path = "../Resources/google_plus_share_large@2x.png"; sourceTree = "<group>"; };
D9EE7437158A8BD400EC1D05 /* google_plus_share.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = google_plus_share.png; path = ../Resources/google_plus_share.png; sourceTree = "<group>"; };
D9EE7438158A8BD400EC1D05 /* google_plus_share@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "google_plus_share@2x.png"; path = "../Resources/google_plus_share@2x.png"; sourceTree = "<group>"; };
D9EE7448158A8C0E00EC1D05 /* GTLBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBase64.h; sourceTree = "<group>"; };
D9EE7449158A8C0E00EC1D05 /* GTLBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBase64.m; sourceTree = "<group>"; };
D9EE744A158A8C0E00EC1D05 /* GTLBatchQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBatchQuery.h; sourceTree = "<group>"; };
D9EE744B158A8C0E00EC1D05 /* GTLBatchQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBatchQuery.m; sourceTree = "<group>"; };
D9EE744C158A8C0E00EC1D05 /* GTLBatchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBatchResult.h; sourceTree = "<group>"; };
D9EE744D158A8C0E00EC1D05 /* GTLBatchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBatchResult.m; sourceTree = "<group>"; };
D9EE744E158A8C0E00EC1D05 /* GTLDateTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLDateTime.h; sourceTree = "<group>"; };
D9EE744F158A8C0E00EC1D05 /* GTLDateTime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLDateTime.m; sourceTree = "<group>"; };
D9EE7450158A8C0E00EC1D05 /* GTLDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLDefines.h; sourceTree = "<group>"; };
D9EE7451158A8C0E00EC1D05 /* GTLErrorObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLErrorObject.h; sourceTree = "<group>"; };
D9EE7452158A8C0E00EC1D05 /* GTLErrorObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLErrorObject.m; sourceTree = "<group>"; };
D9EE7453158A8C0E00EC1D05 /* GTLFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLFramework.h; sourceTree = "<group>"; };
D9EE7454158A8C0E00EC1D05 /* GTLFramework.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLFramework.m; sourceTree = "<group>"; };
D9EE7455158A8C0E00EC1D05 /* GTLJSONParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLJSONParser.h; sourceTree = "<group>"; };
D9EE7456158A8C0E00EC1D05 /* GTLJSONParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLJSONParser.m; sourceTree = "<group>"; };
D9EE7457158A8C0E00EC1D05 /* GTLObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLObject.h; sourceTree = "<group>"; };
D9EE7458158A8C0E00EC1D05 /* GTLObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLObject.m; sourceTree = "<group>"; };
D9EE745A158A8C0E00EC1D05 /* GTLPlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlus.h; sourceTree = "<group>"; };
D9EE745B158A8C0E00EC1D05 /* GTLPlusConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusConstants.h; sourceTree = "<group>"; };
D9EE745C158A8C0E00EC1D05 /* GTLPlusConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusConstants.m; sourceTree = "<group>"; };
D9EE745D158A8C0E00EC1D05 /* GTLPlusItemScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusItemScope.h; sourceTree = "<group>"; };
D9EE745E158A8C0E00EC1D05 /* GTLPlusItemScope.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusItemScope.m; sourceTree = "<group>"; };
D9EE745F158A8C0E00EC1D05 /* GTLPlusMoment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusMoment.h; sourceTree = "<group>"; };
D9EE7460158A8C0E00EC1D05 /* GTLPlusMoment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusMoment.m; sourceTree = "<group>"; };
D9EE7463158A8C0E00EC1D05 /* GTLQueryPlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLQueryPlus.h; sourceTree = "<group>"; };
D9EE7464158A8C0E00EC1D05 /* GTLQueryPlus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLQueryPlus.m; sourceTree = "<group>"; };
D9EE7465158A8C0E00EC1D05 /* GTLServicePlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLServicePlus.h; sourceTree = "<group>"; };
D9EE7466158A8C0E00EC1D05 /* GTLServicePlus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLServicePlus.m; sourceTree = "<group>"; };
D9EE7467158A8C0E00EC1D05 /* GTLQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLQuery.h; sourceTree = "<group>"; };
D9EE7468158A8C0E00EC1D05 /* GTLQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLQuery.m; sourceTree = "<group>"; };
D9EE7469158A8C0E00EC1D05 /* GTLRuntimeCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLRuntimeCommon.h; sourceTree = "<group>"; };
D9EE746A158A8C0E00EC1D05 /* GTLRuntimeCommon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLRuntimeCommon.m; sourceTree = "<group>"; };
D9EE746B158A8C0E00EC1D05 /* GTLService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLService.h; sourceTree = "<group>"; };
D9EE746C158A8C0E00EC1D05 /* GTLService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLService.m; sourceTree = "<group>"; };
D9EE746D158A8C0E00EC1D05 /* GTLTargetNamespace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLTargetNamespace.h; sourceTree = "<group>"; };
D9EE746E158A8C0E00EC1D05 /* GTLUploadParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLUploadParameters.h; sourceTree = "<group>"; };
D9EE746F158A8C0E00EC1D05 /* GTLUploadParameters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLUploadParameters.m; sourceTree = "<group>"; };
D9EE7470158A8C0E00EC1D05 /* GTLUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLUtilities.h; sourceTree = "<group>"; };
D9EE7471158A8C0E00EC1D05 /* GTLUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLUtilities.m; sourceTree = "<group>"; };
D9EE7472158A8C0E00EC1D05 /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = "<group>"; };
D9EE7473158A8C0E00EC1D05 /* GTMGarbageCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGarbageCollection.h; sourceTree = "<group>"; };
D9EE7474158A8C0E00EC1D05 /* GTMHTTPFetcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcher.h; sourceTree = "<group>"; };
D9EE7475158A8C0E00EC1D05 /* GTMHTTPFetcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcher.m; sourceTree = "<group>"; };
D9EE7476158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcherLogging.h; sourceTree = "<group>"; };
D9EE7477158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherLogging.m; sourceTree = "<group>"; };
D9EE7478158A8C0E00EC1D05 /* GTMHTTPFetcherService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcherService.h; sourceTree = "<group>"; };
D9EE7479158A8C0E00EC1D05 /* GTMHTTPFetcherService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherService.m; sourceTree = "<group>"; };
D9EE747A158A8C0E00EC1D05 /* GTMHTTPFetchHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetchHistory.h; sourceTree = "<group>"; };
D9EE747B158A8C0E00EC1D05 /* GTMHTTPFetchHistory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetchHistory.m; sourceTree = "<group>"; };
D9EE747C158A8C0E00EC1D05 /* GTMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLogger.h; sourceTree = "<group>"; };
D9EE747D158A8C0E00EC1D05 /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLogger.m; sourceTree = "<group>"; };
D9EE747E158A8C0E00EC1D05 /* GTMMethodCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMMethodCheck.h; sourceTree = "<group>"; };
D9EE747F158A8C0E00EC1D05 /* GTMMethodCheck.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMMethodCheck.m; sourceTree = "<group>"; };
D9EE7480158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = "<group>"; };
D9EE7481158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = "<group>"; };
D9EE7482158A8C0E00EC1D05 /* GTMNSString+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+URLArguments.h"; sourceTree = "<group>"; };
D9EE7483158A8C0E00EC1D05 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = "<group>"; };
D9EE7484158A8C0E00EC1D05 /* GTMOAuth2Authentication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2Authentication.h; sourceTree = "<group>"; };
D9EE7485158A8C0E00EC1D05 /* GTMOAuth2Authentication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2Authentication.m; sourceTree = "<group>"; };
D9EE7486158A8C0E00EC1D05 /* GTMOAuth2SignIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2SignIn.h; sourceTree = "<group>"; };
D9EE7487158A8C0E00EC1D05 /* GTMOAuth2SignIn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2SignIn.m; sourceTree = "<group>"; };
D9EE7488158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2ViewControllerTouch.h; sourceTree = "<group>"; };
D9EE7489158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2ViewControllerTouch.m; sourceTree = "<group>"; };
D9EE748A158A8C0E00EC1D05 /* GTMOAuth2ViewTouch.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GTMOAuth2ViewTouch.xib; sourceTree = "<group>"; };
D9EE748B158A8C0E00EC1D05 /* GTMObjC2Runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMObjC2Runtime.h; sourceTree = "<group>"; };
D9EE748C158A8C0E00EC1D05 /* GTMObjC2Runtime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMObjC2Runtime.m; sourceTree = "<group>"; };
D9EE74AD158A8D1E00EC1D05 /* libGooglePlus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libGooglePlus.a; path = ../lib/libGooglePlus.a; sourceTree = "<group>"; };
D9EE74AE158A8D1E00EC1D05 /* libGooglePlusUniversal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libGooglePlusUniversal.a; path = ../lib/libGooglePlusUniversal.a; sourceTree = "<group>"; };
D9EE74B1158A8E0500EC1D05 /* GooglePlusSample-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GooglePlusSample-Info.plist"; sourceTree = SOURCE_ROOT; };
D9EE74B2158A8E0500EC1D05 /* GooglePlusSample-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GooglePlusSample-Prefix.pch"; sourceTree = SOURCE_ROOT; };
D9EE74B3158A8E0500EC1D05 /* GooglePlusSampleAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusSampleAppDelegate.h; sourceTree = SOURCE_ROOT; };
D9EE74B4158A8E0500EC1D05 /* GooglePlusSampleAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GooglePlusSampleAppDelegate.m; sourceTree = SOURCE_ROOT; };
D9EE74B5158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusSampleMasterViewController.h; sourceTree = SOURCE_ROOT; };
D9EE74B6158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GooglePlusSampleMasterViewController.m; sourceTree = SOURCE_ROOT; };
D9EE74B7158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GooglePlusSampleMasterViewController.xib; sourceTree = SOURCE_ROOT; };
D9EE74B8158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusSampleMomentsViewController.h; sourceTree = SOURCE_ROOT; };
D9EE74B9158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GooglePlusSampleMomentsViewController.m; sourceTree = SOURCE_ROOT; };
D9EE74BA158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GooglePlusSampleMomentsViewController.xib; sourceTree = SOURCE_ROOT; };
D9EE74BB158A8E0500EC1D05 /* GooglePlusSampleShareViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusSampleShareViewController.h; sourceTree = SOURCE_ROOT; };
D9EE74BC158A8E0500EC1D05 /* GooglePlusSampleShareViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GooglePlusSampleShareViewController.m; sourceTree = SOURCE_ROOT; };
D9EE74BD158A8E0500EC1D05 /* GooglePlusSampleShareViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GooglePlusSampleShareViewController.xib; sourceTree = SOURCE_ROOT; };
D9EE74BE158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusSampleSignInViewController.h; sourceTree = SOURCE_ROOT; };
D9EE74BF158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GooglePlusSampleSignInViewController.m; sourceTree = SOURCE_ROOT; };
D9EE74C0158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GooglePlusSampleSignInViewController.xib; sourceTree = SOURCE_ROOT; };
D9EE74CC158A8E2900EC1D05 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = SOURCE_ROOT; };
D9EE74E0158A9A7D00EC1D05 /* google_plus_sign_in_wide.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = google_plus_sign_in_wide.png; path = ../Resources/google_plus_sign_in_wide.png; sourceTree = "<group>"; };
D9EE74E1158A9A7D00EC1D05 /* google_plus_sign_in_wide@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "google_plus_sign_in_wide@2x.png"; path = "../Resources/google_plus_sign_in_wide@2x.png"; sourceTree = "<group>"; };
D9EE74E2158A9A7D00EC1D05 /* google_plus_sign_in.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = google_plus_sign_in.png; path = ../Resources/google_plus_sign_in.png; sourceTree = "<group>"; };
D9EE74E3158A9A7D00EC1D05 /* google_plus_sign_in@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "google_plus_sign_in@2x.png"; path = "../Resources/google_plus_sign_in@2x.png"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0043C7971580045B000DF02E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D973B402158ABC1F0083A4B5 /* MessageUI.framework in Frameworks */,
00F70E9B158008040077799E /* SystemConfiguration.framework in Frameworks */,
00F70E99158007D90077799E /* Security.framework in Frameworks */,
0043C79F1580045B000DF02E /* UIKit.framework in Frameworks */,
0043C7A11580045B000DF02E /* Foundation.framework in Frameworks */,
0043C7A31580045B000DF02E /* CoreGraphics.framework in Frameworks */,
D9EE74B0158A8D1E00EC1D05 /* libGooglePlusUniversal.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0043C78F1580045B000DF02E = {
isa = PBXGroup;
children = (
D9EE7434158A8BB500EC1D05 /* GooglePlusSDK */,
D9EE7446158A8C0E00EC1D05 /* GoogleOpenSource */,
0043C7A41580045B000DF02E /* GooglePlusSample */,
0043C79D1580045B000DF02E /* Frameworks */,
0043C79B1580045B000DF02E /* Products */,
);
sourceTree = "<group>";
};
0043C79B1580045B000DF02E /* Products */ = {
isa = PBXGroup;
children = (
0043C79A1580045B000DF02E /* GooglePlusSample.app */,
);
name = Products;
sourceTree = "<group>";
};
0043C79D1580045B000DF02E /* Frameworks */ = {
isa = PBXGroup;
children = (
D973B401158ABC1F0083A4B5 /* MessageUI.framework */,
00F70E9A158008040077799E /* SystemConfiguration.framework */,
00F70E98158007D90077799E /* Security.framework */,
0043C79E1580045B000DF02E /* UIKit.framework */,
0043C7A01580045B000DF02E /* Foundation.framework */,
0043C7A21580045B000DF02E /* CoreGraphics.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
0043C7A41580045B000DF02E /* GooglePlusSample */ = {
isa = PBXGroup;
children = (
D9EE74B3158A8E0500EC1D05 /* GooglePlusSampleAppDelegate.h */,
D9EE74B4158A8E0500EC1D05 /* GooglePlusSampleAppDelegate.m */,
D9EE74B5158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.h */,
D9EE74B6158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.m */,
D9EE74B7158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.xib */,
D9EE74B8158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.h */,
D9EE74B9158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.m */,
D9EE74BA158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.xib */,
D9EE74BB158A8E0500EC1D05 /* GooglePlusSampleShareViewController.h */,
D9EE74BC158A8E0500EC1D05 /* GooglePlusSampleShareViewController.m */,
D9EE74BD158A8E0500EC1D05 /* GooglePlusSampleShareViewController.xib */,
D9EE74BE158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.h */,
D9EE74BF158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.m */,
D9EE74C0158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.xib */,
D98254AB15990DBC0060CA47 /* Resources */,
0043C7A51580045B000DF02E /* Supporting Files */,
);
path = GooglePlusSample;
sourceTree = "<group>";
};
0043C7A51580045B000DF02E /* Supporting Files */ = {
isa = PBXGroup;
children = (
00F70E82158006DC0077799E /* main.m */,
D9EE74CB158A8E2900EC1D05 /* InfoPlist.strings */,
D9EE74B1158A8E0500EC1D05 /* GooglePlusSample-Info.plist */,
D9EE74B2158A8E0500EC1D05 /* GooglePlusSample-Prefix.pch */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
D98254AB15990DBC0060CA47 /* Resources */ = {
isa = PBXGroup;
children = (
D98254A615990D8D0060CA47 /* Icon_2x.png */,
D98254A715990D8D0060CA47 /* Icon.png */,
0C52D6F7158BAB1F001510E6 /* button_background.png */,
);
name = Resources;
sourceTree = "<group>";
};
D9EE7434158A8BB500EC1D05 /* GooglePlusSDK */ = {
isa = PBXGroup;
children = (
D9EE7431158A8BAE00EC1D05 /* GooglePlusShare.h */,
D9EE7432158A8BAE00EC1D05 /* GooglePlusSignIn.h */,
D9EE7433158A8BAE00EC1D05 /* GooglePlusSignInButton.h */,
D9EE74AD158A8D1E00EC1D05 /* libGooglePlus.a */,
D9EE74AE158A8D1E00EC1D05 /* libGooglePlusUniversal.a */,
D9EE7445158A8BDB00EC1D05 /* Resources */,
);
name = GooglePlusSDK;
sourceTree = "<group>";
};
D9EE7445158A8BDB00EC1D05 /* Resources */ = {
isa = PBXGroup;
children = (
D9EE74E0158A9A7D00EC1D05 /* google_plus_sign_in_wide.png */,
D9EE74E1158A9A7D00EC1D05 /* google_plus_sign_in_wide@2x.png */,
D9EE74E2158A9A7D00EC1D05 /* google_plus_sign_in.png */,
D9EE74E3158A9A7D00EC1D05 /* google_plus_sign_in@2x.png */,
D9EE7435158A8BD400EC1D05 /* google_plus_share_large.png */,
D9EE7436158A8BD400EC1D05 /* google_plus_share_large@2x.png */,
D9EE7437158A8BD400EC1D05 /* google_plus_share.png */,
D9EE7438158A8BD400EC1D05 /* google_plus_share@2x.png */,
);
name = Resources;
sourceTree = "<group>";
};
D9EE7446158A8C0E00EC1D05 /* GoogleOpenSource */ = {
isa = PBXGroup;
children = (
D9EE7447158A8C0E00EC1D05 /* GTL */,
D9EE7472158A8C0E00EC1D05 /* GTMDefines.h */,
D9EE7473158A8C0E00EC1D05 /* GTMGarbageCollection.h */,
D9EE7474158A8C0E00EC1D05 /* GTMHTTPFetcher.h */,
D9EE7475158A8C0E00EC1D05 /* GTMHTTPFetcher.m */,
D9EE7476158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.h */,
D9EE7477158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.m */,
D9EE7478158A8C0E00EC1D05 /* GTMHTTPFetcherService.h */,
D9EE7479158A8C0E00EC1D05 /* GTMHTTPFetcherService.m */,
D9EE747A158A8C0E00EC1D05 /* GTMHTTPFetchHistory.h */,
D9EE747B158A8C0E00EC1D05 /* GTMHTTPFetchHistory.m */,
D9EE747C158A8C0E00EC1D05 /* GTMLogger.h */,
D9EE747D158A8C0E00EC1D05 /* GTMLogger.m */,
D9EE747E158A8C0E00EC1D05 /* GTMMethodCheck.h */,
D9EE747F158A8C0E00EC1D05 /* GTMMethodCheck.m */,
D9EE7480158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.h */,
D9EE7481158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.m */,
D9EE7482158A8C0E00EC1D05 /* GTMNSString+URLArguments.h */,
D9EE7483158A8C0E00EC1D05 /* GTMNSString+URLArguments.m */,
D9EE7484158A8C0E00EC1D05 /* GTMOAuth2Authentication.h */,
D9EE7485158A8C0E00EC1D05 /* GTMOAuth2Authentication.m */,
D9EE7486158A8C0E00EC1D05 /* GTMOAuth2SignIn.h */,
D9EE7487158A8C0E00EC1D05 /* GTMOAuth2SignIn.m */,
D9EE7488158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.h */,
D9EE7489158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.m */,
D9EE748A158A8C0E00EC1D05 /* GTMOAuth2ViewTouch.xib */,
D9EE748B158A8C0E00EC1D05 /* GTMObjC2Runtime.h */,
D9EE748C158A8C0E00EC1D05 /* GTMObjC2Runtime.m */,
);
name = GoogleOpenSource;
path = ../OpenSource;
sourceTree = "<group>";
};
D9EE7447158A8C0E00EC1D05 /* GTL */ = {
isa = PBXGroup;
children = (
D9EE7459158A8C0E00EC1D05 /* GTLPlus */,
D9EE7448158A8C0E00EC1D05 /* GTLBase64.h */,
D9EE7449158A8C0E00EC1D05 /* GTLBase64.m */,
D9EE744A158A8C0E00EC1D05 /* GTLBatchQuery.h */,
D9EE744B158A8C0E00EC1D05 /* GTLBatchQuery.m */,
D9EE744C158A8C0E00EC1D05 /* GTLBatchResult.h */,
D9EE744D158A8C0E00EC1D05 /* GTLBatchResult.m */,
D9EE744E158A8C0E00EC1D05 /* GTLDateTime.h */,
D9EE744F158A8C0E00EC1D05 /* GTLDateTime.m */,
D9EE7450158A8C0E00EC1D05 /* GTLDefines.h */,
D9EE7451158A8C0E00EC1D05 /* GTLErrorObject.h */,
D9EE7452158A8C0E00EC1D05 /* GTLErrorObject.m */,
D9EE7453158A8C0E00EC1D05 /* GTLFramework.h */,
D9EE7454158A8C0E00EC1D05 /* GTLFramework.m */,
D9EE7455158A8C0E00EC1D05 /* GTLJSONParser.h */,
D9EE7456158A8C0E00EC1D05 /* GTLJSONParser.m */,
D9EE7457158A8C0E00EC1D05 /* GTLObject.h */,
D9EE7458158A8C0E00EC1D05 /* GTLObject.m */,
D9EE7467158A8C0E00EC1D05 /* GTLQuery.h */,
D9EE7468158A8C0E00EC1D05 /* GTLQuery.m */,
D9EE7469158A8C0E00EC1D05 /* GTLRuntimeCommon.h */,
D9EE746A158A8C0E00EC1D05 /* GTLRuntimeCommon.m */,
D9EE746B158A8C0E00EC1D05 /* GTLService.h */,
D9EE746C158A8C0E00EC1D05 /* GTLService.m */,
D9EE746D158A8C0E00EC1D05 /* GTLTargetNamespace.h */,
D9EE746E158A8C0E00EC1D05 /* GTLUploadParameters.h */,
D9EE746F158A8C0E00EC1D05 /* GTLUploadParameters.m */,
D9EE7470158A8C0E00EC1D05 /* GTLUtilities.h */,
D9EE7471158A8C0E00EC1D05 /* GTLUtilities.m */,
);
path = GTL;
sourceTree = "<group>";
};
D9EE7459158A8C0E00EC1D05 /* GTLPlus */ = {
isa = PBXGroup;
children = (
D9EE745A158A8C0E00EC1D05 /* GTLPlus.h */,
D9EE745B158A8C0E00EC1D05 /* GTLPlusConstants.h */,
D9EE745C158A8C0E00EC1D05 /* GTLPlusConstants.m */,
D9EE745D158A8C0E00EC1D05 /* GTLPlusItemScope.h */,
D9EE745E158A8C0E00EC1D05 /* GTLPlusItemScope.m */,
D9EE745F158A8C0E00EC1D05 /* GTLPlusMoment.h */,
D9EE7460158A8C0E00EC1D05 /* GTLPlusMoment.m */,
D98254F0159937730060CA47 /* GTLPlusPerson.h */,
D98254F1159937730060CA47 /* GTLPlusPerson.m */,
D9EE7463158A8C0E00EC1D05 /* GTLQueryPlus.h */,
D9EE7464158A8C0E00EC1D05 /* GTLQueryPlus.m */,
D9EE7465158A8C0E00EC1D05 /* GTLServicePlus.h */,
D9EE7466158A8C0E00EC1D05 /* GTLServicePlus.m */,
);
path = GTLPlus;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
0043C7991580045B000DF02E /* GooglePlusSample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0043C7B21580045B000DF02E /* Build configuration list for PBXNativeTarget "GooglePlusSample" */;
buildPhases = (
0043C7961580045B000DF02E /* Sources */,
0043C7971580045B000DF02E /* Frameworks */,
0043C7981580045B000DF02E /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = GooglePlusSample;
productName = GooglePlusSample;
productReference = 0043C79A1580045B000DF02E /* GooglePlusSample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0043C7911580045B000DF02E /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0420;
ORGANIZATIONNAME = "Google Inc";
};
buildConfigurationList = 0043C7941580045B000DF02E /* Build configuration list for PBXProject "GooglePlusSample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 0043C78F1580045B000DF02E;
productRefGroup = 0043C79B1580045B000DF02E /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
0043C7991580045B000DF02E /* GooglePlusSample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
0043C7981580045B000DF02E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D9EE743D158A8BD400EC1D05 /* google_plus_share_large.png in Resources */,
D9EE743E158A8BD400EC1D05 /* google_plus_share_large@2x.png in Resources */,
D9EE743F158A8BD400EC1D05 /* google_plus_share.png in Resources */,
D9EE7440158A8BD400EC1D05 /* google_plus_share@2x.png in Resources */,
D9EE74E4158A9A7D00EC1D05 /* google_plus_sign_in_wide.png in Resources */,
D9EE74E5158A9A7D00EC1D05 /* google_plus_sign_in_wide@2x.png in Resources */,
D9EE74E6158A9A7D00EC1D05 /* google_plus_sign_in.png in Resources */,
D9EE74E7158A9A7D00EC1D05 /* google_plus_sign_in@2x.png in Resources */,
D9EE74AB158A8C0E00EC1D05 /* GTMOAuth2ViewTouch.xib in Resources */,
D9EE74C4158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.xib in Resources */,
D9EE74C6158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.xib in Resources */,
D9EE74C8158A8E0500EC1D05 /* GooglePlusSampleShareViewController.xib in Resources */,
D9EE74CA158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.xib in Resources */,
D9EE74CD158A8E2900EC1D05 /* InfoPlist.strings in Resources */,
0C52D6F8158BAB1F001510E6 /* button_background.png in Resources */,
D98254A815990D8D0060CA47 /* Icon_2x.png in Resources */,
D98254A915990D8D0060CA47 /* Icon.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
0043C7961580045B000DF02E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00F70E83158006DC0077799E /* main.m in Sources */,
D9EE748D158A8C0E00EC1D05 /* GTLBase64.m in Sources */,
D9EE748E158A8C0E00EC1D05 /* GTLBatchQuery.m in Sources */,
D9EE748F158A8C0E00EC1D05 /* GTLBatchResult.m in Sources */,
D9EE7490158A8C0E00EC1D05 /* GTLDateTime.m in Sources */,
D9EE7491158A8C0E00EC1D05 /* GTLErrorObject.m in Sources */,
D9EE7492158A8C0E00EC1D05 /* GTLFramework.m in Sources */,
D9EE7493158A8C0E00EC1D05 /* GTLJSONParser.m in Sources */,
D9EE7494158A8C0E00EC1D05 /* GTLObject.m in Sources */,
D9EE7495158A8C0E00EC1D05 /* GTLPlusConstants.m in Sources */,
D9EE7496158A8C0E00EC1D05 /* GTLPlusItemScope.m in Sources */,
D9EE7497158A8C0E00EC1D05 /* GTLPlusMoment.m in Sources */,
D9EE7499158A8C0E00EC1D05 /* GTLQueryPlus.m in Sources */,
D9EE749A158A8C0E00EC1D05 /* GTLServicePlus.m in Sources */,
D9EE749B158A8C0E00EC1D05 /* GTLQuery.m in Sources */,
D9EE749C158A8C0E00EC1D05 /* GTLRuntimeCommon.m in Sources */,
D9EE749D158A8C0E00EC1D05 /* GTLService.m in Sources */,
D9EE749E158A8C0E00EC1D05 /* GTLUploadParameters.m in Sources */,
D9EE749F158A8C0E00EC1D05 /* GTLUtilities.m in Sources */,
D9EE74A0158A8C0E00EC1D05 /* GTMHTTPFetcher.m in Sources */,
D9EE74A1158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.m in Sources */,
D9EE74A2158A8C0E00EC1D05 /* GTMHTTPFetcherService.m in Sources */,
D9EE74A3158A8C0E00EC1D05 /* GTMHTTPFetchHistory.m in Sources */,
D9EE74A4158A8C0E00EC1D05 /* GTMLogger.m in Sources */,
D9EE74A5158A8C0E00EC1D05 /* GTMMethodCheck.m in Sources */,
D9EE74A6158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.m in Sources */,
D9EE74A7158A8C0E00EC1D05 /* GTMNSString+URLArguments.m in Sources */,
D9EE74A8158A8C0E00EC1D05 /* GTMOAuth2Authentication.m in Sources */,
D9EE74A9158A8C0E00EC1D05 /* GTMOAuth2SignIn.m in Sources */,
D9EE74AA158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.m in Sources */,
D9EE74AC158A8C0E00EC1D05 /* GTMObjC2Runtime.m in Sources */,
D9EE74C2158A8E0500EC1D05 /* GooglePlusSampleAppDelegate.m in Sources */,
D9EE74C3158A8E0500EC1D05 /* GooglePlusSampleMasterViewController.m in Sources */,
D9EE74C5158A8E0500EC1D05 /* GooglePlusSampleMomentsViewController.m in Sources */,
D9EE74C7158A8E0500EC1D05 /* GooglePlusSampleShareViewController.m in Sources */,
D9EE74C9158A8E0500EC1D05 /* GooglePlusSampleSignInViewController.m in Sources */,
D98254F2159937730060CA47 /* GTLPlusPerson.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
D9EE74CB158A8E2900EC1D05 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
D9EE74CC158A8E2900EC1D05 /* en */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
0043C7B01580045B000DF02E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_LDFLAGS = (
"-all_load",
"-ObjC",
);
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
0043C7B11580045B000DF02E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_32_BIT)";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
OTHER_LDFLAGS = (
"-all_load",
"-ObjC",
);
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
0043C7B31580045B000DF02E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "GooglePlusSample-Prefix.pch";
INFOPLIST_FILE = "GooglePlusSample-Info.plist";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)\"",
"\"$(SRCROOT)/../lib\"",
);
OTHER_LDFLAGS = (
"-all_load",
"-ObjC",
);
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Debug;
};
0043C7B41580045B000DF02E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "GooglePlusSample-Prefix.pch";
INFOPLIST_FILE = "GooglePlusSample-Info.plist";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)\"",
"\"$(SRCROOT)/../lib\"",
);
OTHER_LDFLAGS = (
"-all_load",
"-ObjC",
);
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0043C7941580045B000DF02E /* Build configuration list for PBXProject "GooglePlusSample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0043C7B01580045B000DF02E /* Debug */,
0043C7B11580045B000DF02E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0043C7B21580045B000DF02E /* Build configuration list for PBXNativeTarget "GooglePlusSample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0043C7B31580045B000DF02E /* Debug */,
0043C7B41580045B000DF02E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0043C7911580045B000DF02E /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:GooglePlusSample.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0043C7991580045B000DF02E"
BuildableName = "GooglePlusSample.app"
BlueprintName = "GooglePlusSample"
ReferencedContainer = "container:GooglePlusSample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0043C7991580045B000DF02E"
BuildableName = "GooglePlusSample.app"
BlueprintName = "GooglePlusSample"
ReferencedContainer = "container:GooglePlusSample.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0043C7991580045B000DF02E"
BuildableName = "GooglePlusSample.app"
BlueprintName = "GooglePlusSample"
ReferencedContainer = "container:GooglePlusSample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0043C7991580045B000DF02E"
BuildableName = "GooglePlusSample.app"
BlueprintName = "GooglePlusSample"
ReferencedContainer = "container:GooglePlusSample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>GooglePlusSample.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>0043C7991580045B000DF02E</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,41 @@
//
// GooglePlusSampleAppDelegate.h
//
// Copyright 2012 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 <UIKit/UIKit.h>
@class GooglePlusShare;
@class GooglePlusSignInButton;
@class GTMOAuth2Authentication;
@interface GooglePlusSampleAppDelegate : UIResponder<UIApplicationDelegate>
// The sample app's |UIWindow|.
@property (retain, nonatomic) UIWindow *window;
// The navigation controller.
@property (retain, nonatomic) UINavigationController *navigationController;
// The Google+ sign-in button to handle the URL redirect.
@property (retain, nonatomic) GooglePlusSignInButton *signInButton;
// The OAuth 2.0 authentication used in the application.
@property (retain, nonatomic) GTMOAuth2Authentication *auth;
// The Google+ share object to handle the URL redirect.
@property (retain, nonatomic) GooglePlusShare *share;
// The OAuth 2.0 client ID to be used for Google+ sign-in, share, and moments.
+ (NSString *)clientID;
@end

View File

@ -0,0 +1,89 @@
//
// GooglePlusSampleAppDelegate.m
//
// Copyright 2012 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 "GooglePlusSampleAppDelegate.h"
#import "GooglePlusSampleMasterViewController.h"
#import "GooglePlusSignIn.h"
#import "GooglePlusSignInButton.h"
@implementation GooglePlusSampleAppDelegate
@synthesize window = window_;
@synthesize navigationController = navigationController_;
@synthesize signInButton = signInButton_;
@synthesize auth = auth_;
@synthesize share = share_;
// DO NOT USE THIS CLIENT ID. IT WILL NOT WORK FOR YOUR APP.
// Please use the client ID created for you by Google.
static NSString * const kClientID = @"571459971810-"
@"2bpoda566pap5kkc0aqljqfjki8tgeb6.apps.googleusercontent.com";
+ (NSString *)clientID {
return kClientID;
}
#pragma mark Object life-cycle.
- (void)dealloc {
[window_ release];
[navigationController_ release];
[signInButton_ release];
[auth_ release];
[share_ release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[[UIWindow alloc]
initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
GooglePlusSampleMasterViewController *masterViewController =
[[[GooglePlusSampleMasterViewController alloc]
initWithNibName:@"GooglePlusSampleMasterViewController"
bundle:nil] autorelease];
self.navigationController =
[[[UINavigationController alloc]
initWithRootViewController:masterViewController] autorelease];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// Handle Google+ share dialog URL.
if ([share_ handleURL:url
sourceApplication:sourceApplication
annotation:annotation]) {
return YES;
}
// Handle Google+ sign-in button URL.
if ([signInButton_ handleURL:url
sourceApplication:sourceApplication
annotation:annotation]) {
return YES;
}
return NO;
}
@end

View File

@ -0,0 +1,23 @@
//
// GooglePlusSampleMasterViewController.h
//
// Copyright 2012 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 <UIKit/UIKit.h>
@interface GooglePlusSampleMasterViewController : UITableViewController
@end

View File

@ -0,0 +1,98 @@
//
// GooglePlusSampleMasterViewController.m
//
// Copyright 2012 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 "GooglePlusSampleMasterViewController.h"
#import "GooglePlusSampleShareViewController.h"
#import "GooglePlusSampleSignInViewController.h"
#import "GooglePlusSampleMomentsViewController.h"
static const int kNumViewControllers = 3;
static NSString * const kMenuOptions[kNumViewControllers] = {
@"Sign In", @"Share", @"Moments" };
static NSString * const kNibNames[kNumViewControllers] = {
@"GooglePlusSampleSignInViewController",
@"GooglePlusSampleShareViewController",
@"GooglePlusSampleMomentsViewController" };
@implementation GooglePlusSampleMasterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = @"Google+ SDK Sample";
UIBarButtonItem *backButton = [[[UIBarButtonItem alloc]
initWithTitle:@"Back"
style:UIBarButtonItemStylePlain
target:self
action:@selector(backPressed)] autorelease];
self.navigationItem.backBarButtonItem = backButton;
}
return self;
}
#pragma mark - View lifecycle
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)
interfaceOrientation {
if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPhone) {
return interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
}
return YES;
}
#pragma mark - UITableViewDelegate/UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return kNumViewControllers;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * const kCellIdentifier = @"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell =
[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:kCellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.textLabel.text = kMenuOptions[indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Class nibClass = NSClassFromString(kNibNames[indexPath.row]);
UIViewController *controller =
[[[nibClass alloc] initWithNibName:nil bundle:nil] autorelease];
controller.navigationItem.title = kMenuOptions[indexPath.row];
[self.navigationController pushViewController:controller animated:YES];
}
@end

View File

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1280</int>
<string key="IBDocument.SystemVersion">10K549</string>
<string key="IBDocument.InterfaceBuilderVersion">1938</string>
<string key="IBDocument.AppKitVersion">1038.36</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">933</string>
</object>
<array key="IBDocument.IntegratedClassDependencies">
<string>IBUINavigationItem</string>
<string>IBUITableView</string>
<string>IBUITableViewController</string>
<string>IBUINavigationController</string>
<string>IBUINavigationBar</string>
<string>IBProxyObject</string>
</array>
<array key="IBDocument.PluginDependencies">
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</array>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<object class="IBProxyObject" id="841351856">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="371349661">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUITableView" id="709618507">
<reference key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<string key="NSFrame">{{0, 20}, {320, 460}}</string>
<reference key="NSSuperview"/>
<object class="NSColor" key="IBUIBackgroundColor" id="800680415">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
<bool key="IBUIClipsSubviews">YES</bool>
<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIAlwaysBounceVertical">YES</bool>
<int key="IBUISeparatorStyle">1</int>
<int key="IBUISectionIndexMinimumDisplayRowCount">0</int>
<bool key="IBUIShowsSelectionImmediatelyOnTouchBegin">YES</bool>
<float key="IBUIRowHeight">44</float>
<float key="IBUISectionHeaderHeight">22</float>
<float key="IBUISectionFooterHeight">22</float>
</object>
<object class="IBUINavigationController" id="271496582">
<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
<object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
<int key="IBUIInterfaceOrientation">1</int>
<int key="interfaceOrientation">1</int>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIHorizontal">NO</bool>
<object class="IBUINavigationBar" key="IBUINavigationBar" id="264651278">
<nil key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{0, 0}</string>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIMultipleTouchEnabled">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<array key="IBUIViewControllers">
<object class="IBUITableViewController" id="84492071">
<object class="IBUITableView" key="IBUIView" id="1024288683">
<reference key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<string key="NSFrame">{{0, 64}, {320, 416}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView"/>
<reference key="IBUIBackgroundColor" ref="800680415"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIAlwaysBounceVertical">YES</bool>
<int key="IBUISeparatorStyle">1</int>
<int key="IBUISectionIndexMinimumDisplayRowCount">0</int>
<bool key="IBUIShowsSelectionImmediatelyOnTouchBegin">YES</bool>
<float key="IBUIRowHeight">44</float>
<float key="IBUISectionHeaderHeight">22</float>
<float key="IBUISectionFooterHeight">22</float>
</object>
<object class="IBUINavigationItem" key="IBUINavigationItem" id="848890509">
<reference key="IBUINavigationBar"/>
<string key="IBUITitle">Google Plus Sample App</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<reference key="IBUIParentViewController" ref="271496582"/>
<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
<object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
<int key="IBUIInterfaceOrientation">1</int>
<int key="interfaceOrientation">1</int>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIHorizontal">NO</bool>
</object>
</array>
</object>
</array>
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords">
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="841351856"/>
<reference key="destination" ref="709618507"/>
</object>
<int key="connectionID">3</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">dataSource</string>
<reference key="source" ref="709618507"/>
<reference key="destination" ref="841351856"/>
</object>
<int key="connectionID">4</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="709618507"/>
<reference key="destination" ref="841351856"/>
</object>
<int key="connectionID">5</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1024288683"/>
<reference key="destination" ref="84492071"/>
</object>
<int key="connectionID">12</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">dataSource</string>
<reference key="source" ref="1024288683"/>
<reference key="destination" ref="84492071"/>
</object>
<int key="connectionID">13</int>
</object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<array key="object" id="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="841351856"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="371349661"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">2</int>
<reference key="object" ref="709618507"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">6</int>
<reference key="object" ref="271496582"/>
<array class="NSMutableArray" key="children">
<reference ref="264651278"/>
<reference ref="84492071"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">7</int>
<reference key="object" ref="264651278"/>
<reference key="parent" ref="271496582"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">10</int>
<reference key="object" ref="84492071"/>
<array class="NSMutableArray" key="children">
<reference ref="1024288683"/>
<reference ref="848890509"/>
</array>
<reference key="parent" ref="271496582"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">11</int>
<reference key="object" ref="1024288683"/>
<reference key="parent" ref="84492071"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">14</int>
<reference key="object" ref="848890509"/>
<reference key="parent" ref="84492071"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.CustomClassName">GooglePlusSampleMasterViewController</string>
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="-2.CustomClassName">UIResponder</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="10.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="11.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="14.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">14</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
<object class="IBPartialClassDescription">
<string key="className">GooglePlusSampleMasterViewController</string>
<string key="superclassName">UITableViewController</string>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/GooglePlusSampleMasterViewController.h</string>
</object>
</object>
</array>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<string key="IBCocoaTouchPluginVersion">933</string>
</data>
</archive>

View File

@ -0,0 +1,45 @@
//
// GooglePlusSampleMomentsViewController.h
//
// Copyright 2012 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 <UIKit/UIKit.h>
@class GTLServicePlus;
// A view controller for writing different kinds of moments to Google+ history.
// The open-source GTLPlus libraries are required. For more details, see
// https://developers.google.com/+/history/ .
@interface GooglePlusSampleMomentsViewController : UIViewController<
UITableViewDelegate,
UITableViewDataSource,
UITextFieldDelegate> {
BOOL keyboardVisible_;
}
// The table that displays the different kinds of moments available.
@property (retain, nonatomic) IBOutlet UITableView *momentsTable;
// The target URL to associate with this moment.
@property (retain, nonatomic) IBOutlet UITextField *momentURL;
// A label to display the result of writing a moment.
@property (retain, nonatomic) IBOutlet UILabel *momentStatus;
// The "Add Moment" button.
@property (retain, nonatomic) IBOutlet UIButton *addButton;
// Called when the user presses the "Add Moment" button.
- (IBAction)momentButton:(id)sender;
@end

View File

@ -0,0 +1,334 @@
//
// GooglePlusSampleMomentsViewController.m
//
// Copyright 2012 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 "GooglePlusSampleMomentsViewController.h"
#import <QuartzCore/QuartzCore.h>
#import "GTLPlus.h"
#import "GTLPlusConstants.h"
#import "GTLPlusItemScope.h"
#import "GTLPlusMoment.h"
#import "GTLQueryPlus.h"
#import "GTLServicePlus.h"
#import "GTMLogger.h"
#import "GTMOAuth2Authentication.h"
#import "GooglePlusSampleAppDelegate.h"
@interface GooglePlusSampleMomentsViewController ()
- (GTLPlusItemScope *)resultFor:(NSString *)selectedMoment;
- (void)animateKeyboard:(NSNotification *)notification
shouldShow:(BOOL)shouldShow;
- (NSString *)momentURLForIndex:(int)i;
- (void)reportAuthStatus;
@end
@implementation GooglePlusSampleMomentsViewController
@synthesize momentsTable = momentsTable_;
@synthesize momentURL = momentURL_;
@synthesize momentStatus = momentStatus_;
@synthesize addButton = addButton_;
// The different kinds of moments.
static const int kNumMomentTypes = 9;
static NSString * const kMomentTypes[kNumMomentTypes] = {
@"AddActivity",
@"BuyActivity",
@"CheckInActivity",
@"CommentActivity",
@"CreateActivity",
@"ListenActivity",
@"ReserveActivity",
@"ReviewActivity",
@"ViewActivity" };
static NSString * const kMomentURLs[kNumMomentTypes] = {
@"thing",
@"a-book",
@"place",
@"blog-entry",
@"photo",
@"song",
@"restaurant",
@"widget",
@"video" };
static NSString * const kMomentURLFormat =
@"https://developers.google.com/+/plugins/snippet/examples/%@";
#pragma mark - Object lifecycle
- (void)dealloc {
// Unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
[momentsTable_ release];
[momentURL_ release];
[momentStatus_ release];
[addButton_ release];
[super dealloc];
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
// Set up "Add Moment" button.
[[addButton_ layer] setCornerRadius:5];
[[addButton_ layer] setMasksToBounds:YES];
CGColorRef borderColor = [[UIColor colorWithWhite:203.0/255.0
alpha:1.0] CGColor];
[[addButton_ layer] setBorderColor:borderColor];
[[addButton_ layer] setBorderWidth:1.0];
// Set up sample view of writing moments.
int selectedRow = [[momentsTable_ indexPathForSelectedRow] row];
momentURL_.text = [self momentURLForIndex:selectedRow];
[self reportAuthStatus];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Register for keyboard notifications while visible.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
// Unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
[super viewWillDisappear:animated];
}
#pragma mark - IBActions
- (IBAction)momentButton:(id)sender {
GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *)
[[UIApplication sharedApplication] delegate];
if (!appDelegate.auth) {
// To authenticate, use Google+ sign-in button.
momentStatus_.text = @"Status: Not authenticated";
return;
}
// Here is an example of writing a moment to Google+ history:
// 1. Create a |GTLServicePlus| instance to send a request to Google+.
GTLServicePlus* plusService = [[[GTLServicePlus alloc] init] autorelease];
plusService.retryEnabled = YES;
// 2. Set a valid |GTMOAuth2Authentication| object as the authorizer.
[plusService setAuthorizer:appDelegate.auth];
// 3. Create a |GTLPlusMoment| object with required fields. For reference, see
// https://developers.google.com/+/history/ .
int selectedRow = [[momentsTable_ indexPathForSelectedRow] row];
NSString *selectedMoment = kMomentTypes[selectedRow];
GTLPlusMoment *moment = [[[GTLPlusMoment alloc] init] autorelease];
moment.type = [NSString stringWithFormat:@"https://schemas.google.com/%@",
selectedMoment];
GTLPlusItemScope *target = [[[GTLPlusItemScope alloc] init] autorelease];
target.url = momentURL_.text;
if ([target.url isEqualToString:@""]) {
target.url = [self momentURLForIndex:selectedRow];
}
moment.target = target;
// CommentActivity, ReserveActivity, and ReviewActivity require setting a
// |result| field in the request.
GTLPlusItemScope *result = [self resultFor:selectedMoment];
if (result) {
moment.result = result;
}
// 4. Create a |GTLQuery| object to write a moment.
GTLQueryPlus *query =
[GTLQueryPlus queryForMomentsInsertWithObject:moment
userId:@"me"
collection:kGTLPlusCollectionVault];
[plusService executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
id object,
NSError *error) {
if (error) {
GTMLoggerError(@"Error: %@", error);
momentStatus_.text =
[NSString stringWithFormat:@"Status: Error: %@", error];
} else {
momentStatus_.text = [NSString stringWithFormat:
@"Status: Saved to Google+ history (%@)",
selectedMoment];
}
}];
}
#pragma mark - UITableViewDelegate/UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return kNumMomentTypes;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * const kCellIdentifier = @"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:kCellIdentifier]
autorelease];
cell.accessoryType = UITableViewCellAccessoryNone;
}
// Configure the cell.
cell.textLabel.text = kMomentTypes[indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int selectedRow = [[momentsTable_ indexPathForSelectedRow] row];
momentURL_.text = [self momentURLForIndex:selectedRow];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#pragma mark - UIKeyboard
- (void)keyboardWillShow:(NSNotification *)notification {
[self animateKeyboard:notification shouldShow:YES];
}
- (void)keyboardWillHide:(NSNotification *)notification {
[self animateKeyboard:notification shouldShow:NO];
}
#pragma mark - Private methods
// Helps set required result field for select moment types.
- (GTLPlusItemScope *)resultFor:(NSString *)selectedMoment {
GTLPlusItemScope *result = [[[GTLPlusItemScope alloc] init] autorelease];
if ([selectedMoment isEqualToString:@"CommentActivity"]) {
result.type = @"http://schema.org/Comment";
result.url =
@"https://developers.google.com/+/plugins/snippet/examples/blog-entry#comment-1";
result.name = @"This is amazing!";
result.text = @"I can't wait to use it on my site :)";
return result;
} else if ([selectedMoment isEqualToString:@"ReserveActivity"]) {
result.type = @"http://schema.org/Reservation";
result.startDate = @"2012-06-28T19:00:00-08:00";
result.attendeeCount = [[[NSNumber alloc] initWithInt:3] autorelease];
return result;
} else if ([selectedMoment isEqualToString:@"ReviewActivity"]) {
result.type = @"http://schema.org/Review";
result.name = @"A Humble Review of Widget";
result.url =
@"https://developers.google.com/+/plugins/snippet/examples/review";
result.text =
@"It's amazingly effective at whatever it is that it's supposed to do.";
GTLPlusItemScope *rating = [[[GTLPlusItemScope alloc] init] autorelease];
rating.type = @"http://schema.org/Rating";
rating.ratingValue = @"100";
rating.bestRating = @"100";
rating.worstRating = @"0";
result.reviewRating =
[[[NSArray alloc] initWithObjects:rating, nil] autorelease];
return result;
}
return nil;
}
// Helps animate keyboard for target URL text field.
- (void)animateKeyboard:(NSNotification *)notification
shouldShow:(BOOL)shouldShow {
NSDictionary *userInfo = [notification userInfo];
CGFloat height = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey]
CGRectValue].size.height;
CGFloat duration = [[userInfo
objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
CGRect newFrame = self.view.frame;
if (shouldShow) {
newFrame.size.height -= height;
} else {
newFrame.size.height += height;
}
self.view.frame = newFrame;
[UIView commitAnimations];
if (shouldShow) {
keyboardVisible_ = YES;
} else {
keyboardVisible_ = NO;
}
}
- (NSString *)momentURLForIndex:(int)i {
return [NSString stringWithFormat:kMomentURLFormat, kMomentURLs[i]];
}
- (void)reportAuthStatus {
NSString *authStatus = @"";
GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *)
[[UIApplication sharedApplication] delegate];
if (appDelegate.auth) {
authStatus = @"Status: Authenticated";
} else {
// To authenticate, use Google+ sign-in button.
authStatus = @"Status: Not authenticated";
}
momentStatus_.text = authStatus;
}
@end

View File

@ -0,0 +1,462 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1280</int>
<string key="IBDocument.SystemVersion">10K549</string>
<string key="IBDocument.InterfaceBuilderVersion">1938</string>
<string key="IBDocument.AppKitVersion">1038.36</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">933</string>
</object>
<array key="IBDocument.IntegratedClassDependencies">
<string>IBUITextField</string>
<string>IBUITableView</string>
<string>IBUIButton</string>
<string>IBUIView</string>
<string>IBUILabel</string>
<string>IBProxyObject</string>
</array>
<array key="IBDocument.PluginDependencies">
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</array>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<object class="IBProxyObject" id="372490531">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="975951072">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIView" id="191373211">
<reference key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<array class="NSMutableArray" key="NSSubviews">
<object class="IBUILabel" id="29623071">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">290</int>
<string key="NSFrame">{{10, 5}, {296, 21}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="1013113918"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Select an activity (scroll for more)</string>
<object class="NSColor" key="IBUITextColor" id="216027142">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MCAwIDAAA</bytes>
</object>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">10</float>
<object class="IBUIFontDescription" key="IBUIFontDescription" id="950407250">
<int key="type">1</int>
<double key="pointSize">17</double>
</object>
<object class="NSFont" key="IBUIFont" id="63576769">
<string key="NSName">Helvetica</string>
<double key="NSSize">17</double>
<int key="NSfFlags">16</int>
</object>
</object>
<object class="IBUITableView" id="1013113918">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">274</int>
<string key="NSFrame">{{0, 32}, {320, 132}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="885245054"/>
<object class="NSColor" key="IBUIBackgroundColor" id="373936525">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
<bool key="IBUIClipsSubviews">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBUIAlwaysBounceVertical">YES</bool>
<int key="IBUISeparatorStyle">1</int>
<int key="IBUISectionIndexMinimumDisplayRowCount">0</int>
<bool key="IBUIShowsSelectionImmediatelyOnTouchBegin">YES</bool>
<float key="IBUIRowHeight">44</float>
<float key="IBUISectionHeaderHeight">22</float>
<float key="IBUISectionFooterHeight">22</float>
</object>
<object class="IBUIView" id="885245054">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">266</int>
<array class="NSMutableArray" key="NSSubviews">
<object class="IBUILabel" id="625104685">
<reference key="NSNextResponder" ref="885245054"/>
<int key="NSvFlags">266</int>
<string key="NSFrame">{{11, 20}, {296, 21}}</string>
<reference key="NSSuperview" ref="885245054"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="621451586"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Enter a Moment URL</string>
<reference key="IBUITextColor" ref="216027142"/>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">10</float>
<reference key="IBUIFontDescription" ref="950407250"/>
<reference key="IBUIFont" ref="63576769"/>
</object>
<object class="IBUITextField" id="621451586">
<reference key="NSNextResponder" ref="885245054"/>
<int key="NSvFlags">266</int>
<string key="NSFrame">{{11, 51}, {291, 31}}</string>
<reference key="NSSuperview" ref="885245054"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="525343048"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText"/>
<int key="IBUIBorderStyle">3</int>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<object class="NSColorSpace" key="NSCustomColorSpace" id="523142132">
<int key="NSID">2</int>
</object>
</object>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">17</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIFontDescription" key="IBUIFontDescription">
<int key="type">1</int>
<double key="pointSize">14</double>
</object>
<object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica</string>
<double key="NSSize">14</double>
<int key="NSfFlags">16</int>
</object>
</object>
<object class="IBUIButton" id="525343048">
<reference key="NSNextResponder" ref="885245054"/>
<int key="NSvFlags">264</int>
<string key="NSFrame">{{12, 113}, {142, 37}}</string>
<reference key="NSSuperview" ref="885245054"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="851809288"/>
<bool key="IBUIOpaque">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentHorizontalAlignment">0</int>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUINormalTitle">Add Moment</string>
<object class="NSColor" key="IBUIHighlightedTitleColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MC40MzkyMTU2ODYzIDAuMTI1NDkwMTk2MSAwLjA2Mjc0NTA5ODA0AA</bytes>
</object>
<object class="NSColor" key="IBUINormalTitleColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MC42MzUyOTQxMTc2IDAuMzIxNTY4NjI3NSAwLjI1ODgyMzUyOTQAA</bytes>
</object>
<object class="NSColor" key="IBUINormalTitleShadowColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC41AA</bytes>
</object>
<object class="NSCustomResource" key="IBUINormalBackgroundImage">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">button_background.png</string>
</object>
<object class="IBUIFontDescription" key="IBUIFontDescription">
<int key="type">2</int>
<double key="pointSize">15</double>
</object>
<object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica-Bold</string>
<double key="NSSize">15</double>
<int key="NSfFlags">16</int>
</object>
</object>
<object class="IBUILabel" id="851809288">
<reference key="NSNextResponder" ref="885245054"/>
<int key="NSvFlags">266</int>
<string key="NSFrame">{{11, 180}, {290, 21}}</string>
<reference key="NSSuperview" ref="885245054"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Status:</string>
<reference key="IBUITextColor" ref="216027142"/>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">11</float>
<reference key="IBUIFontDescription" ref="950407250"/>
<reference key="IBUIFont" ref="63576769"/>
</object>
</array>
<string key="NSFrame">{{0, 172}, {320, 244}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="625104685"/>
<reference key="IBUIBackgroundColor" ref="373936525"/>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</array>
<string key="NSFrame">{{0, 64}, {320, 416}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="29623071"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
<reference key="NSCustomColorSpace" ref="523142132"/>
</object>
<array key="IBUIGestureRecognizers" id="0"/>
<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
<object class="IBUISimulatedNavigationBarMetrics" key="IBUISimulatedTopBarMetrics">
<bool key="IBUIPrompted">NO</bool>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</array>
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords">
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="191373211"/>
</object>
<int key="connectionID">3</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">momentsTable</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="1013113918"/>
</object>
<int key="connectionID">17</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">momentStatus</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="851809288"/>
</object>
<int key="connectionID">25</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">addButton</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="525343048"/>
</object>
<int key="connectionID">28</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">momentURL</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="621451586"/>
</object>
<int key="connectionID">30</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">dataSource</string>
<reference key="source" ref="1013113918"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">18</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1013113918"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">19</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="621451586"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">31</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">momentButton:</string>
<reference key="source" ref="525343048"/>
<reference key="destination" ref="372490531"/>
<int key="IBEventType">7</int>
</object>
<int key="connectionID">20</int>
</object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<reference key="object" ref="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1</int>
<reference key="object" ref="191373211"/>
<array class="NSMutableArray" key="children">
<reference ref="29623071"/>
<reference ref="885245054"/>
<reference ref="1013113918"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="372490531"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="975951072"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">7</int>
<reference key="object" ref="1013113918"/>
<array class="NSMutableArray" key="children"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">26</int>
<reference key="object" ref="29623071"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">27</int>
<reference key="object" ref="885245054"/>
<array class="NSMutableArray" key="children">
<reference ref="621451586"/>
<reference ref="525343048"/>
<reference ref="625104685"/>
<reference ref="851809288"/>
</array>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">13</int>
<reference key="object" ref="625104685"/>
<reference key="parent" ref="885245054"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">12</int>
<reference key="object" ref="621451586"/>
<reference key="parent" ref="885245054"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">16</int>
<reference key="object" ref="525343048"/>
<reference key="parent" ref="885245054"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">22</int>
<reference key="object" ref="851809288"/>
<reference key="parent" ref="885245054"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.CustomClassName">GooglePlusSampleMomentsViewController</string>
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="-2.CustomClassName">UIResponder</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="12.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="13.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="16.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<real value="0.0" key="16.IBUIButtonInspectorSelectedStateConfigurationMetadataKey"/>
<string key="22.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="26.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="27.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">31</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
<object class="IBPartialClassDescription">
<string key="className">GooglePlusSampleMomentsViewController</string>
<string key="superclassName">UIViewController</string>
<object class="NSMutableDictionary" key="actions">
<string key="NS.key.0">momentButton:</string>
<string key="NS.object.0">id</string>
</object>
<object class="NSMutableDictionary" key="actionInfosByName">
<string key="NS.key.0">momentButton:</string>
<object class="IBActionInfo" key="NS.object.0">
<string key="name">momentButton:</string>
<string key="candidateClassName">id</string>
</object>
</object>
<dictionary class="NSMutableDictionary" key="outlets">
<string key="addButton">UIButton</string>
<string key="momentStatus">UILabel</string>
<string key="momentURL">UITextField</string>
<string key="momentsTable">UITableView</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
<object class="IBToOneOutletInfo" key="addButton">
<string key="name">addButton</string>
<string key="candidateClassName">UIButton</string>
</object>
<object class="IBToOneOutletInfo" key="momentStatus">
<string key="name">momentStatus</string>
<string key="candidateClassName">UILabel</string>
</object>
<object class="IBToOneOutletInfo" key="momentURL">
<string key="name">momentURL</string>
<string key="candidateClassName">UITextField</string>
</object>
<object class="IBToOneOutletInfo" key="momentsTable">
<string key="name">momentsTable</string>
<string key="candidateClassName">UITableView</string>
</object>
</dictionary>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/GooglePlusSampleMomentsViewController.h</string>
</object>
</object>
</array>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
<string key="NS.key.0">button_background.png</string>
<string key="NS.object.0">{1, 1}</string>
</object>
<string key="IBCocoaTouchPluginVersion">933</string>
</data>
</archive>

View File

@ -0,0 +1,50 @@
//
// GooglePlusSampleShareViewController.h
//
// Copyright 2012 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 <MessageUI/MFMailComposeViewController.h>
#import <MessageUI/MessageUI.h>
#import <UIKit/UIKit.h>
#import "GooglePlusShare.h"
// A view controller for the Google+ share dialog which contains a text field
// to prefill the user comment, and a text field for an optional URL to share.
// A Google+ share button is provided to launch the share dialog.
@interface GooglePlusSampleShareViewController : UIViewController<
GooglePlusShareDelegate,
UITextFieldDelegate,
UIActionSheetDelegate,
MFMailComposeViewControllerDelegate> {
// The Google+ share object to manage the share dialog.
GooglePlusShare *share_;
}
// The text to prefill the user comment in the share dialog.
@property (retain, nonatomic) IBOutlet UITextField *sharePrefillText;
// The URL resource to share in the share dialog.
@property (retain, nonatomic) IBOutlet UITextField *shareURL;
// A label to display the result of the share action.
@property (retain, nonatomic) IBOutlet UILabel *shareStatus;
// A toolbar to share via Google+ or email.
@property (retain, nonatomic) IBOutlet UIToolbar *shareToolbar;
// Called when the share button is pressed.
- (IBAction)shareButton:(id)sender;
// Called when the toolbar share button is pressed.
- (IBAction)shareToolbar:(id)sender;
@end

View File

@ -0,0 +1,145 @@
//
// GooglePlusSampleShareViewController.m
//
// Copyright 2012 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 "GooglePlusSampleShareViewController.h"
#import "GooglePlusSampleAppDelegate.h"
@implementation GooglePlusSampleShareViewController
@synthesize sharePrefillText = sharePrefillText_;
@synthesize shareURL = shareURL_;
@synthesize shareStatus = shareStatus_;
@synthesize shareToolbar = shareToolbar_;
- (void)dealloc {
[sharePrefillText_ release];
[shareURL_ release];
[shareStatus_ release];
[share_ release];
[shareToolbar_ release];
[super dealloc];
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
// Set up Google+ share dialog.
GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *)
[[UIApplication sharedApplication] delegate];
NSString *clientID = [GooglePlusSampleAppDelegate clientID];
share_ = [[GooglePlusShare alloc] initWithClientID:clientID];
share_.delegate = self;
appDelegate.share = share_;
[super viewDidLoad];
}
- (void)viewDidUnload {
GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *)
[[UIApplication sharedApplication] delegate];
appDelegate.share = nil;
share_.delegate = nil;
[share_ release];
share_ = nil;
[super viewDidUnload];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#pragma mark - GooglePlusShareDelegate
- (void)finishedSharing:(BOOL)shared {
NSString *text = shared ? @"Success" : @"Canceled";
shareStatus_.text = [NSString stringWithFormat:@"Status: %@", text];
}
#pragma mark - UIActionSheetDelegate
- (void)actionSheet:(UIActionSheet *)actionSheet
didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
[self shareButton:nil];
} else if (buttonIndex == 1) {
shareStatus_.text = @"Status: Sharing...";
MFMailComposeViewController *picker =
[[[MFMailComposeViewController alloc] init] autorelease];
picker.mailComposeDelegate = self;
[picker setSubject:sharePrefillText_.text];
[picker setMessageBody:shareURL_.text isHTML:NO];
[self presentModalViewController:picker animated:YES];
}
}
#pragma mark - MFMailComposeViewControllerDelegate
- (void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error {
NSString *text;
switch (result) {
case MFMailComposeResultCancelled:
text = @"Canceled";
break;
case MFMailComposeResultSaved:
text = @"Saved";
break;
case MFMailComposeResultSent:
text = @"Sent";
break;
case MFMailComposeResultFailed:
text = @"Failed";
break;
default:
text = @"Not sent";
break;
}
shareStatus_.text = [NSString stringWithFormat:@"Status: %@", text];
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark - IBActions
- (IBAction)shareButton:(id)sender {
NSString *inputURL = shareURL_.text;
NSURL *urlToShare = [inputURL length] ? [NSURL URLWithString:inputURL] : nil;
NSString *inputText = sharePrefillText_.text;
NSString *text = [inputText length] ? inputText : nil;
shareStatus_.text = @"Status: Sharing...";
[[[[share_ shareDialog] setURLToShare:urlToShare] setPrefillText:text] open];
}
- (IBAction)shareToolbar:(id)sender {
UIActionSheet *actionSheet =
[[[UIActionSheet alloc] initWithTitle:@"Share this post"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Google+", @"Email", nil]
autorelease];
[actionSheet showFromToolbar:shareToolbar_];
}
@end

View File

@ -0,0 +1,493 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1280</int>
<string key="IBDocument.SystemVersion">10K549</string>
<string key="IBDocument.InterfaceBuilderVersion">1938</string>
<string key="IBDocument.AppKitVersion">1038.36</string>
<string key="IBDocument.HIToolboxVersion">461.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="NS.object.0">933</string>
</object>
<array key="IBDocument.IntegratedClassDependencies">
<string>IBUIView</string>
<string>IBProxyObject</string>
<string>IBUILabel</string>
<string>IBUIToolbar</string>
<string>IBUIBarButtonItem</string>
<string>IBUITextField</string>
<string>IBUIButton</string>
</array>
<array key="IBDocument.PluginDependencies">
<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</array>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<object class="IBProxyObject" id="372490531">
<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBProxyObject" id="975951072">
<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIView" id="191373211">
<reference key="NSNextResponder"/>
<int key="NSvFlags">274</int>
<array class="NSMutableArray" key="NSSubviews">
<object class="IBUITextField" id="914769097">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{20, 55}, {280, 31}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="72662020"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText">http://developers.google.com/</string>
<int key="IBUIBorderStyle">3</int>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<object class="NSColorSpace" key="NSCustomColorSpace" id="191774102">
<int key="NSID">2</int>
</object>
</object>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">17</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<object class="IBUIFontDescription" key="IBUIFontDescription" id="494599157">
<int key="type">1</int>
<double key="pointSize">14</double>
</object>
<object class="NSFont" key="IBUIFont" id="583966885">
<string key="NSName">Helvetica</string>
<double key="NSSize">14</double>
<int key="NSfFlags">16</int>
</object>
</object>
<object class="IBUITextField" id="410760382">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{20, 144}, {280, 31}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="483186074"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentVerticalAlignment">0</int>
<string key="IBUIText">Welcome to Google+ Platform</string>
<int key="IBUIBorderStyle">3</int>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
<reference key="NSCustomColorSpace" ref="191774102"/>
</object>
<bool key="IBUIAdjustsFontSizeToFit">YES</bool>
<float key="IBUIMinimumFontSize">17</float>
<object class="IBUITextInputTraits" key="IBUITextInputTraits">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
<reference key="IBUIFontDescription" ref="494599157"/>
<reference key="IBUIFont" ref="583966885"/>
</object>
<object class="IBUILabel" id="108661184">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{20, 26}, {179, 21}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="914769097"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">URL to Share (optional)</string>
<object class="NSColor" key="IBUITextColor" id="867790682">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MCAwIDAAA</bytes>
</object>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">10</float>
<object class="IBUIFontDescription" key="IBUIFontDescription" id="1056628031">
<int key="type">1</int>
<double key="pointSize">17</double>
</object>
<object class="NSFont" key="IBUIFont" id="1071033096">
<string key="NSName">Helvetica</string>
<double key="NSSize">17</double>
<int key="NSfFlags">16</int>
</object>
</object>
<object class="IBUILabel" id="72662020">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{20, 115}, {156, 21}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="410760382"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Prefill Text (optional)</string>
<reference key="IBUITextColor" ref="867790682"/>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">10</float>
<reference key="IBUIFontDescription" ref="1056628031"/>
<reference key="IBUIFont" ref="1071033096"/>
</object>
<object class="IBUIButton" id="483186074">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">292</int>
<string key="NSFrame">{{21, 209}, {112, 32}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="94975264"/>
<bool key="IBUIOpaque">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentHorizontalAlignment">0</int>
<int key="IBUIContentVerticalAlignment">0</int>
<object class="NSColor" key="IBUIHighlightedTitleColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
</object>
<object class="NSColor" key="IBUINormalTitleColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA</bytes>
</object>
<object class="NSColor" key="IBUINormalTitleShadowColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC41AA</bytes>
</object>
<object class="NSCustomResource" key="IBUINormalBackgroundImage">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">google_plus_share_large.png</string>
</object>
<object class="IBUIFontDescription" key="IBUIFontDescription">
<int key="type">2</int>
<double key="pointSize">15</double>
</object>
<object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica-Bold</string>
<double key="NSSize">15</double>
<int key="NSfFlags">16</int>
</object>
</object>
<object class="IBUILabel" id="94975264">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">290</int>
<string key="NSFrame">{{20, 283}, {280, 21}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="819777366"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClipsSubviews">YES</bool>
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Status:</string>
<reference key="IBUITextColor" ref="867790682"/>
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">1</int>
<float key="IBUIMinimumFontSize">15</float>
<reference key="IBUIFontDescription" ref="1056628031"/>
<reference key="IBUIFont" ref="1071033096"/>
</object>
<object class="IBUIToolbar" id="819777366">
<reference key="NSNextResponder" ref="191373211"/>
<int key="NSvFlags">266</int>
<string key="NSFrame">{{0, 372}, {320, 44}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView"/>
<bool key="IBUIOpaque">NO</bool>
<bool key="IBUIClearsContextBeforeDrawing">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<array class="NSMutableArray" key="IBUIItems">
<object class="IBUIBarButtonItem" id="937701942">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<reference key="IBUIToolbar" ref="819777366"/>
<int key="IBUISystemItemIdentifier">5</int>
</object>
<object class="IBUIBarButtonItem" id="47552833">
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIStyle">1</int>
<reference key="IBUIToolbar" ref="819777366"/>
<int key="IBUISystemItemIdentifier">9</int>
</object>
</array>
</object>
</array>
<string key="NSFrame">{{0, 64}, {320, 416}}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="108661184"/>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
<reference key="NSCustomColorSpace" ref="191774102"/>
</object>
<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
<object class="IBUISimulatedNavigationBarMetrics" key="IBUISimulatedTopBarMetrics">
<bool key="IBUIPrompted">NO</bool>
</object>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
</object>
</array>
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords">
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">view</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="191373211"/>
</object>
<int key="connectionID">3</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">sharePrefillText</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="410760382"/>
</object>
<int key="connectionID">11</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">shareStatus</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="94975264"/>
</object>
<int key="connectionID">14</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">shareToolbar</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="819777366"/>
</object>
<int key="connectionID">19</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">shareURL</string>
<reference key="source" ref="372490531"/>
<reference key="destination" ref="914769097"/>
</object>
<int key="connectionID">21</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="914769097"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">23</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="410760382"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">22</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">shareButton:</string>
<reference key="source" ref="483186074"/>
<reference key="destination" ref="372490531"/>
<int key="IBEventType">7</int>
</object>
<int key="connectionID">10</int>
</object>
<object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">shareToolbar:</string>
<reference key="source" ref="47552833"/>
<reference key="destination" ref="372490531"/>
</object>
<int key="connectionID">20</int>
</object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<array key="object" id="0"/>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">1</int>
<reference key="object" ref="191373211"/>
<array class="NSMutableArray" key="children">
<reference ref="108661184"/>
<reference ref="914769097"/>
<reference ref="72662020"/>
<reference ref="410760382"/>
<reference ref="483186074"/>
<reference ref="94975264"/>
<reference ref="819777366"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="372490531"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="975951072"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">5</int>
<reference key="object" ref="914769097"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">6</int>
<reference key="object" ref="410760382"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">7</int>
<reference key="object" ref="108661184"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">8</int>
<reference key="object" ref="72662020"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">9</int>
<reference key="object" ref="483186074"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">13</int>
<reference key="object" ref="94975264"/>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">15</int>
<reference key="object" ref="819777366"/>
<array class="NSMutableArray" key="children">
<reference ref="47552833"/>
<reference ref="937701942"/>
</array>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">17</int>
<reference key="object" ref="47552833"/>
<reference key="parent" ref="819777366"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">18</int>
<reference key="object" ref="937701942"/>
<reference key="parent" ref="819777366"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.CustomClassName">GooglePlusSampleShareViewController</string>
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="-2.CustomClassName">UIResponder</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="13.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="15.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="17.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="18.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="8.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="9.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">23</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
<object class="IBPartialClassDescription">
<string key="className">GooglePlusSampleShareViewController</string>
<string key="superclassName">UIViewController</string>
<dictionary class="NSMutableDictionary" key="actions">
<string key="shareButton:">id</string>
<string key="shareToolbar:">id</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="actionInfosByName">
<object class="IBActionInfo" key="shareButton:">
<string key="name">shareButton:</string>
<string key="candidateClassName">id</string>
</object>
<object class="IBActionInfo" key="shareToolbar:">
<string key="name">shareToolbar:</string>
<string key="candidateClassName">id</string>
</object>
</dictionary>
<dictionary class="NSMutableDictionary" key="outlets">
<string key="sharePrefillText">UITextField</string>
<string key="shareStatus">UILabel</string>
<string key="shareToolbar">UIToolbar</string>
<string key="shareURL">UITextField</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
<object class="IBToOneOutletInfo" key="sharePrefillText">
<string key="name">sharePrefillText</string>
<string key="candidateClassName">UITextField</string>
</object>
<object class="IBToOneOutletInfo" key="shareStatus">
<string key="name">shareStatus</string>
<string key="candidateClassName">UILabel</string>
</object>
<object class="IBToOneOutletInfo" key="shareToolbar">
<string key="name">shareToolbar</string>
<string key="candidateClassName">UIToolbar</string>
</object>
<object class="IBToOneOutletInfo" key="shareURL">
<string key="name">shareURL</string>
<string key="candidateClassName">UITextField</string>
</object>
</dictionary>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/GooglePlusSampleShareViewController.h</string>
</object>
</object>
</array>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
<string key="NS.key.0">google_plus_share_large.png</string>
<string key="NS.object.0">{112, 32}</string>
</object>
<string key="IBCocoaTouchPluginVersion">933</string>
</data>
</archive>

View File

@ -0,0 +1,40 @@
//
// GooglePlusSignInViewController.h
//
// Copyright 2012 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 <UIKit/UIKit.h>
#import "GooglePlusSignIn.h"
@class GooglePlusSignInButton;
// A view controller for the Google+ sign-in button which initiates a standard
// OAuth 2.0 flow and provides an access token and a refresh token. A "Sign out"
// button is provided to allow users to sign out of this application.
@interface GooglePlusSampleSignInViewController : UIViewController<
GooglePlusSignInDelegate>
// The button that handles Google+ sign-in.
@property (retain, nonatomic) IBOutlet GooglePlusSignInButton *signInButton;
// A label to display the result of the sign-in action.
@property (retain, nonatomic) IBOutlet UILabel *signInAuthStatus;
// A button to sign out of this application.
@property (retain, nonatomic) IBOutlet UIButton *signOutButton;
// Called when the user presses the "Sign out" button.
- (IBAction)signOut:(id)sender;
@end

Some files were not shown because too many files have changed in this diff Show More