diff --git a/.gitmodules b/.gitmodules index 32520cec..4bee5f39 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "External/FontReplacer"] path = External/FontReplacer 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 diff --git a/External/Pearl b/External/Pearl index d295f7d4..19a70544 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit d295f7d413fb2afa9fcdf70079969fd78ac8fb70 +Subproject commit 19a7054441049ea1519fe0bb72bc52d4542964cc diff --git a/External/facebook-ios-sdk b/External/facebook-ios-sdk new file mode 160000 index 00000000..68253505 --- /dev/null +++ b/External/facebook-ios-sdk @@ -0,0 +1 @@ +Subproject commit 682535050207c771706823cbf0fc31a206b12956 diff --git a/External/google-plus-ios-sdk/Changelog b/External/google-plus-ios-sdk/Changelog new file mode 100644 index 00000000..90344a99 --- /dev/null +++ b/External/google-plus-ios-sdk/Changelog @@ -0,0 +1,3 @@ +2012-06-25 -- v1.0.0 +- Google+ sign-in button, share plugin, and Google+ history integration library + with sample app. diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLBase64.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBase64.h new file mode 100644 index 00000000..fd0a0518 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBase64.h @@ -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 + +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); diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLBase64.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBase64.m new file mode 100644 index 00000000..28938e24 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBase64.m @@ -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); +} diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchQuery.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchQuery.h new file mode 100644 index 00000000..68018bfa --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchQuery.h @@ -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 { + @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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchQuery.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchQuery.m new file mode 100644 index 00000000..f62eaca1 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchQuery.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchResult.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchResult.h new file mode 100644 index 00000000..9675aaf7 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchResult.h @@ -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 { + @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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchResult.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchResult.m new file mode 100644 index 00000000..f17748d5 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLBatchResult.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLDateTime.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLDateTime.h new file mode 100644 index 00000000..4a04b4dc --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLDateTime.h @@ -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 +#import "GTLDefines.h" + +@interface GTLDateTime : NSObject { + 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLDateTime.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLDateTime.m new file mode 100644 index 00000000..7e45759e --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLDateTime.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLDefines.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLDefines.h new file mode 100644 index 00000000..ebd96558 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLDefines.h @@ -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 +#import + +// +// 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLErrorObject.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLErrorObject.h new file mode 100644 index 00000000..c2ec67db --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLErrorObject.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLErrorObject.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLErrorObject.m new file mode 100644 index 00000000..1fa1023a --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLErrorObject.m @@ -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 + + diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLFramework.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLFramework.h new file mode 100644 index 00000000..106f420e --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLFramework.h @@ -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 + +#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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLFramework.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLFramework.m new file mode 100644 index 00000000..6bfc7f22 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLFramework.m @@ -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; +} diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLJSONParser.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLJSONParser.h new file mode 100644 index 00000000..d9715295 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLJSONParser.h @@ -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 + +#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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLJSONParser.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLJSONParser.m new file mode 100644 index 00000000..a089a93d --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLJSONParser.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLObject.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLObject.h new file mode 100644 index 00000000..fcbdf43c --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLObject.h @@ -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 + +#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 { + + @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 + +// 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLObject.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLObject.m new file mode 100644 index 00000000..cf4e84cc --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLObject.m @@ -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 + +#import "GTLObject.h" +#import "GTLRuntimeCommon.h" +#import "GTLJSONParser.h" + +static NSString *const kUserDataPropertyKey = @"_userData"; + +@interface GTLObject () ++ (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 batch = + (id ) 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)aClass { + NSDictionary *resultMap = + [GTLUtilities mergedClassDictionaryForSelector:@selector(propertyToJSONKeyMap) + startClass:aClass + ancestorClass:[GTLObject class] + cache:gJSONKeyMapCache]; + return resultMap; +} + ++ (NSDictionary *)arrayPropertyToClassMapForClass:(Class)aClass { + NSDictionary *resultMap = + [GTLUtilities mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap) + startClass:aClass + ancestorClass:[GTLObject class] + cache:gArrayPropertyToClassMapCache]; + return resultMap; +} + +#pragma mark Runtime Support + ++ (Class)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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlus.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlus.h new file mode 100644 index 00000000..ac691b73 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlus.h @@ -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" diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusConstants.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusConstants.h new file mode 100644 index 00000000..e2e425dd --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusConstants.h @@ -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 + +#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" diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusConstants.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusConstants.m new file mode 100644 index 00000000..2c15fda1 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusConstants.m @@ -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"; diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusItemScope.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusItemScope.h new file mode 100644 index 00000000..38166b26 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusItemScope.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusItemScope.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusItemScope.m new file mode 100644 index 00000000..e9c89799 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusItemScope.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusMoment.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusMoment.h new file mode 100644 index 00000000..1d4d98d1 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusMoment.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusMoment.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusMoment.m new file mode 100644 index 00000000..6d95a0ac --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusMoment.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusPerson.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusPerson.h new file mode 100644 index 00000000..95169fa8 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusPerson.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusPerson.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusPerson.m new file mode 100644 index 00000000..d56f8824 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLPlusPerson.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLQueryPlus.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLQueryPlus.h new file mode 100644 index 00000000..4d39389d --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLQueryPlus.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLQueryPlus.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLQueryPlus.m new file mode 100644 index 00000000..65bff36b --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLQueryPlus.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLServicePlus.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLServicePlus.h new file mode 100644 index 00000000..22a575b8 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLServicePlus.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLServicePlus.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLServicePlus.m new file mode 100644 index 00000000..ee66db50 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLPlus/GTLServicePlus.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLQuery.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLQuery.h new file mode 100644 index 00000000..0afe823e --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLQuery.h @@ -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 +- (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 { + @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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLQuery.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLQuery.m new file mode 100644 index 00000000..55f98223 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLQuery.m @@ -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 + +#import "GTLQuery.h" +#import "GTLRuntimeCommon.h" +#import "GTLUtilities.h" + +@interface GTLQuery () +@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)aClass { + NSDictionary *resultMap = + [GTLUtilities mergedClassDictionaryForSelector:@selector(parameterNameMap) + startClass:aClass + ancestorClass:[GTLQuery class] + cache:gParameterNameMapCache]; + return resultMap; +} + ++ (NSDictionary *)arrayPropertyToClassMapForClass:(Class)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)ancestorClass { + return [GTLQuery class]; +} + ++ (BOOL)resolveInstanceMethod:(SEL)sel { + BOOL resolved = [GTLRuntimeCommon resolveInstanceMethod:sel onClass:self]; + if (resolved) + return YES; + + return [super resolveInstanceMethod:sel]; +} + +@end diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLRuntimeCommon.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLRuntimeCommon.h new file mode 100644 index 00000000..28822b5e --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLRuntimeCommon.h @@ -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 + +#import "GTLDefines.h" + +// This protocol and support class are an internal implementation detail so +// GTLObject and GTLQuery can share some code. + +@protocol GTLRuntimeCommon +@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)aClass; +// Array item types ++ (NSDictionary *)arrayPropertyToClassMapForClass:(Class)aClass; +// The parent class for dynamic support ++ (Class)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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLRuntimeCommon.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLRuntimeCommon.m new file mode 100644 index 00000000..6b2880be --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLRuntimeCommon.m @@ -0,0 +1,1135 @@ +/* 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.m +// + +#include + +#import "GTLRuntimeCommon.h" + +#import "GTLDateTime.h" +#import "GTLObject.h" +#import "GTLUtilities.h" + +static NSString *const kReturnClassKey = @"returnClass"; +static NSString *const kContainedClassKey = @"containedClass"; +static NSString *const kJSONKey = @"jsonKey"; + +// Note: NSObject's class is used as a marker for the expected/default class +// when Discovery says it can be any type of object. + +@implementation GTLRuntimeCommon + +// Helper to generically convert JSON to an api object type. ++ (id)objectFromJSON:(id)json + defaultClass:(Class)defaultClass + surrogates:(NSDictionary *)surrogates + isCacheable:(BOOL*)isCacheable { + id result = nil; + BOOL canBeCached = YES; + + // TODO(TVL): use defaultClass to validate things like expectedClass is + // done in jsonFromAPIObject:expectedClass:isCacheable:? + + if ([json isKindOfClass:[NSDictionary class]]) { + // If no default, or the default was any object, then default to base + // object here (and hope there is a kind to get the right thing). + if ((defaultClass == Nil) || [defaultClass isEqual:[NSObject class]]) { + defaultClass = [GTLObject class]; + } + result = [GTLObject objectForJSON:json + defaultClass:defaultClass + surrogates:surrogates + batchClassMap:nil]; + } else if ([json isKindOfClass:[NSArray class]]) { + NSArray *jsonArray = json; + // make an object for each JSON dictionary in the array + NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:[jsonArray count]]; + for (id jsonItem in jsonArray) { + id item = [self objectFromJSON:jsonItem + defaultClass:defaultClass + surrogates:surrogates + isCacheable:NULL]; + [resultArray addObject:item]; + } + result = resultArray; + } else if ([json isKindOfClass:[NSString class]]) { + // DateTimes live in JSON as strings, so convert + if ([defaultClass isEqual:[GTLDateTime class]]) { + result = [GTLDateTime dateTimeWithRFC3339String:json]; + } else { + result = json; + canBeCached = NO; + } + } else if ([json isKindOfClass:[NSNumber class]] || + [json isKindOfClass:[NSNull class]]) { + result = json; + canBeCached = NO; + } else { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: unsupported class '%s' in objectFromJSON", + class_getName([json class])); + } + + if (isCacheable) { + *isCacheable = canBeCached; + } + return result; +} + +// Helper to generically convert an api object type to JSON. +// |expectedClass| is the type that was expected for |obj|. ++ (id)jsonFromAPIObject:(id)obj + expectedClass:(Class)expectedClass + isCacheable:(BOOL*)isCacheable { + id result = nil; + BOOL canBeCached = YES; + BOOL checkExpected = (expectedClass != Nil); + + if ([obj isKindOfClass:[NSString class]]) { + result = [[obj copy] autorelease]; + canBeCached = NO; + } else if ([obj isKindOfClass:[NSNumber class]] || + [obj isKindOfClass:[NSNull class]]) { + result = obj; + canBeCached = NO; + } else if ([obj isKindOfClass:[GTLObject class]]) { + result = [obj JSON]; + } else if ([obj isKindOfClass:[NSArray class]]) { + checkExpected = NO; + NSArray *array = obj; + // get the JSON for each thing in the array + NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:[array count]]; + for (id item in array) { + id itemJSON = [self jsonFromAPIObject:item + expectedClass:expectedClass + isCacheable:NULL]; + [resultArray addObject:itemJSON]; + } + result = resultArray; + } else if ([obj isKindOfClass:[GTLDateTime class]]) { + // DateTimes live in JSON as strings, so convert. + GTLDateTime *dateTime = obj; + result = [dateTime stringValue]; + } else { + checkExpected = NO; + GTL_DEBUG_LOG(@"GTLRuntimeCommon: unsupported class '%s' in jsonFromAPIObject", + class_getName([obj class])); + } + + if (checkExpected) { + // If the default was any object, then clear it to skip validation checks. + if ([expectedClass isEqual:[NSObject class]] || + [obj isKindOfClass:[NSNull class]]) { + expectedClass = nil; + } + if (expectedClass && ![obj isKindOfClass:expectedClass]) { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: jsonFromAPIObject expected class '%s' instead got '%s'", + class_getName(expectedClass), class_getName([obj class])); + } + } + + if (isCacheable) { + *isCacheable = canBeCached; + } + return result; +} + +#pragma mark JSON/Object Utilities + +static NSMutableDictionary *gDispatchCache = nil; + +static CFStringRef SelectorKeyCopyDescriptionCallBack(const void *key) { + // Make a CFString from the key + NSString *name = NSStringFromSelector((SEL) key); + CFStringRef str = CFStringCreateCopy(kCFAllocatorDefault, (CFStringRef) name); + return str; +} + +// Save the dispatch details for the specified class and selector ++ (void)setStoredDispatchForClass:(Class)dispatchClass + selector:(SEL)sel + returnClass:(Class)returnClass + containedClass:(Class)containedClass + jsonKey:(NSString *)jsonKey { + // cache structure: + // class -> + // selector -> + // returnClass + // containedClass + // jsonKey + @synchronized([GTLRuntimeCommon class]) { + if (gDispatchCache == nil) { + gDispatchCache = [GTLUtilities newStaticDictionary]; + } + + CFMutableDictionaryRef classDict = + (CFMutableDictionaryRef) [gDispatchCache objectForKey:dispatchClass]; + if (classDict == nil) { + // We create a CFDictionary since the keys are raw selectors rather than + // NSStrings + const CFDictionaryKeyCallBacks keyCallBacks = { + .version = 0, + .retain = NULL, + .release = NULL, + .copyDescription = SelectorKeyCopyDescriptionCallBack, + .equal = NULL, // defaults to pointer comparison + .hash = NULL // defaults to the pointer value + }; + const CFIndex capacity = 0; // no limit + classDict = CFDictionaryCreateMutable(kCFAllocatorDefault, capacity, + &keyCallBacks, + &kCFTypeDictionaryValueCallBacks); + [gDispatchCache setObject:(id)classDict + forKey:(id)dispatchClass]; + CFRelease(classDict); + } + + NSDictionary *selDict = (NSDictionary *)CFDictionaryGetValue(classDict, sel); + if (selDict == nil) { + selDict = [NSDictionary dictionaryWithObjectsAndKeys: + jsonKey, kJSONKey, + returnClass, kReturnClassKey, // can be nil (primitive types) + containedClass, kContainedClassKey, // may be nil + nil]; + CFDictionarySetValue(classDict, sel, selDict); + } else { + // we already have a dictionary for this selector on this class, which is + // surprising + GTL_DEBUG_LOG(@"Storing duplicate dispatch for %@ selector %@", + dispatchClass, NSStringFromSelector(sel)); + } + } +} + ++ (BOOL)getStoredDispatchForClass:(Class)dispatchClass + selector:(SEL)sel + returnClass:(Class *)outReturnClass + containedClass:(Class *)outContainedClass + jsonKey:(NSString **)outJsonKey { + @synchronized([GTLRuntimeCommon class]) { + // walk from this class up the hierarchy to the ancestor class + Class topClass = class_getSuperclass([dispatchClass ancestorClass]); + for (Class currClass = dispatchClass; + currClass != topClass; + currClass = class_getSuperclass(currClass)) { + + CFMutableDictionaryRef classDict = + (CFMutableDictionaryRef) [gDispatchCache objectForKey:currClass]; + if (classDict) { + NSMutableDictionary *selDict = + (NSMutableDictionary *) CFDictionaryGetValue(classDict, sel); + if (selDict) { + if (outReturnClass) { + *outReturnClass = [selDict objectForKey:kReturnClassKey]; + } + if (outContainedClass) { + *outContainedClass = [selDict objectForKey:kContainedClassKey]; + } + if (outJsonKey) { + *outJsonKey = [selDict objectForKey:kJSONKey]; + } + return YES; + } + } + } + } + GTL_DEBUG_LOG(@"Failed to find stored dispatch info for %@ %s", + dispatchClass, sel_getName(sel)); + return NO; +} + +#pragma mark IMPs - getters and setters for specific object types + +#if !__LP64__ + +// NSInteger on 32bit +static NSInteger DynamicInteger32Getter(id self, SEL sel) { + // get an NSInteger (NSNumber) from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [self JSONValueForKey:jsonKey]; + num = GTL_EnsureNSNumber(num); + NSInteger result = [num integerValue]; + return result; + } + return 0; +} + +static void DynamicInteger32Setter(id self, SEL sel, NSInteger val) { + // save an NSInteger (NSNumber) into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [NSNumber numberWithInteger:val]; + [self setJSONValue:num forKey:jsonKey]; + } +} + +// NSUInteger on 32bit +static NSUInteger DynamicUInteger32Getter(id self, SEL sel) { + // get an NSUInteger (NSNumber) from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [self JSONValueForKey:jsonKey]; + num = GTL_EnsureNSNumber(num); + NSUInteger result = [num unsignedIntegerValue]; + return result; + } + return 0; +} + +static void DynamicUInteger32Setter(id self, SEL sel, NSUInteger val) { + // save an NSUInteger (NSNumber) into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [NSNumber numberWithUnsignedInteger:val]; + [self setJSONValue:num forKey:jsonKey]; + } +} + +#endif // !__LP64__ + +// NSInteger on 64bit, long long on 32bit and 64bit +static long long DynamicLongLongGetter(id self, SEL sel) { + // get a long long (NSNumber) from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [self JSONValueForKey:jsonKey]; + num = GTL_EnsureNSNumber(num); + long long result = [num longLongValue]; + return result; + } + return 0; +} + +static void DynamicLongLongSetter(id self, SEL sel, long long val) { + // save a long long (NSNumber) into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [NSNumber numberWithLongLong:val]; + [self setJSONValue:num forKey:jsonKey]; + } +} + +// NSUInteger on 64bit, unsiged long long on 32bit and 64bit +static unsigned long long DynamicULongLongGetter(id self, SEL sel) { + // get an unsigned long long (NSNumber) from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [self JSONValueForKey:jsonKey]; + num = GTL_EnsureNSNumber(num); + unsigned long long result = [num unsignedLongLongValue]; + return result; + } + return 0; +} + +static void DynamicULongLongSetter(id self, SEL sel, unsigned long long val) { + // save an unsigned long long (NSNumber) into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [NSNumber numberWithUnsignedLongLong:val]; + [self setJSONValue:num forKey:jsonKey]; + } +} + +// float +static float DynamicFloatGetter(id self, SEL sel) { + // get a float (NSNumber) from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [self JSONValueForKey:jsonKey]; + num = GTL_EnsureNSNumber(num); + float result = [num floatValue]; + return result; + } + return 0.0f; +} + +static void DynamicFloatSetter(id self, SEL sel, float val) { + // save a float (NSNumber) into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [NSNumber numberWithFloat:val]; + [self setJSONValue:num forKey:jsonKey]; + } +} + +// double +static double DynamicDoubleGetter(id self, SEL sel) { + // get a double (NSNumber) from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [self JSONValueForKey:jsonKey]; + num = GTL_EnsureNSNumber(num); + double result = [num doubleValue]; + return result; + } + return 0.0; +} + +static void DynamicDoubleSetter(id self, SEL sel, double val) { + // save a double (NSNumber) into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [NSNumber numberWithDouble:val]; + [self setJSONValue:num forKey:jsonKey]; + } +} + +// BOOL +static BOOL DynamicBooleanGetter(id self, SEL sel) { + // get a BOOL (NSNumber) from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [self JSONValueForKey:jsonKey]; + BOOL flag = [num boolValue]; + return flag; + } + return NO; +} + +static void DynamicBooleanSetter(id self, SEL sel, BOOL val) { + // save a BOOL (NSNumber) into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSNumber *num = [NSNumber numberWithBool:val]; + [self setJSONValue:num forKey:jsonKey]; + } +} + +// NSString +static NSString *DynamicStringGetter(id self, SEL sel) { + // get an NSString from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + + NSString *str = [self JSONValueForKey:jsonKey]; + return str; + } + return nil; +} + +static void DynamicStringSetter(id self, SEL sel, + NSString *str) { + // save an NSString into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + NSString *copiedStr = [str copy]; + [self setJSONValue:copiedStr forKey:jsonKey]; + [copiedStr release]; + } +} + +// GTLDateTime +static GTLDateTime *DynamicDateTimeGetter(id self, SEL sel) { + // get a GTLDateTime from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + + // Return the cached object before creating on demand. + GTLDateTime *cachedDateTime = [self cacheChildForKey:jsonKey]; + if (cachedDateTime != nil) { + return cachedDateTime; + } + NSString *str = [self JSONValueForKey:jsonKey]; + id cacheValue, resultValue; + if (![str isKindOfClass:[NSNull class]]) { + GTLDateTime *dateTime = [GTLDateTime dateTimeWithRFC3339String:str]; + + cacheValue = dateTime; + resultValue = dateTime; + } else { + cacheValue = nil; + resultValue = [NSNull null]; + } + [self setCacheChild:cacheValue forKey:jsonKey]; + return resultValue; + } + return nil; +} + +static void DynamicDateTimeSetter(id self, SEL sel, + GTLDateTime *dateTime) { + // save an GTLDateTime into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + id cacheValue, jsonValue; + if (![dateTime isKindOfClass:[NSNull class]]) { + jsonValue = [dateTime stringValue]; + cacheValue = dateTime; + } else { + jsonValue = [NSNull null]; + cacheValue = nil; + } + + [self setJSONValue:jsonValue forKey:jsonKey]; + [self setCacheChild:cacheValue forKey:jsonKey]; + } +} + +// NSNumber +static NSNumber *DynamicNumberGetter(id self, SEL sel) { + // get an NSNumber from the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + + NSNumber *num = [self JSONValueForKey:jsonKey]; + num = GTL_EnsureNSNumber(num); + return num; + } + return nil; +} + +static void DynamicNumberSetter(id self, SEL sel, + NSNumber *num) { + // save an NSNumber into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + [self setJSONValue:num forKey:jsonKey]; + } +} + +// GTLObject +static GTLObject *DynamicObjectGetter(id self, SEL sel) { + // get a GTLObject from the JSON dictionary + NSString *jsonKey = nil; + Class returnClass = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:&returnClass + containedClass:NULL + jsonKey:&jsonKey]) { + + // Return the cached object before creating on demand. + GTLObject *cachedObj = [self cacheChildForKey:jsonKey]; + if (cachedObj != nil) { + return cachedObj; + } + NSMutableDictionary *dict = [self JSONValueForKey:jsonKey]; + if ([dict isKindOfClass:[NSMutableDictionary class]]) { + // get the class of the object being returned, and instantiate it + if (returnClass == Nil) { + returnClass = [GTLObject class]; + } + + NSDictionary *surrogates = self.surrogates; + GTLObject *obj = [GTLObject objectForJSON:dict + defaultClass:returnClass + surrogates:surrogates + batchClassMap:nil]; + [self setCacheChild:obj forKey:jsonKey]; + return obj; + } else if ([dict isKindOfClass:[NSNull class]]) { + [self setCacheChild:nil forKey:jsonKey]; + return (id) [NSNull null]; + } else if (dict != nil) { + // unexpected; probably got a string -- let the caller figure it out + GTL_DEBUG_LOG(@"GTLObject: unexpected JSON: %@.%@ should be a dictionary, actually is a %@:\n%@", + NSStringFromClass(selfClass), NSStringFromSelector(sel), + NSStringFromClass([dict class]), dict); + return (GTLObject *)dict; + } + } + return nil; +} + +static void DynamicObjectSetter(id self, SEL sel, + GTLObject *obj) { + // save a GTLObject into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + id cacheValue, jsonValue; + if (![obj isKindOfClass:[NSNull class]]) { + NSMutableDictionary *dict = [obj JSON]; + if (dict == nil && obj != nil) { + // adding an empty object; it should have a JSON dictionary so it can + // hold future assignments + obj.JSON = [NSMutableDictionary dictionary]; + jsonValue = obj.JSON; + } else { + jsonValue = dict; + } + cacheValue = obj; + } else { + jsonValue = [NSNull null]; + cacheValue = nil; + } + [self setJSONValue:jsonValue forKey:jsonKey]; + [self setCacheChild:cacheValue forKey:jsonKey]; + } +} + +// get an NSArray of GTLObjects, NSStrings, or NSNumbers from the +// JSON dictionary for this object +static NSMutableArray *DynamicArrayGetter(id self, SEL sel) { + NSString *jsonKey = nil; + Class containedClass = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:&containedClass + jsonKey:&jsonKey]) { + + // Return the cached array before creating on demand. + NSMutableArray *cachedArray = [self cacheChildForKey:jsonKey]; + if (cachedArray != nil) { + return cachedArray; + } + NSMutableArray *result = nil; + NSArray *array = [self JSONValueForKey:jsonKey]; + if (array != nil) { + if ([array isKindOfClass:[NSArray class]]) { + NSDictionary *surrogates = self.surrogates; + result = [GTLRuntimeCommon objectFromJSON:array + defaultClass:containedClass + 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(selfClass), NSStringFromSelector(sel), + NSStringFromClass([array class]), array); + } +#endif + result = (NSMutableArray *)array; + } + } + + [self setCacheChild:result forKey:jsonKey]; + return result; + } + return nil; +} + +static void DynamicArraySetter(id self, SEL sel, + NSMutableArray *array) { + // save an array of GTLObjects objects into the JSON dictionary + NSString *jsonKey = nil; + Class selfClass = [self class]; + Class containedClass = nil; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:&containedClass + jsonKey:&jsonKey]) { + id json = [GTLRuntimeCommon jsonFromAPIObject:array + expectedClass:containedClass + isCacheable:NULL]; + [self setJSONValue:json forKey:jsonKey]; + [self setCacheChild:array forKey:jsonKey]; + } +} + +// type 'id' +static id DynamicNSObjectGetter(id self, SEL sel) { + NSString *jsonKey = nil; + Class returnClass = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:&returnClass + containedClass:NULL + jsonKey:&jsonKey]) { + + // Return the cached object before creating on demand. + id cachedObj = [self cacheChildForKey:jsonKey]; + if (cachedObj != nil) { + return cachedObj; + } + + id jsonObj = [self JSONValueForKey:jsonKey]; + if (jsonObj != nil) { + BOOL shouldCache = NO; + NSDictionary *surrogates = self.surrogates; + id result = [GTLRuntimeCommon objectFromJSON:jsonObj + defaultClass:nil + surrogates:surrogates + isCacheable:&shouldCache]; + + [self setCacheChild:(shouldCache ? result : nil) + forKey:jsonKey]; + return result; + } + } + return nil; +} + +static void DynamicNSObjectSetter(id self, SEL sel, id obj) { + NSString *jsonKey = nil; + Class selfClass = [self class]; + if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass + selector:sel + returnClass:NULL + containedClass:NULL + jsonKey:&jsonKey]) { + BOOL shouldCache = NO; + id json = [GTLRuntimeCommon jsonFromAPIObject:obj + expectedClass:Nil + isCacheable:&shouldCache]; + [self setJSONValue:json forKey:jsonKey]; + [self setCacheChild:(shouldCache ? obj : nil) + forKey:jsonKey]; + } +} + +#pragma mark Runtime lookup support + +static objc_property_t PropertyForSel(Class startClass, + SEL sel, BOOL isSetter, + Class *outFoundClass) { + const char *baseName = sel_getName(sel); + size_t baseNameLen = strlen(baseName); + if (isSetter) { + baseName += 3; // skip "set" + baseNameLen -= 4; // subtract "set" and the final colon + } + + // walk from this class up the hierarchy to the ancestor class + Class topClass = class_getSuperclass([startClass ancestorClass]); + for (Class currClass = startClass; + currClass != topClass; + currClass = class_getSuperclass(currClass)) { + // step through this class's properties + objc_property_t foundProp = NULL; + 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); + size_t propNameLen = strlen(propName); + + // search for an exact-name match (a getter), but case-insensitive on the + // first character (in case baseName comes from a setter) + if (baseNameLen == propNameLen + && strncasecmp(baseName, propName, 1) == 0 + && (baseNameLen <= 1 + || strncmp(baseName + 1, propName + 1, baseNameLen - 1) == 0)) { + // return the actual property name + foundProp = *prop; + + // if requested, return the class containing the property + if (outFoundClass) *outFoundClass = currClass; + break; + } + } + free(properties); + } + if (foundProp) return foundProp; + } + + // not found; this occasionally happens when the system looks for a method + // like "getFoo" or "descriptionWithLocale:indent:" + return NULL; +} + +typedef struct { + const char *attributePrefix; + + const char *setterEncoding; + IMP setterFunction; + const char *getterEncoding; + IMP getterFunction; + + // These are the "fixed" return classes, but some properties will require + // looking up the return class instead (because it is a subclass of + // GTLObject). + const char *returnClassName; + Class returnClass; + BOOL extractReturnClass; + +} GTLDynamicImpInfo; + +static const GTLDynamicImpInfo *DynamicImpInfoForProperty(objc_property_t prop, + Class *outReturnClass) { + + if (outReturnClass) *outReturnClass = nil; + + // dynamic method resolution: + // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html + // + // property runtimes: + // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html + + // Get and parse the property attributes, which look something like + // T@"NSString",&,D,P + // Ti,D -- NSInteger on 32bit + // Tq,D -- NSInteger on 64bit, long long on 32bit & 64bit + // Tc,D -- BOOL comes as char + // T@"NSString",D + // T@"GTLLink",D + // T@"NSArray",D + + + static GTLDynamicImpInfo kImplInfo[] = { +#if !__LP64__ + { // NSInteger on 32bit + "Ti", + "v@:i", (IMP)DynamicInteger32Setter, + "i@:", (IMP)DynamicInteger32Getter, + nil, nil, + NO + }, + { // NSUInteger on 32bit + "TI", + "v@:I", (IMP)DynamicUInteger32Setter, + "I@:", (IMP)DynamicUInteger32Getter, + nil, nil, + NO + }, +#endif + { // NSInteger on 64bit, long long on 32bit and 64bit. + "Tq", + "v@:q", (IMP)DynamicLongLongSetter, + "q@:", (IMP)DynamicLongLongGetter, + nil, nil, + NO + }, + { // NSUInteger on 64bit, long long on 32bit and 64bit. + "TQ", + "v@:Q", (IMP)DynamicULongLongSetter, + "Q@:", (IMP)DynamicULongLongGetter, + nil, nil, + NO + }, + { // float + "Tf", + "v@:f", (IMP)DynamicFloatSetter, + "f@:", (IMP)DynamicFloatGetter, + nil, nil, + NO + }, + { // double + "Td", + "v@:d", (IMP)DynamicDoubleSetter, + "d@:", (IMP)DynamicDoubleGetter, + nil, nil, + NO + }, + { // BOOL + "Tc", + "v@:c", (IMP)DynamicBooleanSetter, + "c@:", (IMP)DynamicBooleanGetter, + nil, nil, + NO + }, + { // NSString + "T@\"NSString\"", + "v@:@", (IMP)DynamicStringSetter, + "@@:", (IMP)DynamicStringGetter, + "NSString", nil, + NO + }, + { // NSNumber + "T@\"NSNumber\"", + "v@:@", (IMP)DynamicNumberSetter, + "@@:", (IMP)DynamicNumberGetter, + "NSNumber", nil, + NO + }, + { // GTLDateTime +#if !defined(GTL_TARGET_NAMESPACE) + "T@\"GTLDateTime\"", + "v@:@", (IMP)DynamicDateTimeSetter, + "@@:", (IMP)DynamicDateTimeGetter, + "GTLDateTime", nil, + NO +#else + "T@\"" GTL_TARGET_NAMESPACE_STRING "_" "GTLDateTime\"", + "v@:@", (IMP)DynamicDateTimeSetter, + "@@:", (IMP)DynamicDateTimeGetter, + GTL_TARGET_NAMESPACE_STRING "_" "GTLDateTime", nil, + NO +#endif + }, + { // NSArray with type + "T@\"NSArray\"", + "v@:@", (IMP)DynamicArraySetter, + "@@:", (IMP)DynamicArrayGetter, + "NSArray", nil, + NO + }, + { // id (any of the objects above) + "T@,", + "v@:@", (IMP)DynamicNSObjectSetter, + "@@:", (IMP)DynamicNSObjectGetter, + "NSObject", nil, + NO + }, + { // GTLObject - Last, cause it's a special case and prefix is general + "T@\"", + "v@:@", (IMP)DynamicObjectSetter, + "@@:", (IMP)DynamicObjectGetter, + nil, nil, + YES + }, + }; + + static BOOL hasLookedUpClasses = NO; + if (!hasLookedUpClasses) { + // Unfortunately, you can't put [NSString class] into the static structure, + // so this lookup has to be done at runtime. + hasLookedUpClasses = YES; + for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) { + if (kImplInfo[idx].returnClassName) { + kImplInfo[idx].returnClass = objc_getClass(kImplInfo[idx].returnClassName); + NSCAssert1(kImplInfo[idx].returnClass != nil, + @"GTLRuntimeCommon: class lookup failed: %s", kImplInfo[idx].returnClassName); + } + } + } + + const char *attr = property_getAttributes(prop); + + const char *dynamicMarker = strstr(attr, ",D"); + if (!dynamicMarker || + (dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: property %s isn't dynamic, attributes %s", + property_getName(prop), attr ? attr : "(nil)"); + return NULL; + } + + const GTLDynamicImpInfo *result = NULL; + + // Cycle over the list + + for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) { + const char *attributePrefix = kImplInfo[idx].attributePrefix; + if (strncmp(attr, attributePrefix, strlen(attributePrefix)) == 0) { + result = &kImplInfo[idx]; + if (outReturnClass) *outReturnClass = result->returnClass; + break; + } + } + + if (result == NULL) { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: unexpected attributes %s for property %s", + attr ? attr : "(nil)", property_getName(prop)); + return NULL; + } + + if (result->extractReturnClass && outReturnClass) { + + // add a null at the next quotation mark + char *attrCopy = strdup(attr); + char *classNameStart = attrCopy + 3; + char *classNameEnd = strstr(classNameStart, "\""); + if (classNameEnd) { + *classNameEnd = '\0'; + + // Lookup the return class + *outReturnClass = objc_getClass(classNameStart); + if (*outReturnClass == nil) { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: did not find class with name \"%s\" " + "for property \"%s\" with attributes \"%s\"", + classNameStart, property_getName(prop), attr); + } + } else { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: Failed to find end of class name for " + "property \"%s\" with attributes \"%s\"", + property_getName(prop), attr); + } + free(attrCopy); + } + + return result; +} + +#pragma mark Runtime - wiring point + ++ (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class)onClass { + // dynamic method resolution: + // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html + // + // property runtimes: + // http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html + + const char *selName = sel_getName(sel); + size_t selNameLen = strlen(selName); + char lastChar = selName[selNameLen - 1]; + BOOL isSetter = (lastChar == ':'); + + // look for a declared property matching this selector name exactly + Class foundClass = nil; + + objc_property_t prop = PropertyForSel(onClass, sel, isSetter, &foundClass); + if (prop != NULL && foundClass != nil) { + + Class returnClass = nil; + const GTLDynamicImpInfo *implInfo = DynamicImpInfoForProperty(prop, + &returnClass); + if (implInfo == NULL) { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: unexpected return type class %s for " + "property \"%s\" of class \"%s\"", + returnClass ? class_getName(returnClass) : "", + property_getName(prop), + class_getName(onClass)); + } + + if (implInfo != NULL) { + IMP imp = ( isSetter ? implInfo->setterFunction : implInfo->getterFunction ); + const char *encoding = ( isSetter ? implInfo->setterEncoding : implInfo->getterEncoding ); + + class_addMethod(foundClass, sel, imp, encoding); + + const char *propName = property_getName(prop); + NSString *propStr = [NSString stringWithUTF8String:propName]; + + // replace the property name with the proper JSON key if it's + // special-cased with a map in the found class; otherwise, the property + // name is the JSON key + NSDictionary *keyMap = + [[foundClass ancestorClass] propertyToJSONKeyMapForClass:foundClass]; + NSString *jsonKey = [keyMap objectForKey:propStr]; + if (jsonKey == nil) { + jsonKey = propStr; + } + + Class containedClass = nil; + + // For arrays we need to look up what the contained class is. + if (imp == (IMP)DynamicArraySetter || imp == (IMP)DynamicArrayGetter) { + NSDictionary *classMap = + [[foundClass ancestorClass] arrayPropertyToClassMapForClass:foundClass]; + containedClass = [classMap objectForKey:jsonKey]; + if (containedClass == Nil) { + GTL_DEBUG_LOG(@"GTLRuntimeCommon: expected array item class for " + "property \"%s\" of class \"%s\"", + property_getName(prop), class_getName(foundClass)); + } + } + + // save the dispatch info to the cache + [GTLRuntimeCommon setStoredDispatchForClass:foundClass + selector:sel + returnClass:returnClass + containedClass:containedClass + jsonKey:jsonKey]; + return YES; + } + } + + return NO; +} + +@end diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLService.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLService.h new file mode 100644 index 00000000..f7929e2f --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLService.h @@ -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 + +#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)query + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector; + +#if NS_BLOCKS_AVAILABLE +- (GTLServiceTicket *)executeQuery:(id)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 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 executingQuery_; + id 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 executingQuery; // Query currently being fetched by this ticket +@property (nonatomic, retain) id 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 + diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLService.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLService.m new file mode 100644 index 00000000..2e405324 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLService.m @@ -0,0 +1,2387 @@ +/* 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.m +// + +#import +#if TARGET_OS_MAC +#include +#endif + +#if TARGET_OS_IPHONE +#import +#endif + +#define GTLSERVICE_DEFINE_GLOBALS 1 +#import "GTLService.h" + +static NSString *const kUserDataPropertyKey = @"_userData"; + +static NSString* const kFetcherDelegateKey = @"_delegate"; +static NSString* const kFetcherObjectClassKey = @"_objectClass"; +static NSString* const kFetcherFinishedSelectorKey = @"_finishedSelector"; +static NSString* const kFetcherCompletionHandlerKey = @"_completionHandler"; +static NSString* const kFetcherTicketKey = @"_ticket"; +static NSString* const kFetcherFetchErrorKey = @"_fetchError"; +static NSString* const kFetcherParsingNotificationKey = @"_parseNotification"; +static NSString* const kFetcherParsedObjectKey = @"_parsedObject"; +static NSString* const kFetcherBatchClassMapKey = @"_batchClassMap"; +static NSString* const kFetcherCallbackThreadKey = @"_callbackThread"; +static NSString* const kFetcherCallbackRunLoopModesKey = @"_runLoopModes"; + +static const NSUInteger kMaxNumberOfNextPagesFetched = 25; + +// we'll enforce 50K chunks minimum just to avoid the server getting hit +// with too many small upload chunks +static const NSUInteger kMinimumUploadChunkSize = 50000; +static const NSUInteger kStandardUploadChunkSize = NSUIntegerMax; + +// Helper to get the ETag if it is defined on an object. +static NSString *ETagIfPresent(GTLObject *obj) { + NSString *result = [obj.JSON objectForKey:@"etag"]; + return result; +} + +@interface GTLServiceTicket () +@property (retain) NSOperation *parseOperation; +@property (assign) BOOL isREST; +@end + +// category to provide opaque access to tickets stored in fetcher properties +@implementation GTMHTTPFetcher (GTLServiceTicketAdditions) +- (id)ticket { + return [self propertyForKey:kFetcherTicketKey]; +} +@end + +// If GTMHTTPUploadFetcher is available, it can be used for chunked uploads +// +// We locally declare some methods of GTMHTTPUploadFetcher so we +// do not need to import the header, as some projects may not have it available +@interface GTMHTTPUploadFetcher : GTMHTTPFetcher ++ (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request + uploadData:(NSData *)data + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(NSUInteger)chunkSize + fetcherService:(GTMHTTPFetcherService *)fetcherService; ++ (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request + uploadFileHandle:(NSFileHandle *)uploadFileHandle + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(NSUInteger)chunkSize + fetcherService:(GTMHTTPFetcherService *)fetcherService; ++ (GTMHTTPUploadFetcher *)uploadFetcherWithLocation:(NSURL *)location + uploadFileHandle:(NSFileHandle *)fileHandle + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(NSUInteger)chunkSize + fetcherService:(GTMHTTPFetcherService *)fetcherService; +- (void)pauseFetching; +- (void)resumeFetching; +- (BOOL)isPaused; +@end + + +@interface GTLService () +- (void)prepareToParseObjectForFetcher:(GTMHTTPFetcher *)fetcher; +- (void)handleParsedObjectForFetcher:(GTMHTTPFetcher *)fetcher; +- (BOOL)fetchNextPageWithQuery:(GTLQuery *)query + delegate:(id)delegate + didFinishedSelector:(SEL)finishedSelector + completionHandler:(GTLServiceCompletionHandler)completionHandler + ticket:(GTLServiceTicket *)ticket; +- (id )nextPageQueryForQuery:(GTLQuery *)query + result:(GTLObject *)object + ticket:(GTLServiceTicket *)ticket; +- (GTLObject *)mergedNewResultObject:(GTLObject *)newResult + oldResultObject:(GTLObject *)oldResult + forQuery:(GTLQuery *)query; +- (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request + fetcherService:(GTMHTTPFetcherService *)fetcherService + params:(GTLUploadParameters *)uploadParams; ++ (void)invokeCallback:(SEL)callbackSel + target:(id)target + ticket:(id)ticket + object:(id)object + error:(id)error; +- (BOOL)invokeRetrySelector:(SEL)retrySelector + delegate:(id)delegate + ticket:(GTLServiceTicket *)ticket + willRetry:(BOOL)willRetry + error:(NSError *)error; +- (BOOL)objectFetcher:(GTMHTTPFetcher *)fetcher + willRetry:(BOOL)willRetry + forError:(NSError *)error; +- (void)objectFetcher:(GTMHTTPFetcher *)fetcher + finishedWithData:(NSData *)data + error:(NSError *)error; +- (void)parseObjectFromDataOfFetcher:(GTMHTTPFetcher *)fetcher; +@end + +@interface GTLObject (StandardProperties) +@property (retain) NSString *ETag; +@property (retain) NSString *nextPageToken; +@property (retain) NSNumber *nextStartIndex; +@end + +@implementation GTLService + +@synthesize userAgentAddition = userAgentAddition_, + fetcherService = fetcherService_, + parseQueue = parseQueue_, + shouldFetchNextPages = shouldFetchNextPages_, + surrogates = surrogates_, + uploadProgressSelector = uploadProgressSelector_, + retryEnabled = isRetryEnabled_, + retrySelector = retrySelector_, + maxRetryInterval = maxRetryInterval_, + APIKey = apiKey_, + isRESTDataWrapperRequired = isRESTDataWrapperRequired_, + urlQueryParameters = urlQueryParameters_, + additionalHTTPHeaders = additionalHTTPHeaders_, + apiVersion = apiVersion_, + rpcURL = rpcURL_, + rpcUploadURL = rpcUploadURL_; + +#if NS_BLOCKS_AVAILABLE +@synthesize retryBlock = retryBlock_, + uploadProgressBlock = uploadProgressBlock_; +#endif + ++ (Class)ticketClass { + return [GTLServiceTicket class]; +} + +- (id)init { + self = [super init]; + if (self) { + +#if GTL_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5) + // For 10.6 and up, always use an operation queue + parseQueue_ = [[NSOperationQueue alloc] init]; +#elif !GTL_SKIP_PARSE_THREADING + // Avoid NSOperationQueue prior to 10.5.6, per + // http://www.mikeash.com/?page=pyblog/use-nsoperationqueue.html + SInt32 bcdSystemVersion = 0; + (void) Gestalt(gestaltSystemVersion, &bcdSystemVersion); + + if (bcdSystemVersion >= 0x1057) { + parseQueue_ = [[NSOperationQueue alloc] init]; + } +#else + // parseQueue_ defaults to nil, so parsing will be done immediately + // on the current thread +#endif + + fetcherService_ = [[GTMHTTPFetcherService alloc] init]; + + NSUInteger chunkSize = [[self class] defaultServiceUploadChunkSize]; + self.serviceUploadChunkSize = chunkSize; + } + return self; +} + +- (void)dealloc { + [parseQueue_ release]; + [userAgent_ release]; + [fetcherService_ release]; + [userAgentAddition_ release]; + [serviceProperties_ release]; + [surrogates_ release]; +#if NS_BLOCKS_AVAILABLE + [uploadProgressBlock_ release]; + [retryBlock_ release]; +#endif + [apiKey_ release]; + [apiVersion_ release]; + [rpcURL_ release]; + [rpcUploadURL_ release]; + [urlQueryParameters_ release]; + [additionalHTTPHeaders_ release]; + + [super dealloc]; +} + +- (NSString *)requestUserAgent { + NSString *userAgent = self.userAgent; + if ([userAgent length] == 0) { + // the service instance is missing an explicit user-agent; use the bundle ID + // or process name + NSBundle *owningBundle = [NSBundle bundleForClass:[self class]]; + if (owningBundle == nil + || [[owningBundle bundleIdentifier] isEqual:@"com.google.GTLFramework"]) { + + owningBundle = [NSBundle mainBundle]; + } + + userAgent = GTMApplicationIdentifier(owningBundle); + } + + NSString *requestUserAgent = userAgent; + + // if the user agent already specifies the library version, we'll + // use it verbatim in the request + NSString *libraryString = @"google-api-objc-client"; + NSRange libRange = [userAgent rangeOfString:libraryString + options:NSCaseInsensitiveSearch]; + if (libRange.location == NSNotFound) { + // the user agent doesn't specify the client library, so append that + // information, and the system version + NSString *libVersionString = GTLFrameworkVersionString(); + + NSString *systemString = GTMSystemVersionString(); + + // We don't clean this with GTMCleanedUserAgentString so spaces are + // preserved + NSString *userAgentAddition = self.userAgentAddition; + NSString *customString = userAgentAddition ? + [@" " stringByAppendingString:userAgentAddition] : @""; + + // Google servers look for gzip in the user agent before sending gzip- + // encoded responses. See Service.java + requestUserAgent = [NSString stringWithFormat:@"%@ %@/%@ %@%@ (gzip)", + userAgent, libraryString, libVersionString, systemString, customString]; + } + return requestUserAgent; +} + +- (NSMutableURLRequest *)requestForURL:(NSURL *)url + ETag:(NSString *)etag + httpMethod:(NSString *)httpMethod + ticket:(GTLServiceTicket *)ticket { + + // subclasses may add headers to this + NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:url + cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:60] autorelease]; + NSString *requestUserAgent = self.requestUserAgent; + [request setValue:requestUserAgent forHTTPHeaderField:@"User-Agent"]; + + if ([httpMethod length] > 0) { + [request setHTTPMethod:httpMethod]; + } + + if ([etag length] > 0) { + + // it's rather unexpected for an etagged object to be provided for a GET, + // but we'll check for an etag anyway, similar to HttpGDataRequest.java, + // and if present use it to request only an unchanged resource + + BOOL isDoingHTTPGet = (httpMethod == nil + || [httpMethod caseInsensitiveCompare:@"GET"] == NSOrderedSame); + + if (isDoingHTTPGet) { + + // set the etag header, even if weak, indicating we don't want + // another copy of the resource if it's the same as the object + [request setValue:etag forHTTPHeaderField:@"If-None-Match"]; + + } else { + + // if we're doing PUT or DELETE, set the etag header indicating + // we only want to update the resource if our copy matches the current + // one (unless the etag is weak and so shouldn't be a constraint at all) + BOOL isWeakETag = [etag hasPrefix:@"W/"]; + + BOOL isModifying = + [httpMethod caseInsensitiveCompare:@"PUT"] == NSOrderedSame + || [httpMethod caseInsensitiveCompare:@"DELETE"] == NSOrderedSame + || [httpMethod caseInsensitiveCompare:@"PATCH"] == NSOrderedSame; + + if (isModifying && !isWeakETag) { + [request setValue:etag forHTTPHeaderField:@"If-Match"]; + } + } + } + + return request; +} + +- (NSMutableURLRequest *)requestForURL:(NSURL *)url + ETag:(NSString *)etag + httpMethod:(NSString *)httpMethod { + // this public entry point authenticates from the service object but + // not from the auth token in the ticket + return [self requestForURL:url ETag:etag httpMethod:httpMethod ticket:nil]; +} + +// objectRequestForURL returns an NSMutableURLRequest for a GTLObject +// +// 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 { + if (object) { + // if the object being sent has an etag, add it to the request header to + // avoid retrieving a duplicate or to avoid writing over an updated + // version of the resource on the server + // + // Typically, delete requests will provide an explicit ETag parameter, and + // other requests will have the ETag carried inside the object being updated + if (etag == nil) { + SEL selEtag = @selector(ETag); + if ([object respondsToSelector:selEtag]) { + etag = [object performSelector:selEtag]; + } + } + } + + NSMutableURLRequest *request = [self requestForURL:url + ETag:etag + httpMethod:httpMethod + ticket:ticket]; + NSString *acceptValue; + NSString *contentTypeValue; + if (isREST) { + acceptValue = @"application/json"; + contentTypeValue = @"application/json; charset=utf-8"; + } else { + acceptValue = @"application/json-rpc"; + contentTypeValue = @"application/json-rpc; charset=utf-8"; + } + [request setValue:acceptValue forHTTPHeaderField:@"Accept"]; + [request setValue:contentTypeValue forHTTPHeaderField:@"Content-Type"]; + + [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"]; + + // Add the additional http headers from the service, and then from the query + NSDictionary *headers = self.additionalHTTPHeaders; + for (NSString *key in headers) { + NSString *value = [headers valueForKey:key]; + [request setValue:value forHTTPHeaderField:key]; + } + + headers = additionalHeaders; + for (NSString *key in headers) { + NSString *value = [headers valueForKey:key]; + [request setValue:value forHTTPHeaderField:key]; + } + + return request; +} + +#pragma mark - + +// common fetch starting method + +- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL + objectClass:(Class)objectClass + bodyObject:(GTLObject *)bodyObject + dataToPost:(NSData *)dataToPost + ETag:(NSString *)etag + httpMethod:(NSString *)httpMethod + mayAuthorize:(BOOL)mayAuthorize + isREST:(BOOL)isREST + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector + completionHandler:(id)completionHandler // GTLServiceCompletionHandler + executingQuery:(id)query + ticket:(GTLServiceTicket *)ticket { + + GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector, @encode(GTLServiceTicket *), @encode(GTLObject *), @encode(NSError *), 0); + + // The completionHandler argument is declared as an id, not as a block + // pointer, so this can be built with the 10.6 SDK and still run on 10.5. + // If the argument were declared as a block pointer, the invocation for + // fetchObjectWithURL: created in GTLService would cause an exception + // since 10.5's NSInvocation cannot deal with encoding of block pointers. + + GTL_DEBUG_ASSERT(targetURL != nil, @"no url?"); + if (targetURL == nil) return nil; + + // we need to create a ticket unless one was created earlier (like during + // authentication) + if (!ticket) { + ticket = [[[self class] ticketClass] ticketForService:self]; + } + + ticket.isREST = isREST; + + // Add any service specific query parameters. + NSDictionary *urlQueryParameters = self.urlQueryParameters; + if ([urlQueryParameters count] > 0) { + targetURL = [GTLUtilities URLWithString:[targetURL absoluteString] + queryParameters:urlQueryParameters]; + } + + // If this is REST and there is a developer key, add it onto the url. RPC + // adds the key into the payload, not on the url. + NSString *apiKey = self.APIKey; + if (isREST && [apiKey length] > 0) { + NSString *const kDeveloperAPIQueryParamKey = @"key"; + NSDictionary *queryParameters; + queryParameters = [NSDictionary dictionaryWithObject:apiKey + forKey:kDeveloperAPIQueryParamKey]; + targetURL = [GTLUtilities URLWithString:[targetURL absoluteString] + queryParameters:queryParameters]; + } + + NSDictionary *additionalHeaders = query.additionalHTTPHeaders; + + NSMutableURLRequest *request = [self objectRequestForURL:targetURL + object:bodyObject + ETag:etag + httpMethod:httpMethod + isREST:isREST + additionalHeaders:additionalHeaders + ticket:ticket]; + + GTMAssertSelectorNilOrImplementedWithArgs(delegate, ticket.uploadProgressSelector, + @encode(GTLServiceTicket *), @encode(unsigned long long), + @encode(unsigned long long), 0); + GTMAssertSelectorNilOrImplementedWithArgs(delegate, ticket.retrySelector, + @encode(GTLServiceTicket *), @encode(BOOL), @encode(NSError *), 0); + + SEL finishedSel = @selector(objectFetcher:finishedWithData:error:); + + ticket.postedObject = bodyObject; + + ticket.executingQuery = query; + if (ticket.originalQuery == nil) { + ticket.originalQuery = query; + } + + GTMHTTPFetcherService *fetcherService = self.fetcherService; + GTMHTTPFetcher *fetcher; + + GTLUploadParameters *uploadParams = query.uploadParameters; + if (uploadParams == nil) { + // Not uploading a file with this request + fetcher = [fetcherService fetcherWithRequest:request]; + } else { + fetcher = [self uploadFetcherWithRequest:request + fetcherService:fetcherService + params:uploadParams]; + } + + if (finishedSelector) { + // if we don't have a method name, default to the finished selector as + // a useful fetcher log comment + fetcher.comment = NSStringFromSelector(finishedSelector); + } + + // allow the user to specify static app-wide cookies for fetching + NSInteger cookieStorageMethod = [self cookieStorageMethod]; + if (cookieStorageMethod >= 0) { + fetcher.cookieStorageMethod = cookieStorageMethod; + } + + if (!mayAuthorize) { + fetcher.authorizer = nil; + } + + // copy the ticket's retry settings into the fetcher + fetcher.retryEnabled = ticket.retryEnabled; + fetcher.maxRetryInterval = ticket.maxRetryInterval; + + BOOL shouldExamineRetries; +#if NS_BLOCKS_AVAILABLE + shouldExamineRetries = (ticket.retrySelector != nil + || ticket.retryBlock != nil); +#else + shouldExamineRetries = (ticket.retrySelector != nil); +#endif + if (shouldExamineRetries) { + [fetcher setRetrySelector:@selector(objectFetcher:willRetry:forError:)]; + } + + // remember the object fetcher in the ticket + ticket.objectFetcher = fetcher; + + // add parameters used by the callbacks + + [fetcher setProperty:objectClass forKey:kFetcherObjectClassKey]; + + [fetcher setProperty:delegate forKey:kFetcherDelegateKey]; + + [fetcher setProperty:NSStringFromSelector(finishedSelector) + forKey:kFetcherFinishedSelectorKey]; + + [fetcher setProperty:ticket + forKey:kFetcherTicketKey]; + +#if NS_BLOCKS_AVAILABLE + // copy the completion handler block to the heap; this does nothing if the + // block is already on the heap + completionHandler = [[completionHandler copy] autorelease]; + [fetcher setProperty:completionHandler + forKey:kFetcherCompletionHandlerKey]; +#endif + + // set the upload data + fetcher.postData = dataToPost; + + // failed fetches call the failure selector, which will delete the ticket + BOOL didFetch = [fetcher beginFetchWithDelegate:self + didFinishSelector:finishedSel]; + + // If something weird happens and the networking callbacks have been called + // already synchronously, we don't want to return the ticket since the caller + // will never know when to stop retaining it, so we'll make sure the + // success/failure callbacks have not yet been called by checking the + // ticket + if (!didFetch || ticket.hasCalledCallback) { + fetcher.properties = nil; + return nil; + } + + return ticket; +} + +- (GTMHTTPUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request + fetcherService:(GTMHTTPFetcherService *)fetcherService + params:(GTLUploadParameters *)uploadParams { + // Hang on to the user's requested chunk size, and ensure it's not tiny + NSUInteger uploadChunkSize = [self serviceUploadChunkSize]; + if (uploadChunkSize < kMinimumUploadChunkSize) { + uploadChunkSize = kMinimumUploadChunkSize; + } + +#ifdef GTL_TARGET_NAMESPACE + // Prepend the class name prefix + Class uploadClass = NSClassFromString(@GTL_TARGET_NAMESPACE_STRING + "_GTMHTTPUploadFetcher"); +#else + Class uploadClass = NSClassFromString(@"GTMHTTPUploadFetcher"); +#endif + GTL_ASSERT(uploadClass != nil, @"GTMHTTPUploadFetcher needed"); + + NSString *uploadMIMEType = uploadParams.MIMEType; + NSData *uploadData = uploadParams.data; + NSFileHandle *uploadFileHandle = uploadParams.fileHandle; + NSURL *uploadLocationURL = uploadParams.uploadLocationURL; + + GTMHTTPUploadFetcher *fetcher; + if (uploadData) { + fetcher = [uploadClass uploadFetcherWithRequest:request + uploadData:uploadData + uploadMIMEType:uploadMIMEType + chunkSize:uploadChunkSize + fetcherService:fetcherService]; + } else if (uploadLocationURL) { + GTL_DEBUG_ASSERT(uploadFileHandle != nil, + @"Resume requires a file handle"); + fetcher = [uploadClass uploadFetcherWithLocation:uploadLocationURL + uploadFileHandle:uploadFileHandle + uploadMIMEType:uploadMIMEType + chunkSize:uploadChunkSize + fetcherService:fetcherService]; + } else { + fetcher = [uploadClass uploadFetcherWithRequest:request + uploadFileHandle:uploadFileHandle + uploadMIMEType:uploadMIMEType + chunkSize:uploadChunkSize + fetcherService:fetcherService]; + } + + NSString *slug = [uploadParams slug]; + if ([slug length] > 0) { + [[fetcher mutableRequest] setValue:slug forHTTPHeaderField:@"Slug"]; + } + return fetcher; +} + +#pragma mark - + +// RPC fetch methods + +- (NSDictionary *)rpcPayloadForMethodNamed:(NSString *)methodName + parameters:(NSDictionary *)parameters + bodyObject:(GTLObject *)bodyObject + requestID:(NSString *)requestID { + GTL_DEBUG_ASSERT([requestID length] > 0, @"Got an empty request id"); + + // First, merge the developer key and bodyObject into the parameters. + + NSString *apiKey = self.APIKey; + NSUInteger apiKeyLen = [apiKey length]; + + NSString *const kDeveloperAPIParamKey = @"key"; + NSString *const kBodyObjectParamKey = @"resource"; + + NSDictionary *finalParams; + if ((apiKeyLen == 0) && (bodyObject == nil)) { + // Nothing needs to be added, just send the dict along. + finalParams = parameters; + } else { + NSMutableDictionary *worker = [NSMutableDictionary dictionary]; + if ([parameters count] > 0) { + [worker addEntriesFromDictionary:parameters]; + } + if ((apiKeyLen > 0) + && ([worker objectForKey:kDeveloperAPIParamKey] == nil)) { + [worker setObject:apiKey forKey:kDeveloperAPIParamKey]; + } + if (bodyObject != nil) { + GTL_DEBUG_ASSERT([parameters objectForKey:kBodyObjectParamKey] == nil, + @"There was already something under the 'data' key?!"); + [worker setObject:[bodyObject JSON] forKey:kBodyObjectParamKey]; + } + finalParams = worker; + } + + // Now, build up the full dictionary for the JSON-RPC (this is the body of + // the HTTP PUT). + + // Spec calls for the jsonrpc entry. Google doesn't require it, but include + // it so the code can work with other servers. + NSMutableDictionary *rpcPayload = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"2.0", @"jsonrpc", + methodName, @"method", + requestID, @"id", + nil]; + + // Google extension, provide the version of the api. + NSString *apiVersion = self.apiVersion; + if ([apiVersion length] > 0) { + [rpcPayload setObject:apiVersion forKey:@"apiVersion"]; + } + + if ([finalParams count] > 0) { + [rpcPayload setObject:finalParams forKey:@"params"]; + } + + return rpcPayload; +} + +- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName + objectClass:(Class)objectClass + parameters:(NSDictionary *)parameters + bodyObject:(GTLObject *)bodyObject + requestID:(NSString *)requestID + urlQueryParameters:(NSDictionary *)urlQueryParameters + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector + completionHandler:(id)completionHandler // GTLServiceCompletionHandler + executingQuery:(id)executingQuery + ticket:(GTLServiceTicket *)ticket { + GTL_DEBUG_ASSERT([methodName length] > 0, @"Got an empty method name"); + if ([methodName length] == 0) return nil; + + // If we didn't get a requestID, assign one (call came from one of the public + // calls that doesn't take a GTLQuery object). + if (requestID == nil) { + requestID = [GTLQuery nextRequestID]; + } + + NSData *dataToPost = nil; + GTLUploadParameters *uploadParameters = executingQuery.uploadParameters; + BOOL shouldSendBody = !uploadParameters.shouldSendUploadOnly; + if (shouldSendBody) { + NSDictionary *rpcPayload = [self rpcPayloadForMethodNamed:methodName + parameters:parameters + bodyObject:bodyObject + requestID:requestID]; + + NSError *error = nil; + dataToPost = [GTLJSONParser dataWithObject:rpcPayload + humanReadable:NO + error:&error]; + if (dataToPost == nil) { + // There is the chance something went into parameters that wasn't valid. + GTL_DEBUG_LOG(@"JSON generation error: %@", error); + return nil; + } + } + + BOOL isUploading = (uploadParameters != nil); + NSURL *rpcURL = (isUploading ? self.rpcUploadURL : self.rpcURL); + + if ([urlQueryParameters count] > 0) { + rpcURL = [GTLUtilities URLWithString:[rpcURL absoluteString] + queryParameters:urlQueryParameters]; + } + + BOOL mayAuthorize = (executingQuery ? + !executingQuery.shouldSkipAuthorization : YES); + + GTLServiceTicket *resultTicket = [self fetchObjectWithURL:rpcURL + objectClass:objectClass + bodyObject:bodyObject + dataToPost:dataToPost + ETag:nil + httpMethod:@"POST" + mayAuthorize:mayAuthorize + isREST:NO + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:completionHandler + executingQuery:executingQuery + ticket:ticket]; + + // Set the fetcher log comment to default to the method name + NSUInteger pageNumber = resultTicket.pagesFetchedCounter; + if (pageNumber == 0) { + resultTicket.objectFetcher.comment = methodName; + } else { + // Also put the page number in the log comment + [resultTicket.objectFetcher setCommentWithFormat:@"%@ (page %lu)", + methodName, (unsigned long) (pageNumber + 1)]; + } + + return resultTicket; +} + +- (GTLServiceTicket *)executeBatchQuery:(GTLBatchQuery *)batch + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector + completionHandler:(id)completionHandler // GTLServiceCompletionHandler + ticket:(GTLServiceTicket *)ticket { + GTLBatchQuery *batchCopy = [[batch copy] autorelease]; + NSArray *queries = batchCopy.queries; + NSUInteger numberOfQueries = [queries count]; + if (numberOfQueries == 0) return nil; + + // Build up the array of RPC calls. + NSMutableArray *rpcPayloads = [NSMutableArray arrayWithCapacity:numberOfQueries]; + NSMutableArray *requestIDs = [NSMutableSet setWithCapacity:numberOfQueries]; + for (GTLQuery *query in queries) { + NSString *methodName = query.methodName; + NSDictionary *parameters = query.JSON; + GTLObject *bodyObject = query.bodyObject; + NSString *requestID = query.requestID; + + if ([methodName length] == 0 || [requestID length] == 0) { + GTL_DEBUG_ASSERT(0, @"Invalid query - id:%@ method:%@", + requestID, methodName); + return nil; + } + + GTL_DEBUG_ASSERT(query.additionalHTTPHeaders == nil, + @"additionalHTTPHeaders disallowed on queries added to a batch - query %@ (%@)", + requestID, methodName); + + GTL_DEBUG_ASSERT(query.uploadParameters == nil, + @"uploadParameters disallowed on queries added to a batch - query %@ (%@)", + requestID, methodName); + + NSDictionary *rpcPayload = [self rpcPayloadForMethodNamed:methodName + parameters:parameters + bodyObject:bodyObject + requestID:requestID]; + [rpcPayloads addObject:rpcPayload]; + + if ([requestIDs containsObject:requestID]) { + GTL_DEBUG_LOG(@"Duplicate request id in batch: %@", requestID); + return nil; + } + [requestIDs addObject:requestID]; + } + + NSError *error = nil; + NSData *dataToPost = nil; + dataToPost = [GTLJSONParser dataWithObject:rpcPayloads + humanReadable:NO + error:&error]; + if (dataToPost == nil) { + // There is the chance something went into parameters that wasn't valid. + GTL_DEBUG_LOG(@"JSON generation error: %@", error); + return nil; + } + + BOOL mayAuthorize = (batchCopy ? !batchCopy.shouldSkipAuthorization : YES); + + // urlQueryParameters on the queries are currently unsupport during a batch + // as it's not clear how to map them. + + NSURL *rpcURL = self.rpcURL; + GTLServiceTicket *resultTicket = [self fetchObjectWithURL:rpcURL + objectClass:[GTLBatchResult class] + bodyObject:nil + dataToPost:dataToPost + ETag:nil + httpMethod:@"POST" + mayAuthorize:mayAuthorize + isREST:NO + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:completionHandler + executingQuery:batch + ticket:ticket]; + +#if !STRIP_GTM_FETCH_LOGGING + // Set the fetcher log comment + // + // Because this has expensive set operations, it's conditionally + // compiled in + NSArray *methodNames = [queries valueForKey:@"methodName"]; + methodNames = [[NSSet setWithArray:methodNames] allObjects]; // de-dupe + NSString *methodsStr = [methodNames componentsJoinedByString:@", "]; + + NSUInteger pageNumber = ticket.pagesFetchedCounter; + NSString *pageStr = @""; + if (pageNumber > 0) { + pageStr = [NSString stringWithFormat:@"page %lu, ", + (unsigned long) pageNumber + 1]; + } + [resultTicket.objectFetcher setCommentWithFormat:@"batch: %@ (%@%lu queries)", + methodsStr, pageStr, (unsigned long) numberOfQueries]; +#endif + + return resultTicket; +} + + +#pragma mark - + +// REST fetch methods + +- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL + objectClass:(Class)objectClass + bodyObject:(GTLObject *)bodyObject + ETag:(NSString *)etag + httpMethod:(NSString *)httpMethod + mayAuthorize:(BOOL)mayAuthorize + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector + completionHandler:(id)completionHandler // GTLServiceCompletionHandler + ticket:(GTLServiceTicket *)ticket { + // if no URL was supplied, treat this as if the fetch failed (below) + // and immediately return a nil ticket, skipping the callbacks + // + // this might be considered normal (say, updating a read-only entry + // that lacks an edit link) though higher-level calls may assert or + // return errors depending on the specific usage + if (targetURL == nil) return nil; + + NSData *dataToPost = nil; + if (bodyObject != nil) { + NSError *error = nil; + + NSDictionary *whatToSend; + NSDictionary *json = bodyObject.JSON; + if (isRESTDataWrapperRequired_) { + // create the top-level "data" object + NSDictionary *dataDict = [NSDictionary dictionaryWithObject:json + forKey:@"data"]; + whatToSend = dataDict; + } else { + whatToSend = json; + } + dataToPost = [GTLJSONParser dataWithObject:whatToSend + humanReadable:NO + error:&error]; + if (dataToPost == nil) { + GTL_DEBUG_LOG(@"JSON generation error: %@", error); + } + } + + return [self fetchObjectWithURL:targetURL + objectClass:objectClass + bodyObject:bodyObject + dataToPost:dataToPost + ETag:etag + httpMethod:httpMethod + mayAuthorize:mayAuthorize + isREST:YES + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:completionHandler + executingQuery:nil + ticket:ticket]; +} + +- (void)invokeProgressCallbackForTicket:(GTLServiceTicket *)ticket + deliveredBytes:(unsigned long long)numReadSoFar + totalBytes:(unsigned long long)total { + + SEL progressSelector = [ticket uploadProgressSelector]; + if (progressSelector) { + + GTMHTTPFetcher *fetcher = ticket.objectFetcher; + id delegate = [fetcher propertyForKey:kFetcherDelegateKey]; + + NSMethodSignature *signature = [delegate methodSignatureForSelector:progressSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + + [invocation setSelector:progressSelector]; + [invocation setTarget:delegate]; + [invocation setArgument:&ticket atIndex:2]; + [invocation setArgument:&numReadSoFar atIndex:3]; + [invocation setArgument:&total atIndex:4]; + [invocation invoke]; + } + +#if NS_BLOCKS_AVAILABLE + GTLServiceUploadProgressBlock block = ticket.uploadProgressBlock; + if (block) { + block(ticket, numReadSoFar, total); + } +#endif +} + +// sentData callback from fetcher +- (void)objectFetcher:(GTMHTTPFetcher *)fetcher + didSendBytes:(NSInteger)bytesSent + totalBytesSent:(NSInteger)totalBytesSent +totalBytesExpectedToSend:(NSInteger)totalBytesExpected { + + GTLServiceTicket *ticket = [fetcher propertyForKey:kFetcherTicketKey]; + + [self invokeProgressCallbackForTicket:ticket + deliveredBytes:(unsigned long long)totalBytesSent + totalBytes:(unsigned long long)totalBytesExpected]; +} + +- (void)objectFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error { + // we now have the JSON data for an object, or an error + if (error == nil) { + if ([data length] > 0) { + [self prepareToParseObjectForFetcher:fetcher]; + } else { + // no data (such as when deleting) + [self handleParsedObjectForFetcher:fetcher]; + } + } else { + // There was an error from the fetch + NSInteger status = [error code]; + if (status >= 300) { + // Return the HTTP error status code along with a more descriptive error + // from within the HTTP response payload. + NSData *responseData = fetcher.downloadedData; + if ([responseData length] > 0) { + NSDictionary *responseHeaders = fetcher.responseHeaders; + NSString *contentType = [responseHeaders objectForKey:@"Content-Type"]; + + if ([data length] > 0) { + if ([contentType hasPrefix:@"application/json"]) { + NSError *parseError = nil; + NSMutableDictionary *jsonWrapper = [GTLJSONParser objectWithData:data + error:&parseError]; + if (parseError) { + // We could not parse the JSON payload + error = parseError; + } else { + // Convert the JSON error payload into a structured error + NSMutableDictionary *errorJSON = [jsonWrapper valueForKey:@"error"]; + GTLErrorObject *errorObject = [GTLErrorObject objectWithJSON:errorJSON]; + error = [errorObject foundationError]; + } + } else { + // No structured JSON error was available; make a plaintext server + // error response visible in the error object. + NSString *reasonStr = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:reasonStr + forKey:NSLocalizedFailureReasonErrorKey]; + error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain + code:status + userInfo:userInfo]; + } + } else { + // Response data length is zero; we'll settle for returning the + // fetcher's error. + } + } + } + + // store the error, call the callbacks, and bail + [fetcher setProperty:error + forKey:kFetcherFetchErrorKey]; + + [self handleParsedObjectForFetcher:fetcher]; + } +} + +// Three methods handle parsing of the fetched JSON data: +// - prepareToParse posts a start notification and then spawns off parsing +// on the operation queue (if there's an operation queue) +// - parseObject does the parsing of the JSON string +// - handleParsedObject posts the stop notification and calls the callback +// with the parsed object or an error +// +// The middle method may run on a separate thread. + +- (void)prepareToParseObjectForFetcher:(GTMHTTPFetcher *)fetcher { + // save the current thread into the fetcher, since we'll handle additional + // fetches and callbacks on this thread + [fetcher setProperty:[NSThread currentThread] + forKey:kFetcherCallbackThreadKey]; + + // copy the run loop modes, if any, so we don't need to access them + // from the parsing thread + [fetcher setProperty:[[[self runLoopModes] copy] autorelease] + forKey:kFetcherCallbackRunLoopModesKey]; + + // we post parsing notifications now to ensure they're on caller's + // original thread + GTLServiceTicket *ticket = [fetcher propertyForKey:kFetcherTicketKey]; + NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter]; + [defaultNC postNotificationName:kGTLServiceTicketParsingStartedNotification + object:ticket]; + [fetcher setProperty:@"1" + forKey:kFetcherParsingNotificationKey]; + + id executingQuery = ticket.executingQuery; + if ([executingQuery isBatchQuery]) { + // build a dictionary of expected classes for the batch responses + GTLBatchQuery *batchQuery = executingQuery; + NSArray *queries = batchQuery.queries; + NSDictionary *batchClassMap = [NSMutableDictionary dictionaryWithCapacity:[queries count]]; + for (GTLQuery *query in queries) { + [batchClassMap setValue:query.expectedObjectClass + forKey:query.requestID]; + } + [fetcher setProperty:batchClassMap + forKey:kFetcherBatchClassMapKey]; + } + + // if there's an operation queue, then use that to schedule parsing on another + // thread + SEL parseSel = @selector(parseObjectFromDataOfFetcher:); + NSOperationQueue *queue = self.parseQueue; + if (queue) { + NSInvocationOperation *op; + op = [[[NSInvocationOperation alloc] initWithTarget:self + selector:parseSel + object:fetcher] autorelease]; + ticket.parseOperation = op; + [queue addOperation:op]; + // the fetcher now belongs to the parsing thread + } else { + // parse on the current thread, on Mac OS X 10.4 through 10.5.7 + // or when the project defines GTL_SKIP_PARSE_THREADING + [self performSelector:parseSel + withObject:fetcher]; + } +} + +- (void)parseObjectFromDataOfFetcher:(GTMHTTPFetcher *)fetcher { + // This method runs in a separate thread + + // Generally protect the fetcher properties, since canceling a ticket would + // release the fetcher properties dictionary + NSMutableDictionary *properties = [[fetcher.properties retain] autorelease]; + + // The callback thread is retaining the fetcher, so the fetcher shouldn't keep + // retaining the callback thread + NSThread *callbackThread = [properties valueForKey:kFetcherCallbackThreadKey]; + [[callbackThread retain] autorelease]; + [properties removeObjectForKey:kFetcherCallbackThreadKey]; + + GTLServiceTicket *ticket = [properties valueForKey:kFetcherTicketKey]; + [[ticket retain] autorelease]; + + NSDictionary *responseHeaders = fetcher.responseHeaders; + NSString *contentType = [responseHeaders objectForKey:@"Content-Type"]; + NSData *data = fetcher.downloadedData; + + NSOperation *parseOperation = ticket.parseOperation; + + GTL_DEBUG_ASSERT([contentType hasPrefix:@"application/json"], + @"Got unexpected content type '%@'", contentType); + if ([contentType hasPrefix:@"application/json"] && [data length] > 0) { +#if GTL_LOG_PERFORMANCE + NSTimeInterval secs1, secs2; + secs1 = [NSDate timeIntervalSinceReferenceDate]; +#endif + + NSError *parseError = nil; + NSMutableDictionary *jsonWrapper = [GTLJSONParser objectWithData:data + error:&parseError]; + if ([parseOperation isCancelled]) return; + + if (parseError != nil) { + [properties setValue:parseError forKey:kFetcherFetchErrorKey]; + } else { + NSMutableDictionary *json; + NSDictionary *batchClassMap = nil; + + // In theory, just checking for "application/json-rpc" vs + // "application/json" would work. But the JSON-RPC spec allows for + // "application/json" also so we have to carry a flag all the way in + // saying which type of result to expect and process as. + BOOL isREST = ticket.isREST; + if (isREST) { + if (isRESTDataWrapperRequired_) { + json = [jsonWrapper valueForKey:@"data"]; + } else { + json = jsonWrapper; + } + } else { + batchClassMap = [properties valueForKey:kFetcherBatchClassMapKey]; + if (batchClassMap) { + // A batch gets the whole array as it's json. + json = jsonWrapper; + } else { + json = [jsonWrapper valueForKey:@"result"]; + } + } + + if (json != nil) { + Class defaultClass = [properties valueForKey:kFetcherObjectClassKey]; + NSDictionary *surrogates = ticket.surrogates; + + GTLObject *parsedObject = [GTLObject objectForJSON:json + defaultClass:defaultClass + surrogates:surrogates + batchClassMap:batchClassMap]; + + [properties setValue:parsedObject forKey:kFetcherParsedObjectKey]; + } else if (!isREST) { + NSMutableDictionary *errorJSON = [jsonWrapper valueForKey:@"error"]; + GTL_DEBUG_ASSERT(errorJSON != nil, @"no result or error in response:\n%@", + jsonWrapper); + GTLErrorObject *errorObject = [GTLErrorObject objectWithJSON:errorJSON]; + NSError *error = [errorObject foundationError]; + + // Store the error and let it go to the callback + [properties setValue:error + forKey:kFetcherFetchErrorKey]; + } + } + +#if GTL_LOG_PERFORMANCE + secs2 = [NSDate timeIntervalSinceReferenceDate]; + NSLog(@"allocation of %@ took %f seconds", objectClass, secs2 - secs1); +#endif + } + + if ([parseOperation isCancelled]) return; + + SEL parseDoneSel = @selector(handleParsedObjectForFetcher:); + NSArray *runLoopModes = [properties valueForKey:kFetcherCallbackRunLoopModesKey]; + if (runLoopModes) { + [self performSelector:parseDoneSel + onThread:callbackThread + withObject:fetcher + waitUntilDone:NO + modes:runLoopModes]; + } else { + // defaults to common modes + [self performSelector:parseDoneSel + onThread:callbackThread + withObject:fetcher + waitUntilDone:NO]; + } + + // the fetcher now belongs to the callback thread +} + +- (void)handleParsedObjectForFetcher:(GTMHTTPFetcher *)fetcher { + // After parsing is complete, this is invoked on the thread that the + // fetch was performed on + // + // There may not be an object due to a fetch or parsing error + + GTLServiceTicket *ticket = [fetcher propertyForKey:kFetcherTicketKey]; + ticket.parseOperation = nil; + + // unpack the callback parameters + id delegate = [fetcher propertyForKey:kFetcherDelegateKey]; + NSString *selString = [fetcher propertyForKey:kFetcherFinishedSelectorKey]; + SEL finishedSelector = NSSelectorFromString(selString); + +#if NS_BLOCKS_AVAILABLE + GTLServiceCompletionHandler completionHandler; + completionHandler = [fetcher propertyForKey:kFetcherCompletionHandlerKey]; +#else + id completionHandler = nil; +#endif + + GTLObject *object = [fetcher propertyForKey:kFetcherParsedObjectKey]; + NSError *error = [fetcher propertyForKey:kFetcherFetchErrorKey]; + + GTLQuery *executingQuery = (GTLQuery *)ticket.executingQuery; + + BOOL shouldFetchNextPages = ticket.shouldFetchNextPages; + GTLObject *previousObject = ticket.fetchedObject; + + if (shouldFetchNextPages + && (previousObject != nil) + && (object != nil)) { + // Accumulate new results + object = [self mergedNewResultObject:object + oldResultObject:previousObject + forQuery:executingQuery]; + } + + ticket.fetchedObject = object; + ticket.fetchError = error; + + if ([fetcher propertyForKey:kFetcherParsingNotificationKey] != nil) { + // we want to always balance the start and stop notifications + NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter]; + [defaultNC postNotificationName:kGTLServiceTicketParsingStoppedNotification + object:ticket]; + } + + BOOL shouldCallCallbacks = YES; + + // Use the nextPageToken to fetch any later pages for non-batch queries + // + // This assumes a pagination model where objects have entries in an "items" + // field and a "nextPageToken" field, and queries support a "pageToken" + // parameter. + if (ticket.shouldFetchNextPages) { + // Determine if we should fetch more pages of results + + GTLQuery *nextPageQuery = [self nextPageQueryForQuery:executingQuery + result:object + ticket:ticket]; + if (nextPageQuery) { + BOOL isFetchingMore = [self fetchNextPageWithQuery:nextPageQuery + delegate:delegate + didFinishedSelector:finishedSelector + completionHandler:completionHandler + ticket:ticket]; + if (isFetchingMore) { + shouldCallCallbacks = NO; + } + } else { + // No more page tokens are present +#if DEBUG && !GTL_SKIP_PAGES_WARNING + // Each next page followed to accumulate all pages of a feed takes up to + // a few seconds. When multiple pages are being fetched, that + // usually indicates that a larger page size (that is, more items per + // feed fetched) should be requested. + // + // To avoid fetching many pages, set query.maxResults so the feed + // requested is large enough to rarely need to follow next links. + NSUInteger pageCount = ticket.pagesFetchedCounter; + if (pageCount > 2) { + NSString *queryLabel = [executingQuery isBatchQuery] ? + @"batch query" : executingQuery.methodName; + NSLog(@"Executing %@ required fetching %u pages; use a query with a" + @" larger maxResults for faster results", + queryLabel, (unsigned int) pageCount); + } +#endif + } + } + + // We no longer care about the queries for page 2 or later, so for the client + // inspecting the ticket in the callback, the executing query should be + // the original one + ticket.executingQuery = ticket.originalQuery; + + if (shouldCallCallbacks) { + // First, call query-specific callback blocks. We do this before the + // fetch callback to let applications do any final clean-up (or update + // their UI) in the fetch callback. + GTLQuery *originalQuery = (GTLQuery *)ticket.originalQuery; +#if NS_BLOCKS_AVAILABLE + if (![originalQuery isBatchQuery]) { + // Single query + GTLServiceCompletionHandler completionBlock = originalQuery.completionBlock; + if (completionBlock) { + completionBlock(ticket, object, error); + } + } else { + // Batch query + // + // We'll step through the queries of the original batch, not of the + // batch result + GTLBatchQuery *batchQuery = (GTLBatchQuery *)originalQuery; + GTLBatchResult *batchResult = (GTLBatchResult *)object; + NSDictionary *successes = batchResult.successes; + NSDictionary *failures = batchResult.failures; + + for (GTLQuery *oneQuery in batchQuery.queries) { + GTLServiceCompletionHandler completionBlock = oneQuery.completionBlock; + if (completionBlock) { + // If there was no networking error, look for a query-specific + // error or result + GTLObject *oneResult = nil; + NSError *oneError = error; + if (oneError == nil) { + NSString *requestID = [oneQuery requestID]; + GTLErrorObject *gtlError = [failures objectForKey:requestID]; + if (gtlError) { + oneError = [gtlError foundationError]; + } else { + oneResult = [successes objectForKey:requestID]; + if (oneResult == nil) { + // We found neither a success nor a failure for this + // query, unexpectedly + GTL_DEBUG_LOG(@"GTLService: Batch result missing for request %@", + requestID); + oneError = [NSError errorWithDomain:kGTLServiceErrorDomain + code:kGTLErrorQueryResultMissing + userInfo:nil]; + } + } + } + completionBlock(ticket, oneResult, oneError); + } + } + } +#endif + // Release query callback blocks + [originalQuery executionDidStop]; + + if (finishedSelector) { + [[self class] invokeCallback:finishedSelector + target:delegate + ticket:ticket + object:object + error:error]; + } + +#if NS_BLOCKS_AVAILABLE + if (completionHandler) { + completionHandler(ticket, object, error); + } +#endif + ticket.hasCalledCallback = YES; + } + fetcher.properties = nil; + +#if NS_BLOCKS_AVAILABLE + // Tickets don't know when the fetch has completed, so the service will + // release their blocks here to avoid unintended retain loops + ticket.retryBlock = nil; + ticket.uploadProgressBlock = nil; +#endif +} + +#pragma mark - + ++ (void)invokeCallback:(SEL)callbackSel + target:(id)target + ticket:(id)ticket + object:(id)object + error:(id)error { + + // GTL fetch callbacks have no return value + NSMethodSignature *signature = [target methodSignatureForSelector:callbackSel]; + NSInvocation *retryInvocation = [NSInvocation invocationWithMethodSignature:signature]; + [retryInvocation setSelector:callbackSel]; + [retryInvocation setTarget:target]; + [retryInvocation setArgument:&ticket atIndex:2]; + [retryInvocation setArgument:&object atIndex:3]; + [retryInvocation setArgument:&error atIndex:4]; + [retryInvocation invoke]; +} + +// The object fetcher may call into this retry method; this one invokes the +// selector provided by the user. +- (BOOL)objectFetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)willRetry forError:(NSError *)error { + + GTLServiceTicket *ticket = [fetcher propertyForKey:kFetcherTicketKey]; + SEL retrySelector = ticket.retrySelector; + if (retrySelector) { + id delegate = [fetcher propertyForKey:kFetcherDelegateKey]; + + willRetry = [self invokeRetrySelector:retrySelector + delegate:delegate + ticket:ticket + willRetry:willRetry + error:error]; + } + +#if NS_BLOCKS_AVAILABLE + BOOL (^retryBlock)(GTLServiceTicket *, BOOL, NSError *) = ticket.retryBlock; + if (retryBlock) { + willRetry = retryBlock(ticket, willRetry, error); + } +#endif + + return willRetry; +} + +- (BOOL)invokeRetrySelector:(SEL)retrySelector + delegate:(id)delegate + ticket:(GTLServiceTicket *)ticket + willRetry:(BOOL)willRetry + error:(NSError *)error { + + if ([delegate respondsToSelector:retrySelector]) { + // Unlike the retry selector invocation in GTMHTTPFetcher, this invocation + // passes the ticket rather than the fetcher as argument 2 + NSMethodSignature *signature = [delegate methodSignatureForSelector:retrySelector]; + NSInvocation *retryInvocation = [NSInvocation invocationWithMethodSignature:signature]; + [retryInvocation setSelector:retrySelector]; + [retryInvocation setTarget:delegate]; + [retryInvocation setArgument:&ticket atIndex:2]; // ticket passed + [retryInvocation setArgument:&willRetry atIndex:3]; + [retryInvocation setArgument:&error atIndex:4]; + [retryInvocation invoke]; + + [retryInvocation getReturnValue:&willRetry]; + } + return willRetry; +} + +- (BOOL)waitForTicket:(GTLServiceTicket *)ticket + timeout:(NSTimeInterval)timeoutInSeconds + fetchedObject:(GTLObject **)outObjectOrNil + error:(NSError **)outErrorOrNil { + + NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + + // loop until the fetch completes with an object or an error, + // or until the timeout has expired + while (![ticket hasCalledCallback] + && [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]; + } + + NSError *fetchError = ticket.fetchError; + + if (![ticket hasCalledCallback] && fetchError == nil) { + fetchError = [NSError errorWithDomain:kGTLServiceErrorDomain + code:kGTLErrorWaitTimedOut + userInfo:nil]; + } + + if (outObjectOrNil) *outObjectOrNil = ticket.fetchedObject; + if (outErrorOrNil) *outErrorOrNil = fetchError; + + return (fetchError == nil); +} + +#pragma mark - + +// Given a single or batch query and its result, make a new query +// for the next pages, if any. Returns nil if there's no additional +// query to make. +// +// This method calls itself recursively to make the individual next page +// queries for a batch query. +- (id )nextPageQueryForQuery:(GTLQuery *)query + result:(GTLObject *)object + ticket:(GTLServiceTicket *)ticket { + if (!query.isBatchQuery) { + // This is a single query + + // Determine if we should fetch more pages of results + GTLQuery *nextPageQuery = nil; + NSString *nextPageToken = nil; + NSNumber *nextStartIndex = nil; + + if ([object respondsToSelector:@selector(nextPageToken)] + && [query respondsToSelector:@selector(pageToken)]) { + nextPageToken = [object performSelector:@selector(nextPageToken)]; + } + + if ([object respondsToSelector:@selector(nextStartIndex)] + && [query respondsToSelector:@selector(startIndex)]) { + nextStartIndex = [object performSelector:@selector(nextStartIndex)]; + } + + if (nextPageToken || nextStartIndex) { + // Make a query for the next page, preserving the request ID + nextPageQuery = [[query copy] autorelease]; + nextPageQuery.requestID = query.requestID; + + if (nextPageToken) { + [nextPageQuery performSelector:@selector(setPageToken:) + withObject:nextPageToken]; + } else { + // Use KVC to unwrap the scalar type instead of converting the + // NSNumber to an integer and using NSInvocation + [nextPageQuery setValue:nextStartIndex + forKey:@"startIndex"]; + } + } + return nextPageQuery; + } else { + // This is a batch query + // + // Check if there's a next page to fetch for any of the success + // results by invoking this method recursively on each of those results + GTLBatchResult *batchResult = (GTLBatchResult *)object; + GTLBatchQuery *nextPageBatchQuery = nil; + NSDictionary *successes = batchResult.successes; + + for (NSString *requestID in successes) { + GTLObject *singleObject = [successes objectForKey:requestID]; + GTLQuery *singleQuery = [ticket queryForRequestID:requestID]; + + GTLQuery *newQuery = [self nextPageQueryForQuery:singleQuery + result:singleObject + ticket:ticket]; + if (newQuery) { + // There is another query to fetch + if (nextPageBatchQuery == nil) { + nextPageBatchQuery = [GTLBatchQuery batchQuery]; + } + [nextPageBatchQuery addQuery:newQuery]; + } + } + return nextPageBatchQuery; + } +} + +// When a ticket is set to fetch more pages for feeds, this routine +// initiates the fetch for each additional feed page +- (BOOL)fetchNextPageWithQuery:(GTLQuery *)query + delegate:(id)delegate + didFinishedSelector:(SEL)finishedSelector + completionHandler:(GTLServiceCompletionHandler)completionHandler + ticket:(GTLServiceTicket *)ticket { + // Sanity check the number of pages fetched already + NSUInteger oldPagesFetchedCounter = ticket.pagesFetchedCounter; + + if (oldPagesFetchedCounter > kMaxNumberOfNextPagesFetched) { + // Sanity check failed: way too many pages were fetched + // + // The client should be querying with a higher max results per page + // to avoid this + GTL_DEBUG_ASSERT(0, @"Fetched too many next pages for %@", + query.methodName); + return NO; + } + + ticket.pagesFetchedCounter = 1 + oldPagesFetchedCounter; + + GTLServiceTicket *newTicket; + if (query.isBatchQuery) { + newTicket = [self executeBatchQuery:(GTLBatchQuery *)query + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:completionHandler + ticket:ticket]; + } else { + newTicket = [self fetchObjectWithMethodNamed:query.methodName + objectClass:query.expectedObjectClass + parameters:query.JSON + bodyObject:query.bodyObject + requestID:query.requestID + urlQueryParameters:query.urlQueryParameters + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:completionHandler + executingQuery:query + ticket:ticket]; + } + + // In the bizarre case that the fetch didn't begin, newTicket will be + // nil. So long as the new ticket is the same as the ticket we're + // continuing, then we're happy. + return (newTicket == ticket); +} + +// Given a new single or batch result (meaning additional pages for a previous +// query result), merge it into the old result. +- (GTLObject *)mergedNewResultObject:(GTLObject *)newResult + oldResultObject:(GTLObject *)oldResult + forQuery:(GTLQuery *)query { + if (query.isBatchQuery) { + // Batch query result + // + // The new batch results are a subset of the old result's queries, since + // not all queries in the batch necessarily have additional pages. + // + // New success objects replace old success objects, with the old items + // prepended; new failure objects replace old success objects. + // We will update the old batch results with accumulated items, using the + // new objects, and return the old batch. + // + // We reuse the old batch results object because it may include some earlier + // results which did not have additional pages. + GTLBatchResult *newBatchResult = (GTLBatchResult *)newResult; + GTLBatchResult *oldBatchResult = (GTLBatchResult *)oldResult; + + NSMutableDictionary *newSuccesses = newBatchResult.successes; + NSMutableDictionary *newFailures = newBatchResult.failures; + NSMutableDictionary *oldSuccesses = oldBatchResult.successes; + NSMutableDictionary *oldFailures = oldBatchResult.failures; + + for (NSString *requestID in newSuccesses) { + // Prepend the old items to the new response's items + // + // We can assume the objects are collections since they're present in + // additional pages. + GTLCollectionObject *newObj = [newSuccesses objectForKey:requestID]; + GTLCollectionObject *oldObj = [oldSuccesses objectForKey:requestID]; + + NSMutableArray *items = [NSMutableArray arrayWithArray:oldObj.items]; + [items addObjectsFromArray:newObj.items]; + [newObj performSelector:@selector(setItems:) withObject:items]; + + // Replace the old object with the new one + [oldSuccesses setObject:newObj forKey:requestID]; + } + + for (NSString *requestID in newFailures) { + // Replace old successes or failures with the new failure + GTLErrorObject *newError = [newFailures objectForKey:requestID]; + [oldFailures setObject:newError forKey:requestID]; + [oldSuccesses removeObjectForKey:requestID]; + } + return oldBatchResult; + } else { + // Single query result + // + // Merge the items into the new object, and return that. + // + // We can assume the objects are collections since they're present in + // additional pages. + GTLCollectionObject *newObj = (GTLCollectionObject *)newResult; + GTLCollectionObject *oldObj = (GTLCollectionObject *)oldResult; + + NSMutableArray *items = [NSMutableArray arrayWithArray:oldObj.items]; + [items addObjectsFromArray:newObj.items]; + [newObj performSelector:@selector(setItems:) withObject:items]; + + return newObj; + } +} + +#pragma mark - + +// GTLQuery methods. + +- (GTLServiceTicket *)executeQuery:(id)queryObj + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + if ([queryObj isBatchQuery]) { + return [self executeBatchQuery:queryObj + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:NULL + ticket:nil]; + } + + GTLQuery *query = [[(GTLQuery *)queryObj copy] autorelease]; + NSString *methodName = query.methodName; + NSDictionary *params = query.JSON; + GTLObject *bodyObject = query.bodyObject; + + return [self fetchObjectWithMethodNamed:methodName + objectClass:query.expectedObjectClass + parameters:params + bodyObject:bodyObject + requestID:query.requestID + urlQueryParameters:query.urlQueryParameters + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + executingQuery:query + ticket:nil]; +} + +#if NS_BLOCKS_AVAILABLE +- (GTLServiceTicket *)executeQuery:(id)queryObj + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + if ([queryObj isBatchQuery]) { + return [self executeBatchQuery:queryObj + delegate:nil + didFinishSelector:NULL + completionHandler:handler + ticket:nil]; + } + + GTLQuery *query = [[(GTLQuery *)queryObj copy] autorelease]; + NSString *methodName = query.methodName; + NSDictionary *params = query.JSON; + GTLObject *bodyObject = query.bodyObject; + + return [self fetchObjectWithMethodNamed:methodName + objectClass:query.expectedObjectClass + parameters:params + bodyObject:bodyObject + requestID:query.requestID + urlQueryParameters:query.urlQueryParameters + delegate:nil + didFinishSelector:NULL + completionHandler:handler + executingQuery:query + ticket:nil]; +} +#endif + +#pragma mark - + +- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName + parameters:(NSDictionary *)parameters + objectClass:(Class)objectClass + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + return [self fetchObjectWithMethodNamed:methodName + objectClass:objectClass + parameters:parameters + bodyObject:nil + requestID:nil + urlQueryParameters:nil + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + executingQuery:nil + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName + insertingObject:(GTLObject *)bodyObject + objectClass:(Class)objectClass + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + return [self fetchObjectWithMethodNamed:methodName + objectClass:objectClass + parameters:nil + bodyObject:bodyObject + requestID:nil + urlQueryParameters:nil + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + executingQuery:nil + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName + parameters:(NSDictionary *)parameters + insertingObject:(GTLObject *)bodyObject + objectClass:(Class)objectClass + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + return [self fetchObjectWithMethodNamed:methodName + objectClass:objectClass + parameters:parameters + bodyObject:bodyObject + requestID:nil + urlQueryParameters:nil + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + executingQuery:nil + ticket:nil]; +} + +#if NS_BLOCKS_AVAILABLE +- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName + parameters:(NSDictionary *)parameters + objectClass:(Class)objectClass + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + return [self fetchObjectWithMethodNamed:methodName + objectClass:objectClass + parameters:parameters + bodyObject:nil + requestID:nil + urlQueryParameters:nil + delegate:nil + didFinishSelector:NULL + completionHandler:handler + executingQuery:nil + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName + insertingObject:(GTLObject *)bodyObject + objectClass:(Class)objectClass + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + return [self fetchObjectWithMethodNamed:methodName + objectClass:objectClass + parameters:nil + bodyObject:bodyObject + requestID:nil + urlQueryParameters:nil + delegate:nil + didFinishSelector:NULL + completionHandler:handler + executingQuery:nil + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectWithMethodNamed:(NSString *)methodName + parameters:(NSDictionary *)parameters + insertingObject:(GTLObject *)bodyObject + objectClass:(Class)objectClass + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + return [self fetchObjectWithMethodNamed:methodName + objectClass:objectClass + parameters:parameters + bodyObject:bodyObject + requestID:nil + urlQueryParameters:nil + delegate:nil + didFinishSelector:NULL + completionHandler:handler + executingQuery:nil + ticket:nil]; +} +#endif + +#pragma mark - + +// These external entry points doing a REST style fetch. + +- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)feedURL + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + // no object class specified; use registered class + return [self fetchObjectWithURL:feedURL + objectClass:nil + bodyObject:nil + ETag:nil + httpMethod:nil + mayAuthorize:YES + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + ticket:nil]; +} + +- (GTLServiceTicket *)fetchPublicObjectWithURL:(NSURL *)feedURL + objectClass:(Class)objectClass + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + return [self fetchObjectWithURL:feedURL + objectClass:objectClass + bodyObject:nil + ETag:nil + httpMethod:nil + mayAuthorize:NO + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)feedURL + objectClass:(Class)objectClass + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + return [self fetchObjectWithURL:feedURL + objectClass:objectClass + bodyObject:nil + ETag:nil + httpMethod:nil + mayAuthorize:YES + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + ticket:nil]; +} + + +- (GTLServiceTicket *)fetchObjectByInsertingObject:(GTLObject *)bodyToPost + forURL:(NSURL *)destinationURL + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + Class objClass = [bodyToPost class]; + NSString *etag = ETagIfPresent(bodyToPost); + + return [self fetchObjectWithURL:destinationURL + objectClass:objClass + bodyObject:bodyToPost + ETag:etag + httpMethod:@"POST" + mayAuthorize:YES + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectByUpdatingObject:(GTLObject *)bodyToPut + forURL:(NSURL *)destinationURL + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + Class objClass = [bodyToPut class]; + NSString *etag = ETagIfPresent(bodyToPut); + + return [self fetchObjectWithURL:destinationURL + objectClass:objClass + bodyObject:bodyToPut + ETag:etag + httpMethod:@"PUT" + mayAuthorize:YES + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + ticket:nil]; +} + + +- (GTLServiceTicket *)deleteResourceURL:(NSURL *)destinationURL + ETag:(NSString *)etagOrNil + delegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + return [self fetchObjectWithURL:destinationURL + objectClass:nil + bodyObject:nil + ETag:etagOrNil + httpMethod:@"DELETE" + mayAuthorize:YES + delegate:delegate + didFinishSelector:finishedSelector + completionHandler:nil + ticket:nil]; +} + + +#if NS_BLOCKS_AVAILABLE +- (GTLServiceTicket *)fetchObjectWithURL:(NSURL *)objectURL + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + return [self fetchObjectWithURL:objectURL + objectClass:nil + bodyObject:nil + ETag:nil + httpMethod:nil + mayAuthorize:YES + delegate:nil + didFinishSelector:NULL + completionHandler:handler + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectByInsertingObject:(GTLObject *)bodyToPost + forURL:(NSURL *)destinationURL + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + Class objClass = [bodyToPost class]; + NSString *etag = ETagIfPresent(bodyToPost); + + return [self fetchObjectWithURL:destinationURL + objectClass:objClass + bodyObject:bodyToPost + ETag:etag + httpMethod:@"POST" + mayAuthorize:YES + delegate:nil + didFinishSelector:NULL + completionHandler:handler + ticket:nil]; +} + +- (GTLServiceTicket *)fetchObjectByUpdatingObject:(GTLObject *)bodyToPut + forURL:(NSURL *)destinationURL + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + Class objClass = [bodyToPut class]; + NSString *etag = ETagIfPresent(bodyToPut); + + return [self fetchObjectWithURL:destinationURL + objectClass:objClass + bodyObject:bodyToPut + ETag:etag + httpMethod:@"PUT" + mayAuthorize:YES + delegate:nil + didFinishSelector:NULL + completionHandler:handler + ticket:nil]; +} + +- (GTLServiceTicket *)deleteResourceURL:(NSURL *)destinationURL + ETag:(NSString *)etagOrNil + completionHandler:(void (^)(GTLServiceTicket *ticket, id object, NSError *error))handler { + return [self fetchObjectWithURL:destinationURL + objectClass:nil + bodyObject:nil + ETag:etagOrNil + httpMethod:@"DELETE" + mayAuthorize:YES + delegate:nil + didFinishSelector:NULL + completionHandler:handler + ticket:nil]; +} + +#endif // NS_BLOCKS_AVAILABLE + +#pragma mark - + +- (NSString *)userAgent { + return userAgent_; +} + +- (void)setExactUserAgent:(NSString *)userAgent { + // internal use only + [userAgent_ release]; + userAgent_ = [userAgent copy]; +} + +- (void)setUserAgent:(NSString *)userAgent { + // remove whitespace and unfriendly characters + NSString *str = GTMCleanedUserAgentString(userAgent); + [self setExactUserAgent:str]; +} + +// +// The following methods pass through to the fetcher service object +// + +- (void)setCookieStorageMethod:(NSInteger)method { + self.fetcherService.cookieStorageMethod = method; +} + +- (NSInteger)cookieStorageMethod { + return self.fetcherService.cookieStorageMethod; +} + +- (void)setShouldFetchInBackground:(BOOL)flag { + self.fetcherService.shouldFetchInBackground = flag; +} + +- (BOOL)shouldFetchInBackground { + return self.fetcherService.shouldFetchInBackground; +} + +- (void)setRunLoopModes:(NSArray *)array { + self.fetcherService.runLoopModes = array; +} + +- (NSArray *)runLoopModes { + return self.fetcherService.runLoopModes; +} + +#pragma mark - + +// The service properties becomes the initial value for each future ticket's +// properties +- (void)setServiceProperties:(NSDictionary *)dict { + [serviceProperties_ autorelease]; + serviceProperties_ = [dict mutableCopy]; +} + +- (NSDictionary *)serviceProperties { + // be sure the returned pointer has the life of the autorelease pool, + // in case self is released immediately + return [[serviceProperties_ retain] autorelease]; +} + +- (void)setServiceProperty:(id)obj forKey:(NSString *)key { + + if (obj == nil) { + // user passed in nil, so delete the property + [serviceProperties_ removeObjectForKey:key]; + } else { + // be sure the property dictionary exists + if (serviceProperties_ == nil) { + [self setServiceProperties:[NSDictionary dictionary]]; + } + [serviceProperties_ setObject:obj forKey:key]; + } +} + +- (id)servicePropertyForKey:(NSString *)key { + id obj = [serviceProperties_ objectForKey:key]; + + // be sure the returned pointer has the life of the autorelease pool, + // in case self is released immediately + return [[obj retain] autorelease]; +} + +- (void)setServiceUserData:(id)userData { + [self setServiceProperty:userData forKey:kUserDataPropertyKey]; +} + +- (id)serviceUserData { + return [[[self servicePropertyForKey:kUserDataPropertyKey] retain] autorelease]; +} + +- (void)setAuthorizer:(id )authorizer { + self.fetcherService.authorizer = authorizer; +} + +- (id )authorizer { + return self.fetcherService.authorizer; +} + ++ (NSUInteger)defaultServiceUploadChunkSize { + // subclasses may override + return kStandardUploadChunkSize; +} + +- (NSUInteger)serviceUploadChunkSize { + return uploadChunkSize_; +} + +- (void)setServiceUploadChunkSize:(NSUInteger)val { + + if (val == kGTLStandardUploadChunkSize) { + // determine an appropriate upload chunk size for the system + + if (![GTMHTTPFetcher doesSupportSentDataCallback]) { + // for 10.4 and iPhone 2, we need a small upload chunk size so there + // are frequent intrachunk callbacks for progress monitoring + val = 75000; + } else { +#if GTL_IPHONE + val = 1000000; +#else + if (NSFoundationVersionNumber >= 751.00) { + // Mac OS X 10.6 + // + // we'll pick a huge upload chunk size, which minimizes http overhead + // and server effort, and we'll hope that NSURLConnection can finally + // handle big uploads reliably + val = 25000000; + } else { + // Mac OS X 10.5 + // + // NSURLConnection is more reliable on POSTs in 10.5 than it was in + // 10.4, but it still fails mysteriously on big uploads on some + // systems, so we'll limit the chunks to a megabyte + val = 1000000; + } +#endif + } + } + uploadChunkSize_ = val; +} + +@end + +@implementation GTLServiceTicket + +@synthesize shouldFetchNextPages = shouldFetchNextPages_, + surrogates = surrogates_, + uploadProgressSelector = uploadProgressSelector_, + retryEnabled = isRetryEnabled_, + hasCalledCallback = hasCalledCallback_, + retrySelector = retrySelector_, + maxRetryInterval = maxRetryInterval_, + objectFetcher = objectFetcher_, + postedObject = postedObject_, + fetchedObject = fetchedObject_, + executingQuery = executingQuery_, + originalQuery = originalQuery_, + fetchError = fetchError_, + pagesFetchedCounter = pagesFetchedCounter_, + APIKey = apiKey_, + parseOperation = parseOperation_, + isREST = isREST_; + +#if NS_BLOCKS_AVAILABLE +@synthesize retryBlock = retryBlock_; +#endif + ++ (id)ticketForService:(GTLService *)service { + return [[[self alloc] initWithService:service] autorelease]; +} + +- (id)initWithService:(GTLService *)service { + self = [super init]; + if (self) { + service_ = [service retain]; + + ticketProperties_ = [service.serviceProperties mutableCopy]; + surrogates_ = [service.surrogates retain]; + uploadProgressSelector_ = service.uploadProgressSelector; + isRetryEnabled_ = service.retryEnabled; + retrySelector_ = service.retrySelector; + maxRetryInterval_ = service.maxRetryInterval; + shouldFetchNextPages_ = service.shouldFetchNextPages; + apiKey_ = [service.APIKey copy]; + +#if NS_BLOCKS_AVAILABLE + uploadProgressBlock_ = [service.uploadProgressBlock copy]; + retryBlock_ = [service.retryBlock copy]; +#endif + } + return self; +} + +- (void)dealloc { + [service_ release]; + [ticketProperties_ release]; + [surrogates_ release]; + [objectFetcher_ release]; +#if NS_BLOCKS_AVAILABLE + [uploadProgressBlock_ release]; + [retryBlock_ release]; +#endif + [postedObject_ release]; + [fetchedObject_ release]; + [executingQuery_ release]; + [originalQuery_ release]; + [fetchError_ release]; + [apiKey_ release]; + [parseOperation_ release]; + + [super dealloc]; +} + +- (NSString *)description { + NSString *devKeyInfo = @""; + if (apiKey_ != nil) { + devKeyInfo = [NSString stringWithFormat:@" devKey:%@", apiKey_]; + } + + NSString *authorizerInfo = @""; + id authorizer = self.objectFetcher.authorizer; + if (authorizer != nil) { + authorizerInfo = [NSString stringWithFormat:@" authorizer:%@", authorizer]; + } + + return [NSString stringWithFormat:@"%@ %p: {service:%@%@%@ fetcher:%@ }", + [self class], self, service_, devKeyInfo, authorizerInfo, objectFetcher_]; +} + +- (void)pauseUpload { + BOOL canPause = [objectFetcher_ respondsToSelector:@selector(pauseFetching)]; + GTL_DEBUG_ASSERT(canPause, @"unpauseable ticket"); + + if (canPause) { + [(GTMHTTPUploadFetcher *)objectFetcher_ pauseFetching]; + } +} + +- (void)resumeUpload { + BOOL canResume = [objectFetcher_ respondsToSelector:@selector(resumeFetching)]; + GTL_DEBUG_ASSERT(canResume, @"unresumable ticket"); + + if (canResume) { + [(GTMHTTPUploadFetcher *)objectFetcher_ resumeFetching]; + } +} + +- (BOOL)isUploadPaused { + BOOL isPausable = [objectFetcher_ respondsToSelector:@selector(isPaused)]; + GTL_DEBUG_ASSERT(isPausable, @"unpauseable ticket"); + + if (isPausable) { + return [(GTMHTTPUploadFetcher *)objectFetcher_ isPaused]; + } + return NO; +} + +- (void)cancelTicket { + NSOperation *parseOperation = self.parseOperation; + [parseOperation cancel]; + self.parseOperation = nil; + + [objectFetcher_ stopFetching]; + objectFetcher_.properties = nil; + + self.objectFetcher = nil; + self.properties = nil; + self.uploadProgressSelector = nil; + +#if NS_BLOCKS_AVAILABLE + self.uploadProgressBlock = nil; + self.retryBlock = nil; +#endif + [self.executingQuery executionDidStop]; + self.executingQuery = self.originalQuery; + + [service_ autorelease]; + service_ = nil; +} + +- (id)service { + return service_; +} + +- (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)setProperties:(NSDictionary *)dict { + [ticketProperties_ autorelease]; + ticketProperties_ = [dict mutableCopy]; +} + +- (NSDictionary *)properties { + // be sure the returned pointer has the life of the autorelease pool, + // in case self is released immediately + return [[ticketProperties_ retain] autorelease]; +} + +- (void)setProperty:(id)obj forKey:(NSString *)key { + if (obj == nil) { + // user passed in nil, so delete the property + [ticketProperties_ removeObjectForKey:key]; + } else { + // be sure the property dictionary exists + if (ticketProperties_ == nil) { + // call setProperties so observers are notified + [self setProperties:[NSDictionary dictionary]]; + } + [ticketProperties_ setObject:obj forKey:key]; + } +} + +- (id)propertyForKey:(NSString *)key { + id obj = [ticketProperties_ objectForKey:key]; + + // be sure the returned pointer has the life of the autorelease pool, + // in case self is released immediately + return [[obj retain] autorelease]; +} + +- (NSDictionary *)surrogates { + return surrogates_; +} + +- (void)setSurrogates:(NSDictionary *)dict { + [surrogates_ autorelease]; + surrogates_ = [dict retain]; +} + +- (SEL)uploadProgressSelector { + return uploadProgressSelector_; +} + +- (void)setUploadProgressSelector:(SEL)progressSelector { + uploadProgressSelector_ = progressSelector; + + // if the user is turning on the progress selector in the ticket after the + // ticket's fetcher has been created, we need to give the fetcher our sentData + // callback. + // + // The progress monitor must be set in the service prior to creation of the + // ticket on 10.4 and iPhone 2.0, since on those systems the upload data must + // be wrapped with a ProgressMonitorInputStream prior to the creation of the + // fetcher. + if (progressSelector != NULL) { + SEL sentDataSel = @selector(objectFetcher:didSendBytes:totalBytesSent:totalBytesExpectedToSend:); + [[self objectFetcher] setSentDataSelector:sentDataSel]; + } +} + +#if NS_BLOCKS_AVAILABLE +- (void)setUploadProgressBlock:(GTLServiceUploadProgressBlock)block { + [uploadProgressBlock_ autorelease]; + uploadProgressBlock_ = [block copy]; + + if (uploadProgressBlock_) { + // As above, we need the fetcher to call us back when bytes are sent. + SEL sentDataSel = @selector(objectFetcher:didSendBytes:totalBytesSent:totalBytesExpectedToSend:); + [[self objectFetcher] setSentDataSelector:sentDataSel]; + } +} + +- (GTLServiceUploadProgressBlock)uploadProgressBlock { + return uploadProgressBlock_; +} +#endif + +- (NSInteger)statusCode { + return [objectFetcher_ statusCode]; +} + +- (GTLQuery *)queryForRequestID:(NSString *)requestID { + id queryObj = self.executingQuery; + if ([queryObj isBatchQuery]) { + GTLBatchQuery *batch = (GTLBatchQuery *)queryObj; + GTLQuery *result = [batch queryForRequestID:requestID]; + return result; + } else { + GTL_DEBUG_ASSERT(0, @"just use ticket.executingQuery"); + return nil; + } +} + +@end diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLTargetNamespace.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLTargetNamespace.h new file mode 100644 index 00000000..9e08a9e4 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLTargetNamespace.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLUploadParameters.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUploadParameters.h new file mode 100644 index 00000000..6b6c20ba --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUploadParameters.h @@ -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 + +#import "GTLDefines.h" + +@interface GTLUploadParameters : NSObject { + @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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLUploadParameters.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUploadParameters.m new file mode 100644 index 00000000..1a668a9d --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUploadParameters.m @@ -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 + +#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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLUtilities.h b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUtilities.h new file mode 100644 index 00000000..1694df61 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUtilities.h @@ -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 + +#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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTL/GTLUtilities.m b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUtilities.m new file mode 100644 index 00000000..71b7ee8a --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTL/GTLUtilities.m @@ -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 + +@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)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; +} diff --git a/External/google-plus-ios-sdk/OpenSource/GTMDefines.h b/External/google-plus-ios-sdk/OpenSource/GTMDefines.h new file mode 100644 index 00000000..59a1723c --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMDefines.h @@ -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 +#include + +#if TARGET_OS_IPHONE +#include +#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__ diff --git a/External/google-plus-ios-sdk/OpenSource/GTMGarbageCollection.h b/External/google-plus-ios-sdk/OpenSource/GTMGarbageCollection.h new file mode 100644 index 00000000..93d4efab --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMGarbageCollection.h @@ -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 + +#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]) + diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetchHistory.h b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetchHistory.h new file mode 100644 index 00000000..96018f5d --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetchHistory.h @@ -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 + +#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 { + @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 { + @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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetchHistory.m b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetchHistory.m new file mode 100644 index 00000000..7bf0684a --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetchHistory.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcher.h b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcher.h new file mode 100644 index 00000000..dedc4c01 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcher.h @@ -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 +// +// +// 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 + +#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 +// 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 +// 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 )cookieStorage; +- (void)updateFetchHistoryWithRequest:(NSURLRequest *)request + response:(NSURLResponse *)response + downloadedData:(NSData *)downloadedData; +- (void)removeCachedDataForRequest:(NSURLRequest *)request; +@end + +@protocol GTMHTTPFetcherServiceProtocol +// 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 +@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 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 fetchHistory_; // if supplied by the caller, used for Last-Modified-Since checks and cookies + NSInteger cookieStorageMethod_; // constant from above + id cookieStorage_; + + id authorizer_; + + // the service object that created and monitors this fetcher, if any + id 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 )staticCookieStorage; + +// Object to add authorization to the request, if needed +@property (retain) id authorizer; + +// The service object that created and monitors this fetcher, if any +@property (retain) id 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 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcher.m b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcher.m new file mode 100644 index 00000000..3a448c6c --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcher.m @@ -0,0 +1,1741 @@ +/* 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.m +// + +#define GTMHTTPFETCHER_DEFINE_GLOBALS 1 + +#import "GTMHTTPFetcher.h" + +#if GTM_BACKGROUND_FETCHING +#import +#endif + +static id gGTMFetcherStaticCookieStorage = nil; +static Class gGTMFetcherConnectionClass = nil; + +// the default max retry interview is 10 minutes for uploads (POST/PUT/PATCH), +// 1 minute for downloads +const NSTimeInterval kUnsetMaxRetryInterval = -1; +const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0; +const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.; + +// +// GTMHTTPFetcher +// + +@interface GTMHTTPFetcher () + +@property (copy) NSString *temporaryDownloadPath; +@property (retain) id cookieStorage; +@property (readwrite, retain) NSData *downloadedData; +#if NS_BLOCKS_AVAILABLE +@property (copy) void (^completionBlock)(NSData *, NSError *); +#endif + +- (BOOL)beginFetchMayDelay:(BOOL)mayDelay + mayAuthorize:(BOOL)mayAuthorize; +- (void)failToBeginFetchWithError:(NSError *)error; + +#if GTM_BACKGROUND_FETCHING +- (void)endBackgroundTask; +- (void)backgroundFetchExpired; +#endif + +- (BOOL)authorizeRequest; +- (void)authorizer:(id )auth + request:(NSMutableURLRequest *)request + finishedWithError:(NSError *)error; + +- (NSString *)createTempDownloadFilePathForPath:(NSString *)targetPath; +- (NSFileManager *)fileManager; +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks; +- (BOOL)shouldReleaseCallbacksUponCompletion; + +- (void)addCookiesToRequest:(NSMutableURLRequest *)request; +- (void)handleCookiesForResponse:(NSURLResponse *)response; + +- (void)logNowWithError:(NSError *)error; + +- (void)invokeFetchCallbacksWithData:(NSData *)data + error:(NSError *)error; +- (void)invokeFetchCallback:(SEL)sel + target:(id)target + data:(NSData *)data + error:(NSError *)error; +- (void)releaseCallbacks; + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; + +- (BOOL)shouldRetryNowForStatus:(NSInteger)status error:(NSError *)error; +- (void)destroyRetryTimer; +- (void)beginRetryTimer; +- (void)primeRetryTimerWithNewTimeInterval:(NSTimeInterval)secs; +- (void)sendStopNotificationIfNeeded; +- (void)retryFetch; +- (void)retryTimerFired:(NSTimer *)timer; +@end + +@interface GTMHTTPFetcher (GTMHTTPFetcherLoggingInternal) +- (void)setupStreamLogging; +- (void)logFetchWithError:(NSError *)error; +@end + +@implementation GTMHTTPFetcher + ++ (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request { + return [[[[self class] alloc] initWithRequest:request] autorelease]; +} + ++ (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL { + return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; +} + ++ (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString { + return [self fetcherWithURL:[NSURL URLWithString:requestURLString]]; +} + ++ (void)initialize { + // note that initialize is guaranteed by the runtime to be called in a + // thread-safe manner + if (!gGTMFetcherStaticCookieStorage) { + Class cookieStorageClass = NSClassFromString(@"GTMCookieStorage"); + if (cookieStorageClass) { + gGTMFetcherStaticCookieStorage = [[cookieStorageClass alloc] init]; + } + } +} + +- (id)init { + return [self initWithRequest:nil]; +} + +- (id)initWithRequest:(NSURLRequest *)request { + self = [super init]; + if (self) { + request_ = [request mutableCopy]; + + if (gGTMFetcherStaticCookieStorage != nil) { + // the user has compiled with the cookie storage class available; + // default to static cookie storage, so our cookies are independent + // of the cookies of other apps + [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic]; + } else { + // default to system default cookie storage + [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodSystemDefault]; + } + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + // disallow use of fetchers in a copy property + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %p (%@)", + [self class], self, [self.mutableRequest URL]]; +} + +#if !GTM_IPHONE +- (void)finalize { + [self stopFetchReleasingCallbacks:YES]; // releases connection_, destroys timers + [super finalize]; +} +#endif + +- (void)dealloc { +#if DEBUG + NSAssert(!isStopNotificationNeeded_, + @"unbalanced fetcher notification for %@", [request_ URL]); +#endif + + // Note: if a connection or a retry timer was pending, then this instance + // would be retained by those so it wouldn't be getting dealloc'd, + // hence we don't need to stopFetch here + [request_ release]; + [connection_ release]; + [downloadedData_ release]; + [downloadPath_ release]; + [temporaryDownloadPath_ release]; + [downloadFileHandle_ release]; + [credential_ release]; + [proxyCredential_ release]; + [postData_ release]; + [postStream_ release]; + [loggedStreamData_ release]; + [response_ release]; +#if NS_BLOCKS_AVAILABLE + [completionBlock_ release]; + [receivedDataBlock_ release]; + [sentDataBlock_ release]; + [retryBlock_ release]; +#endif + [userData_ release]; + [properties_ release]; + [runLoopModes_ release]; + [fetchHistory_ release]; + [cookieStorage_ release]; + [authorizer_ release]; + [service_ release]; + [serviceHost_ release]; + [thread_ release]; + [retryTimer_ release]; + [comment_ release]; + [log_ release]; + + [super dealloc]; +} + +#pragma mark - + +// Begin fetching the URL (or begin a retry fetch). The delegate is retained +// for the duration of the fetch connection. + +- (BOOL)beginFetchWithDelegate:(id)delegate + didFinishSelector:(SEL)finishedSelector { + GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector, @encode(GTMHTTPFetcher *), @encode(NSData *), @encode(NSError *), 0); + GTMAssertSelectorNilOrImplementedWithArgs(delegate, receivedDataSel_, @encode(GTMHTTPFetcher *), @encode(NSData *), 0); + GTMAssertSelectorNilOrImplementedWithArgs(delegate, retrySel_, @encode(GTMHTTPFetcher *), @encode(BOOL), @encode(NSError *), 0); + + // We'll retain the delegate only during the outstanding connection (similar + // to what Cocoa does with performSelectorOnMainThread:) and during + // authorization or delays, since the app would crash + // if the delegate was released before the fetch calls back + [self setDelegate:delegate]; + finishedSel_ = finishedSelector; + + return [self beginFetchMayDelay:YES + mayAuthorize:YES]; +} + +- (BOOL)beginFetchMayDelay:(BOOL)mayDelay + mayAuthorize:(BOOL)mayAuthorize { + // This is the internal entry point for re-starting fetches + NSError *error = nil; + + if (connection_ != nil) { + NSAssert1(connection_ != nil, @"fetch object %@ being reused; this should never happen", self); + goto CannotBeginFetch; + } + + if (request_ == nil) { + NSAssert(request_ != nil, @"beginFetchWithDelegate requires a request"); + goto CannotBeginFetch; + } + + self.downloadedData = nil; + downloadedLength_ = 0; + + if (mayDelay && service_) { + BOOL shouldFetchNow = [service_ fetcherShouldBeginFetching:self]; + if (!shouldFetchNow) { + // the fetch is deferred, but will happen later + return YES; + } + } + + NSString *effectiveHTTPMethod = [request_ valueForHTTPHeaderField:@"X-HTTP-Method-Override"]; + if (effectiveHTTPMethod == nil) { + effectiveHTTPMethod = [request_ HTTPMethod]; + } + BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil + || [effectiveHTTPMethod isEqual:@"GET"]); + + if (postData_ || postStream_) { + if (isEffectiveHTTPGet) { + [request_ setHTTPMethod:@"POST"]; + isEffectiveHTTPGet = NO; + } + + if (postData_) { + [request_ setHTTPBody:postData_]; + } else { + if ([self respondsToSelector:@selector(setupStreamLogging)]) { + [self performSelector:@selector(setupStreamLogging)]; + } + + [request_ setHTTPBodyStream:postStream_]; + } + } + + // We authorize after setting up the http method and body in the request + // because OAuth 1 may need to sign the request body + if (mayAuthorize && authorizer_) { + BOOL isAuthorized = [authorizer_ isAuthorizedRequest:request_]; + if (!isAuthorized) { + // authorization needed + return [self authorizeRequest]; + } + } + + [fetchHistory_ updateRequest:request_ isHTTPGet:isEffectiveHTTPGet]; + + // set the default upload or download retry interval, if necessary + if (isRetryEnabled_ + && maxRetryInterval_ <= kUnsetMaxRetryInterval) { + if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) { + [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval]; + } else { + [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval]; + } + } + + [self addCookiesToRequest:request_]; + + if (downloadPath_ != nil) { + // downloading to a path, so create a temporary file and a file handle for + // downloading + NSString *tempPath = [self createTempDownloadFilePathForPath:downloadPath_]; + + BOOL didCreate = [[NSData data] writeToFile:tempPath + options:0 + error:&error]; + if (!didCreate) goto CannotBeginFetch; + + [self setTemporaryDownloadPath:tempPath]; + + NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:tempPath]; + if (fh == nil) goto CannotBeginFetch; + + [self setDownloadFileHandle:fh]; + } + + // finally, start the connection + + Class connectionClass = [[self class] connectionClass]; + + NSArray *runLoopModes = nil; + + // use the connection-specific run loop modes, if they were provided, + // or else use the GTMHTTPFetcher default run loop modes, if any + if (runLoopModes_) { + runLoopModes = runLoopModes_; + } + + if ([runLoopModes count] == 0) { + + // if no run loop modes were specified, then we'll start the connection + // on the current run loop in the current mode + connection_ = [[connectionClass connectionWithRequest:request_ + delegate:self] retain]; + } else { + + // schedule on current run loop in the specified modes + connection_ = [[connectionClass alloc] initWithRequest:request_ + delegate:self + startImmediately:NO]; + for (NSString *mode in runLoopModes) { + [connection_ scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:mode]; + } + [connection_ start]; + } + hasConnectionEnded_ = NO; + + if (!connection_) { + NSAssert(connection_ != nil, @"beginFetchWithDelegate could not create a connection"); + goto CannotBeginFetch; + } + + if (downloadFileHandle_ != nil) { + // downloading to a file, so downloadedData_ remains nil + } else { + self.downloadedData = [NSMutableData data]; + } + +#if GTM_BACKGROUND_FETCHING + backgroundTaskIdentifer_ = 0; // UIBackgroundTaskInvalid is 0 on iOS 4 + if (shouldFetchInBackground_) { + // For iOS 3 compatibility, ensure that UIApp supports backgrounding + UIApplication *app = [UIApplication sharedApplication]; + if ([app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) { + // Tell UIApplication that we want to continue even when the app is in the + // background + NSThread *thread = [NSThread currentThread]; + backgroundTaskIdentifer_ = [app beginBackgroundTaskWithExpirationHandler:^{ + // Callback - this block is always invoked by UIApplication on the main + // thread, but we want to run the user's callbacks on the thread used + // to start the fetch + [self performSelector:@selector(backgroundFetchExpired) + onThread:thread + withObject:nil + waitUntilDone:YES]; + }]; + } + } +#endif + + // once connection_ is non-nil we can send the start notification + isStopNotificationNeeded_ = YES; + NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter]; + [defaultNC postNotificationName:kGTMHTTPFetcherStartedNotification + object:self]; + return YES; + +CannotBeginFetch: + [self failToBeginFetchWithError:error]; + return NO; +} + +- (void)failToBeginFetchWithError:(NSError *)error { + if (error == nil) { + error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain + code:kGTMHTTPFetcherErrorDownloadFailed + userInfo:nil]; + } + + [[self retain] autorelease]; // In case the callback releases us + + [self invokeFetchCallbacksWithData:nil + error:error]; + + [self releaseCallbacks]; + + [service_ fetcherDidStop:self]; + + self.authorizer = nil; + + if (temporaryDownloadPath_) { + [[self fileManager] removeItemAtPath:temporaryDownloadPath_ + error:NULL]; + self.temporaryDownloadPath = nil; + } +} + +#if GTM_BACKGROUND_FETCHING +- (void)backgroundFetchExpired { + // On background expiration, we stop the fetch and invoke the callbacks + NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain + code:kGTMHTTPFetcherErrorBackgroundExpiration + userInfo:nil]; + [self invokeFetchCallbacksWithData:nil + error:error]; + + // Stopping the fetch here will indirectly call endBackgroundTask + [self stopFetchReleasingCallbacks:NO]; + + [self releaseCallbacks]; + self.authorizer = nil; +} + +- (void)endBackgroundTask { + // Whenever the connection stops or background execution expires, + // we need to tell UIApplication we're done + if (backgroundTaskIdentifer_) { + // If backgroundTaskIdentifer_ is non-zero, we know we're on iOS 4 + UIApplication *app = [UIApplication sharedApplication]; + [app endBackgroundTask:backgroundTaskIdentifer_]; + + backgroundTaskIdentifer_ = 0; + } +} +#endif + +- (BOOL)authorizeRequest { + id authorizer = self.authorizer; + SEL asyncAuthSel = @selector(authorizeRequest:delegate:didFinishSelector:); + if ([authorizer respondsToSelector:asyncAuthSel]) { + SEL callbackSel = @selector(authorizer:request:finishedWithError:); + [authorizer authorizeRequest:request_ + delegate:self + didFinishSelector:callbackSel]; + return YES; + } else { + NSAssert(authorizer == nil, @"invalid authorizer for fetch"); + + // no authorizing possible, and authorizing happens only after any delay; + // just begin fetching + return [self beginFetchMayDelay:NO + mayAuthorize:NO]; + } +} + +- (void)authorizer:(id )auth + request:(NSMutableURLRequest *)request + finishedWithError:(NSError *)error { + if (error != nil) { + // we can't fetch without authorization + [self failToBeginFetchWithError:error]; + } else { + [self beginFetchMayDelay:NO + mayAuthorize:NO]; + } +} + +#if NS_BLOCKS_AVAILABLE +- (BOOL)beginFetchWithCompletionHandler:(void (^)(NSData *data, NSError *error))handler { + self.completionBlock = handler; + + // the user may have called setDelegate: earlier if they want to use other + // delegate-style callbacks during the fetch; otherwise, the delegate is nil, + // which is fine + return [self beginFetchWithDelegate:[self delegate] + didFinishSelector:nil]; +} +#endif + +- (NSString *)createTempDownloadFilePathForPath:(NSString *)targetPath { + NSString *tempDir = nil; + +#if (!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060)) + // find an appropriate directory for the download, ideally on the same disk + // as the final target location so the temporary file won't have to be moved + // to a different disk + // + // available in SDKs for 10.6 and iOS 4 + // + // Oct 2011: We previously also used URLForDirectory for + // (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 40000)) + // but that is returning a non-temporary directory for iOS, unfortunately + + SEL sel = @selector(URLForDirectory:inDomain:appropriateForURL:create:error:); + if ([NSFileManager instancesRespondToSelector:sel]) { + NSError *error = nil; + NSURL *targetURL = [NSURL fileURLWithPath:targetPath]; + NSFileManager *fileMgr = [self fileManager]; + + NSURL *tempDirURL = [fileMgr URLForDirectory:NSItemReplacementDirectory + inDomain:NSUserDomainMask + appropriateForURL:targetURL + create:YES + error:&error]; + tempDir = [tempDirURL path]; + } +#endif + + if (tempDir == nil) { + tempDir = NSTemporaryDirectory(); + } + + static unsigned int counter = 0; + NSString *name = [NSString stringWithFormat:@"gtmhttpfetcher_%u_%u", + ++counter, (unsigned int) arc4random()]; + NSString *result = [tempDir stringByAppendingPathComponent:name]; + return result; +} + +- (void)addCookiesToRequest:(NSMutableURLRequest *)request { + // get cookies for this URL from our storage array, if + // we have a storage array + if (cookieStorageMethod_ != kGTMHTTPFetcherCookieStorageMethodSystemDefault + && cookieStorageMethod_ != kGTMHTTPFetcherCookieStorageMethodNone) { + + NSArray *cookies = [cookieStorage_ cookiesForURL:[request URL]]; + if ([cookies count] > 0) { + + NSDictionary *headerFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies]; + NSString *cookieHeader = [headerFields objectForKey:@"Cookie"]; // key used in header dictionary + if (cookieHeader) { + [request addValue:cookieHeader forHTTPHeaderField:@"Cookie"]; // header name + } + } + } +} + +// Returns YES if this is in the process of fetching a URL, or waiting to +// retry, or waiting for authorization, or waiting to be issued by the +// service object +- (BOOL)isFetching { + if (connection_ != nil || retryTimer_ != nil) return YES; + + BOOL isAuthorizing = [authorizer_ isAuthorizingRequest:request_]; + if (isAuthorizing) return YES; + + BOOL isDelayed = [service_ isDelayingFetcher:self]; + return isDelayed; +} + +// Returns the status code set in connection:didReceiveResponse: +- (NSInteger)statusCode { + + NSInteger statusCode; + + if (response_ != nil + && [response_ respondsToSelector:@selector(statusCode)]) { + + statusCode = [(NSHTTPURLResponse *)response_ statusCode]; + } else { + // Default to zero, in hopes of hinting "Unknown" (we can't be + // sure that things are OK enough to use 200). + statusCode = 0; + } + return statusCode; +} + +- (NSDictionary *)responseHeaders { + if (response_ != nil + && [response_ respondsToSelector:@selector(allHeaderFields)]) { + + NSDictionary *headers = [(NSHTTPURLResponse *)response_ allHeaderFields]; + return headers; + } + return nil; +} + +- (void)releaseCallbacks { + [delegate_ autorelease]; + delegate_ = nil; + +#if NS_BLOCKS_AVAILABLE + self.completionBlock = nil; + self.sentDataBlock = nil; + self.receivedDataBlock = nil; + self.retryBlock = nil; +#endif +} + +// Cancel the fetch of the URL that's currently in progress. +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks { + // if the connection or the retry timer is all that's retaining the fetcher, + // we want to be sure this instance survives stopping at least long enough for + // the stack to unwind + [[self retain] autorelease]; + + [self destroyRetryTimer]; + + if (connection_) { + // in case cancelling the connection calls this recursively, we want + // to ensure that we'll only release the connection and delegate once, + // so first set connection_ to nil + NSURLConnection* oldConnection = connection_; + connection_ = nil; + + if (!hasConnectionEnded_) { + [oldConnection cancel]; + } + + // this may be called in a callback from the connection, so use autorelease + [oldConnection autorelease]; + } + + // send the stopped notification + [self sendStopNotificationIfNeeded]; + + [authorizer_ stopAuthorization]; + + if (shouldReleaseCallbacks) { + [self releaseCallbacks]; + + self.authorizer = nil; + } + + [service_ fetcherDidStop:self]; + + if (temporaryDownloadPath_) { + [[NSFileManager defaultManager] removeItemAtPath:temporaryDownloadPath_ + error:NULL]; + self.temporaryDownloadPath = nil; + } + +#if GTM_BACKGROUND_FETCHING + [self endBackgroundTask]; +#endif +} + +// external stop method +- (void)stopFetching { + [self stopFetchReleasingCallbacks:YES]; +} + +- (void)sendStopNotificationIfNeeded { + if (isStopNotificationNeeded_) { + isStopNotificationNeeded_ = NO; + + NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter]; + [defaultNC postNotificationName:kGTMHTTPFetcherStoppedNotification + object:self]; + } +} + +- (void)retryFetch { + + [self stopFetchReleasingCallbacks:NO]; + + [self beginFetchWithDelegate:delegate_ + didFinishSelector:finishedSel_]; +} + +- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { + NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + + // loop until the callbacks have been called and released, and until + // the connection is no longer pending, or until the timeout has expired + while ((!hasConnectionEnded_ +#if NS_BLOCKS_AVAILABLE + || completionBlock_ != nil +#endif + || delegate_ != nil) + && [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]; + } +} + +- (NSFileManager *)fileManager { + // use a temporary instance of NSFileManager for thread-safety + return [[[NSFileManager alloc] init] autorelease]; +} + +#pragma mark NSURLConnection Delegate Methods + +// +// NSURLConnection Delegate Methods +// + +// This method just says "follow all redirects", which _should_ be the default behavior, +// According to file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Conceptual/URLLoadingSystem +// but the redirects were not being followed until I added this method. May be +// a bug in the NSURLConnection code, or the documentation. +// +// In OS X 10.4.8 and earlier, the redirect request doesn't +// get the original's headers and body. This causes POSTs to fail. +// So we construct a new request, a copy of the original, with overrides from the +// redirect. +// +// Docs say that if redirectResponse is nil, just return the redirectRequest. + +- (NSURLRequest *)connection:(NSURLConnection *)connection + willSendRequest:(NSURLRequest *)redirectRequest + redirectResponse:(NSURLResponse *)redirectResponse { + + if (redirectRequest && redirectResponse) { + // save cookies from the response + [self handleCookiesForResponse:redirectResponse]; + + NSMutableURLRequest *newRequest = [[request_ mutableCopy] autorelease]; + // copy the URL + NSURL *redirectURL = [redirectRequest URL]; + NSURL *url = [newRequest URL]; + + // disallow scheme changes (say, from https to http) + NSString *redirectScheme = [url scheme]; + NSString *newScheme = [redirectURL scheme]; + NSString *newResourceSpecifier = [redirectURL resourceSpecifier]; + + if ([redirectScheme caseInsensitiveCompare:@"http"] == NSOrderedSame + && newScheme != nil + && [newScheme caseInsensitiveCompare:@"https"] == NSOrderedSame) { + + // allow the change from http to https + redirectScheme = newScheme; + } + + NSString *newUrlString = [NSString stringWithFormat:@"%@:%@", + redirectScheme, newResourceSpecifier]; + + NSURL *newURL = [NSURL URLWithString:newUrlString]; + [newRequest setURL:newURL]; + + // any headers in the redirect override headers in the original. + NSDictionary *redirectHeaders = [redirectRequest allHTTPHeaderFields]; + for (NSString *key in redirectHeaders) { + NSString *value = [redirectHeaders objectForKey:key]; + [newRequest setValue:value forHTTPHeaderField:key]; + } + + [self addCookiesToRequest:newRequest]; + + redirectRequest = newRequest; + + // log the response we just received + [self setResponse:redirectResponse]; + [self logNowWithError:nil]; + + // update the request for future logging + NSMutableURLRequest *mutable = [[redirectRequest mutableCopy] autorelease]; + [self setMutableRequest:mutable]; +} + return redirectRequest; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + // this method is called when the server has determined that it + // has enough information to create the NSURLResponse + // it can be called multiple times, for example in the case of a + // redirect, so each time we reset the data. + [downloadedData_ setLength:0]; + [downloadFileHandle_ truncateFileAtOffset:0]; + downloadedLength_ = 0; + + [self setResponse:response]; + + // save cookies from the response + [self handleCookiesForResponse:response]; +} + + +// handleCookiesForResponse: handles storage of cookies for responses passed to +// connection:willSendRequest:redirectResponse: and connection:didReceiveResponse: +- (void)handleCookiesForResponse:(NSURLResponse *)response { + + if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodSystemDefault + || cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodNone) { + + // do nothing special for NSURLConnection's default storage mechanism + // or when we're ignoring cookies + + } else if ([response respondsToSelector:@selector(allHeaderFields)]) { + + // grab the cookies from the header as NSHTTPCookies and store them either + // into our static array or into the fetchHistory + + NSDictionary *responseHeaderFields = [(NSHTTPURLResponse *)response allHeaderFields]; + if (responseHeaderFields) { + + NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseHeaderFields + forURL:[response URL]]; + if ([cookies count] > 0) { + [cookieStorage_ setCookies:cookies]; + } + } + } +} + +-(void)connection:(NSURLConnection *)connection + didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + + if ([challenge previousFailureCount] <= 2) { + + NSURLCredential *credential = credential_; + + if ([[challenge protectionSpace] isProxy] && proxyCredential_ != nil) { + credential = proxyCredential_; + } + + // Here, if credential is still nil, then we *could* try to get it from + // NSURLCredentialStorage's defaultCredentialForProtectionSpace:. + // We don't, because we're assuming: + // + // - for server credentials, we only want ones supplied by the program + // calling http fetcher + // - for proxy credentials, if one were necessary and available in the + // keychain, it would've been found automatically by NSURLConnection + // and this challenge delegate method never would've been called + // anyway + + if (credential) { + // try the credential + [[challenge sender] useCredential:credential + forAuthenticationChallenge:challenge]; + return; + } + } + + // If we don't have credentials, or we've already failed auth 3x, + // report the error, putting the challenge as a value in the userInfo + // dictionary +#if DEBUG + NSAssert(!isCancellingChallenge_, @"isCancellingChallenge_ unexpected"); +#endif + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:challenge + forKey:kGTMHTTPFetcherErrorChallengeKey]; + NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain + code:kGTMHTTPFetcherErrorAuthenticationChallengeFailed + userInfo:userInfo]; + + // cancelAuthenticationChallenge seems to indirectly call + // connection:didFailWithError: now, though that isn't documented + // + // we'll use an ivar to make the indirect invocation of the + // delegate method do nothing + isCancellingChallenge_ = YES; + [[challenge sender] cancelAuthenticationChallenge:challenge]; + isCancellingChallenge_ = NO; + + [self connection:connection didFailWithError:error]; +} + +- (void)invokeFetchCallbacksWithData:(NSData *)data + error:(NSError *)error { + [[self retain] autorelease]; // In case the callback releases us + + [self invokeFetchCallback:finishedSel_ + target:delegate_ + data:data + error:error]; + +#if NS_BLOCKS_AVAILABLE + if (completionBlock_) { + completionBlock_(data, error); + } +#endif +} + +- (void)invokeFetchCallback:(SEL)sel + target:(id)target + data:(NSData *)data + error:(NSError *)error { + // This method is available to subclasses which may provide a customized + // target pointer + if (target && sel) { + NSMethodSignature *sig = [target methodSignatureForSelector:sel]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocation setTarget:target]; + [invocation setArgument:&self atIndex:2]; + [invocation setArgument:&data atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + } +} + +- (void)invokeSentDataCallback:(SEL)sel + target:(id)target + didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten + totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { + if (target && sel) { + NSMethodSignature *sig = [target methodSignatureForSelector:sel]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocation setTarget:target]; + [invocation setArgument:&self atIndex:2]; + [invocation setArgument:&bytesWritten atIndex:3]; + [invocation setArgument:&totalBytesWritten atIndex:4]; + [invocation setArgument:&totalBytesExpectedToWrite atIndex:5]; + [invocation invoke]; + } +} + +- (BOOL)invokeRetryCallback:(SEL)sel + target:(id)target + willRetry:(BOOL)willRetry + error:(NSError *)error { + if (target && sel) { + NSMethodSignature *sig = [target methodSignatureForSelector:sel]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocation setTarget:target]; + [invocation setArgument:&self atIndex:2]; + [invocation setArgument:&willRetry atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + + [invocation getReturnValue:&willRetry]; + } + return willRetry; +} + +- (void)connection:(NSURLConnection *)connection + didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten +totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { + + SEL sel = [self sentDataSelector]; + [self invokeSentDataCallback:sel + target:delegate_ + didSendBodyData:bytesWritten + totalBytesWritten:totalBytesWritten + totalBytesExpectedToWrite:totalBytesExpectedToWrite]; + +#if NS_BLOCKS_AVAILABLE + if (sentDataBlock_) { + sentDataBlock_(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + } +#endif +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { +#if DEBUG + // the download file handle should be set before the fetch is started, not + // after + NSAssert((downloadFileHandle_ == nil) != (downloadedData_ == nil), + @"received data accumulates as NSData or NSFileHandle, not both"); +#endif + + if (downloadFileHandle_ != nil) { + // append to file + @try { + [downloadFileHandle_ writeData:data]; + + downloadedLength_ = [downloadFileHandle_ offsetInFile]; + } + @catch (NSException *exc) { + // couldn't write to file, probably due to a full disk + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[exc reason] + forKey:NSLocalizedDescriptionKey]; + NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain + code:kGTMHTTPFetcherErrorFileHandleException + userInfo:userInfo]; + [self connection:connection didFailWithError:error]; + return; + } + } else { + // append to mutable data + [downloadedData_ appendData:data]; + + downloadedLength_ = [downloadedData_ length]; + } + + if (receivedDataSel_) { + [delegate_ performSelector:receivedDataSel_ + withObject:self + withObject:downloadedData_]; + } + +#if NS_BLOCKS_AVAILABLE + if (receivedDataBlock_) { + receivedDataBlock_(downloadedData_); + } +#endif +} + + +// For error 304's ("Not Modified") where we've cached the data, return +// status 200 ("OK") to the caller (but leave the fetcher status as 304) +// and copy the cached data. +// +// For other errors or if there's no cached data, just return the actual status. +- (NSInteger)statusAfterHandlingNotModifiedError { + + NSInteger status = [self statusCode]; + if (status == kGTMHTTPFetcherStatusNotModified + && [fetchHistory_ shouldCacheETaggedData]) { + + NSData *cachedData = [fetchHistory_ cachedDataForRequest:request_]; + if (cachedData) { + // forge the status to pass on to the delegate + status = 200; + + // copy our stored data + if (downloadFileHandle_ != nil) { + @try { + // Downloading to a file handle won't save to the cache (the data is + // likely inappropriately large for caching), but will still read from + // the cache, on the unlikely chance that the response was Not Modified + // and the URL response was indeed present in the cache. + [downloadFileHandle_ truncateFileAtOffset:0]; + [downloadFileHandle_ writeData:cachedData]; + downloadedLength_ = [downloadFileHandle_ offsetInFile]; + } + @catch (NSException *) { + // Failed to write data, likely due to lack of disk space + status = kGTMHTTPFetcherErrorFileHandleException; + } + } else { + [downloadedData_ setData:cachedData]; + downloadedLength_ = [cachedData length]; + } + } + } + return status; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + // we no longer need to cancel the connection + hasConnectionEnded_ = YES; + + // skip caching ETagged results when the data is being saved to a file + if (downloadFileHandle_ == nil) { + [fetchHistory_ updateFetchHistoryWithRequest:request_ + response:response_ + downloadedData:downloadedData_]; + } else { + [fetchHistory_ removeCachedDataForRequest:request_]; + } + + [[self retain] autorelease]; // in case the callback releases us + + [self logNowWithError:nil]; + + NSInteger status = [self statusAfterHandlingNotModifiedError]; + + // we want to send the stop notification before calling the delegate's + // callback selector, since the callback selector may release all of + // the fetcher properties that the client is using to track the fetches + // + // We'll also stop now so that, to any observers watching the notifications, + // it doesn't look like our wait for a retry (which may be long, + // 30 seconds or more) is part of the network activity + [self sendStopNotificationIfNeeded]; + + BOOL shouldStopFetching = YES; + NSError *error = nil; + + if (status >= 0 && status < 300) { + // success + if (downloadPath_) { + // avoid deleting the downloaded file when the fetch stops + [downloadFileHandle_ closeFile]; + self.downloadFileHandle = nil; + + NSFileManager *fileMgr = [self fileManager]; + [fileMgr removeItemAtPath:downloadPath_ + error:NULL]; + + if ([fileMgr moveItemAtPath:temporaryDownloadPath_ + toPath:downloadPath_ + error:&error]) { + self.temporaryDownloadPath = nil; + } + } + } else { + // status over 300; retry or notify the delegate of failure + if ([self shouldRetryNowForStatus:status error:nil]) { + // retrying + [self beginRetryTimer]; + shouldStopFetching = NO; + } else { + NSDictionary *userInfo = nil; + if ([downloadedData_ length] > 0) { + userInfo = [NSDictionary dictionaryWithObject:downloadedData_ + forKey:kGTMHTTPFetcherStatusDataKey]; + } + error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain + code:status + userInfo:userInfo]; + } + } + + if (shouldStopFetching) { + // call the callbacks + [self invokeFetchCallbacksWithData:downloadedData_ + error:error]; + + BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion]; + [self stopFetchReleasingCallbacks:shouldRelease]; + } +} + +- (BOOL)shouldReleaseCallbacksUponCompletion { + // a subclass can override this to keep callbacks around after the + // connection has finished successfully + return YES; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + // prevent the failure callback from being called twice, since the stopFetch + // call below (either the explicit one at the end of this method, or the + // implicit one when the retry occurs) will release the delegate + if (connection_ == nil) return; + + // if this method was invoked indirectly by cancellation of an authentication + // challenge, defer this until it is called again with the proper error object + if (isCancellingChallenge_) return; + + // we no longer need to cancel the connection + hasConnectionEnded_ = YES; + + [self logNowWithError:error]; + + // see comment about sendStopNotificationIfNeeded + // in connectionDidFinishLoading: + [self sendStopNotificationIfNeeded]; + + if ([self shouldRetryNowForStatus:0 error:error]) { + + [self beginRetryTimer]; + + } else { + + [[self retain] autorelease]; // in case the callback releases us + + [self invokeFetchCallbacksWithData:nil + error:error]; + + [self stopFetchReleasingCallbacks:YES]; + } +} + +- (void)logNowWithError:(NSError *)error { + // if the logging category is available, then log the current request, + // response, data, and error + if ([self respondsToSelector:@selector(logFetchWithError:)]) { + [self performSelector:@selector(logFetchWithError:) withObject:error]; + } +} + +#pragma mark Retries + +- (BOOL)isRetryError:(NSError *)error { + + struct retryRecord { + NSString *const domain; + int code; + }; + + struct retryRecord retries[] = { + { kGTMHTTPFetcherStatusDomain, 408 }, // request timeout + { kGTMHTTPFetcherStatusDomain, 503 }, // service unavailable + { kGTMHTTPFetcherStatusDomain, 504 }, // request timeout + { NSURLErrorDomain, NSURLErrorTimedOut }, + { NSURLErrorDomain, NSURLErrorNetworkConnectionLost }, + { nil, 0 } + }; + + // NSError's isEqual always returns false for equal but distinct instances + // of NSError, so we have to compare the domain and code values explicitly + + for (int idx = 0; retries[idx].domain != nil; idx++) { + + if ([[error domain] isEqual:retries[idx].domain] + && [error code] == retries[idx].code) { + + return YES; + } + } + return NO; +} + + +// shouldRetryNowForStatus:error: returns YES if the user has enabled retries +// and the status or error is one that is suitable for retrying. "Suitable" +// means either the isRetryError:'s list contains the status or error, or the +// user's retrySelector: is present and returns YES when called, or the +// authorizer may be able to fix. +- (BOOL)shouldRetryNowForStatus:(NSInteger)status + error:(NSError *)error { + // Determine if a refreshed authorizer may avoid an authorization error + BOOL shouldRetryForAuthRefresh = NO; + BOOL isFirstAuthError = (authorizer_ != nil) + && !hasAttemptedAuthRefresh_ + && (status == kGTMHTTPFetcherStatusUnauthorized); // 401 + + if (isFirstAuthError) { + if ([authorizer_ respondsToSelector:@selector(primeForRefresh)]) { + BOOL hasPrimed = [authorizer_ primeForRefresh]; + if (hasPrimed) { + shouldRetryForAuthRefresh = YES; + hasAttemptedAuthRefresh_ = YES; + [request_ setValue:nil forHTTPHeaderField:@"Authorization"]; + } + } + } + + // Determine if we're doing exponential backoff retries + BOOL shouldDoIntervalRetry = [self isRetryEnabled] + && ([self nextRetryInterval] < [self maxRetryInterval]); + + BOOL willRetry = NO; + BOOL canRetry = shouldRetryForAuthRefresh || shouldDoIntervalRetry; + if (canRetry) { + // Check if this is a retryable error + if (error == nil) { + // Make an error for the status + NSDictionary *userInfo = nil; + if ([downloadedData_ length] > 0) { + userInfo = [NSDictionary dictionaryWithObject:downloadedData_ + forKey:kGTMHTTPFetcherStatusDataKey]; + } + error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain + code:status + userInfo:userInfo]; + } + + willRetry = shouldRetryForAuthRefresh || [self isRetryError:error]; + + // If the user has installed a retry callback, consult that + willRetry = [self invokeRetryCallback:retrySel_ + target:delegate_ + willRetry:willRetry + error:error]; +#if NS_BLOCKS_AVAILABLE + if (retryBlock_) { + willRetry = retryBlock_(willRetry, error); + } +#endif + } + return willRetry; +} + +- (void)beginRetryTimer { + + NSTimeInterval nextInterval = [self nextRetryInterval]; + NSTimeInterval maxInterval = [self maxRetryInterval]; + + NSTimeInterval newInterval = MIN(nextInterval, maxInterval); + + [self primeRetryTimerWithNewTimeInterval:newInterval]; +} + +- (void)primeRetryTimerWithNewTimeInterval:(NSTimeInterval)secs { + + [self destroyRetryTimer]; + + lastRetryInterval_ = secs; + + retryTimer_ = [NSTimer scheduledTimerWithTimeInterval:secs + target:self + selector:@selector(retryTimerFired:) + userInfo:nil + repeats:NO]; + [retryTimer_ retain]; + + NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter]; + [defaultNC postNotificationName:kGTMHTTPFetcherRetryDelayStartedNotification + object:self]; +} + +- (void)retryTimerFired:(NSTimer *)timer { + + [self destroyRetryTimer]; + + retryCount_++; + + [self retryFetch]; +} + +- (void)destroyRetryTimer { + if (retryTimer_) { + [retryTimer_ invalidate]; + [retryTimer_ autorelease]; + retryTimer_ = nil; + + NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter]; + [defaultNC postNotificationName:kGTMHTTPFetcherRetryDelayStoppedNotification + object:self]; + } +} + +- (NSUInteger)retryCount { + return retryCount_; +} + +- (NSTimeInterval)nextRetryInterval { + // the next wait interval is the factor (2.0) times the last interval, + // but never less than the minimum interval + NSTimeInterval secs = lastRetryInterval_ * retryFactor_; + secs = MIN(secs, maxRetryInterval_); + secs = MAX(secs, minRetryInterval_); + + return secs; +} + +- (BOOL)isRetryEnabled { + return isRetryEnabled_; +} + +- (void)setRetryEnabled:(BOOL)flag { + + if (flag && !isRetryEnabled_) { + // We defer initializing these until the user calls setRetryEnabled + // to avoid using the random number generator if it's not needed. + // However, this means min and max intervals for this fetcher are reset + // as a side effect of calling setRetryEnabled. + // + // make an initial retry interval random between 1.0 and 2.0 seconds + [self setMinRetryInterval:0.0]; + [self setMaxRetryInterval:kUnsetMaxRetryInterval]; + [self setRetryFactor:2.0]; + lastRetryInterval_ = 0.0; + } + isRetryEnabled_ = flag; +}; + +- (NSTimeInterval)maxRetryInterval { + return maxRetryInterval_; +} + +- (void)setMaxRetryInterval:(NSTimeInterval)secs { + if (secs > 0) { + maxRetryInterval_ = secs; + } else { + maxRetryInterval_ = kUnsetMaxRetryInterval; + } +} + +- (double)minRetryInterval { + return minRetryInterval_; +} + +- (void)setMinRetryInterval:(NSTimeInterval)secs { + if (secs > 0) { + minRetryInterval_ = secs; + } else { + // set min interval to a random value between 1.0 and 2.0 seconds + // so that if multiple clients start retrying at the same time, they'll + // repeat at different times and avoid overloading the server + minRetryInterval_ = 1.0 + ((double)(arc4random() & 0x0FFFF) / (double) 0x0FFFF); + } +} + +#pragma mark Getters and Setters + +@dynamic cookieStorageMethod, + retryEnabled, + maxRetryInterval, + minRetryInterval, + retryCount, + nextRetryInterval, + statusCode, + responseHeaders, + fetchHistory, + userData, + properties; + +@synthesize mutableRequest = request_, + credential = credential_, + proxyCredential = proxyCredential_, + postData = postData_, + postStream = postStream_, + delegate = delegate_, + authorizer = authorizer_, + service = service_, + serviceHost = serviceHost_, + servicePriority = servicePriority_, + thread = thread_, + sentDataSelector = sentDataSel_, + receivedDataSelector = receivedDataSel_, + retrySelector = retrySel_, + retryFactor = retryFactor_, + response = response_, + downloadedLength = downloadedLength_, + downloadedData = downloadedData_, + downloadPath = downloadPath_, + temporaryDownloadPath = temporaryDownloadPath_, + downloadFileHandle = downloadFileHandle_, + runLoopModes = runLoopModes_, + comment = comment_, + log = log_, + cookieStorage = cookieStorage_; + +#if NS_BLOCKS_AVAILABLE +@synthesize completionBlock = completionBlock_, + sentDataBlock = sentDataBlock_, + receivedDataBlock = receivedDataBlock_, + retryBlock = retryBlock_; +#endif + +@synthesize shouldFetchInBackground = shouldFetchInBackground_; + +- (NSInteger)cookieStorageMethod { + return cookieStorageMethod_; +} + +- (void)setCookieStorageMethod:(NSInteger)method { + + cookieStorageMethod_ = method; + + if (method == kGTMHTTPFetcherCookieStorageMethodSystemDefault) { + // system default + [request_ setHTTPShouldHandleCookies:YES]; + + // no need for a cookie storage object + self.cookieStorage = nil; + + } else { + // not system default + [request_ setHTTPShouldHandleCookies:NO]; + + if (method == kGTMHTTPFetcherCookieStorageMethodStatic) { + // store cookies in the static array + NSAssert(gGTMFetcherStaticCookieStorage != nil, + @"cookie storage requires GTMHTTPFetchHistory"); + + self.cookieStorage = gGTMFetcherStaticCookieStorage; + } else if (method == kGTMHTTPFetcherCookieStorageMethodFetchHistory) { + // store cookies in the fetch history + self.cookieStorage = [fetchHistory_ cookieStorage]; + } else { + // kGTMHTTPFetcherCookieStorageMethodNone - ignore cookies + self.cookieStorage = nil; + } + } +} + ++ (id )staticCookieStorage { + return gGTMFetcherStaticCookieStorage; +} + ++ (BOOL)doesSupportSentDataCallback { +#if GTM_IPHONE + // NSURLConnection's didSendBodyData: delegate support appears to be + // available starting in iPhone OS 3.0 + return (NSFoundationVersionNumber >= 678.47); +#else + // per WebKit's MaxFoundationVersionWithoutdidSendBodyDataDelegate + // + // indicates if NSURLConnection will invoke the didSendBodyData: delegate + // method + return (NSFoundationVersionNumber > 677.21); +#endif +} + +- (id )fetchHistory { + return fetchHistory_; +} + +- (void)setFetchHistory:(id )fetchHistory { + [fetchHistory_ autorelease]; + fetchHistory_ = [fetchHistory retain]; + + if (fetchHistory_ != nil) { + // set the fetch history's cookie array to be the cookie store + [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodFetchHistory]; + + } else { + // the fetch history was removed + if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodFetchHistory) { + // fall back to static storage + [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic]; + } + } +} + +- (id)userData { + return userData_; +} + +- (void)setUserData:(id)theObj { + [userData_ autorelease]; + userData_ = [theObj retain]; +} + +- (void)setProperties:(NSMutableDictionary *)dict { + [properties_ autorelease]; + + // This copies rather than retains the parameter for compatiblity with + // an earlier version that took an immutable parameter and copied it. + properties_ = [dict mutableCopy]; +} + +- (NSMutableDictionary *)properties { + return properties_; +} + +- (void)setProperty:(id)obj forKey:(NSString *)key { + if (properties_ == nil && obj != nil) { + [self setProperties:[NSMutableDictionary dictionary]]; + } + [properties_ setValue:obj forKey:key]; +} + +- (id)propertyForKey:(NSString *)key { + return [properties_ objectForKey:key]; +} + +- (void)addPropertiesFromDictionary:(NSDictionary *)dict { + if (properties_ == nil && dict != nil) { + [self setProperties:[[dict mutableCopy] autorelease]]; + } else { + [properties_ addEntriesFromDictionary:dict]; + } +} + +- (void)setCommentWithFormat:(id)format, ... { +#if !STRIP_GTM_FETCH_LOGGING + NSString *result = format; + if (format) { + va_list argList; + va_start(argList, format); + result = [[[NSString alloc] initWithFormat:format + arguments:argList] autorelease]; + va_end(argList); + } + [self setComment:result]; +#endif +} + ++ (Class)connectionClass { + if (gGTMFetcherConnectionClass == nil) { + gGTMFetcherConnectionClass = [NSURLConnection class]; + } + return gGTMFetcherConnectionClass; +} + ++ (void)setConnectionClass:(Class)theClass { + gGTMFetcherConnectionClass = theClass; +} + +#if STRIP_GTM_FETCH_LOGGING ++ (void)setLoggingEnabled:(BOOL)flag { +} +#endif // STRIP_GTM_FETCH_LOGGING + +@end + +void GTMAssertSelectorNilOrImplementedWithArgs(id obj, SEL sel, ...) { + + // verify that the object's selector is implemented with the proper + // number and type of arguments +#if DEBUG + va_list argList; + va_start(argList, sel); + + if (obj && sel) { + // check that the selector is implemented + if (![obj respondsToSelector:sel]) { + NSLog(@"\"%@\" selector \"%@\" is unimplemented or misnamed", + NSStringFromClass([obj class]), + NSStringFromSelector(sel)); + NSCAssert(0, @"callback selector unimplemented or misnamed"); + } else { + const char *expectedArgType; + unsigned int argCount = 2; // skip self and _cmd + NSMethodSignature *sig = [obj methodSignatureForSelector:sel]; + + // check that each expected argument is present and of the correct type + while ((expectedArgType = va_arg(argList, const char*)) != 0) { + + if ([sig numberOfArguments] > argCount) { + const char *foundArgType = [sig getArgumentTypeAtIndex:argCount]; + + if(0 != strncmp(foundArgType, expectedArgType, strlen(expectedArgType))) { + NSLog(@"\"%@\" selector \"%@\" argument %d should be type %s", + NSStringFromClass([obj class]), + NSStringFromSelector(sel), (argCount - 2), expectedArgType); + NSCAssert(0, @"callback selector argument type mistake"); + } + } + argCount++; + } + + // check that the proper number of arguments are present in the selector + if (argCount != [sig numberOfArguments]) { + NSLog( @"\"%@\" selector \"%@\" should have %d arguments", + NSStringFromClass([obj class]), + NSStringFromSelector(sel), (argCount - 2)); + NSCAssert(0, @"callback selector arguments incorrect"); + } + } + } + + va_end(argList); +#endif +} + +NSString *GTMCleanedUserAgentString(NSString *str) { + // Reference http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html + // and http://www-archive.mozilla.org/build/user-agent-strings.html + + if (str == nil) return nil; + + NSMutableString *result = [NSMutableString stringWithString:str]; + + // Replace spaces with underscores + [result replaceOccurrencesOfString:@" " + withString:@"_" + options:0 + range:NSMakeRange(0, [result length])]; + + // Delete http token separators and remaining whitespace + static NSCharacterSet *charsToDelete = nil; + if (charsToDelete == nil) { + // Make a set of unwanted characters + NSString *const kSeparators = @"()<>@,;:\\\"/[]?={}"; + + NSMutableCharacterSet *mutableChars; + mutableChars = [[[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy] autorelease]; + [mutableChars addCharactersInString:kSeparators]; + charsToDelete = [mutableChars copy]; // hang on to an immutable copy + } + + while (1) { + NSRange separatorRange = [result rangeOfCharacterFromSet:charsToDelete]; + if (separatorRange.location == NSNotFound) break; + + [result deleteCharactersInRange:separatorRange]; + }; + + return result; +} + +NSString *GTMSystemVersionString(void) { + NSString *systemString = @""; + +#if TARGET_OS_MAC && !TARGET_OS_IPHONE + // Mac build + SInt32 systemMajor = 0, systemMinor = 0, systemRelease = 0; + (void) Gestalt(gestaltSystemVersionMajor, &systemMajor); + (void) Gestalt(gestaltSystemVersionMinor, &systemMinor); + (void) Gestalt(gestaltSystemVersionBugFix, &systemRelease); + + systemString = [NSString stringWithFormat:@"MacOSX/%d.%d.%d", + (int)systemMajor, (int)systemMinor, (int)systemRelease]; + +#elif TARGET_OS_IPHONE + // Compiling against the iPhone SDK + + static NSString *savedSystemString = nil; + if (savedSystemString == nil) { + // Avoid the slowness of calling currentDevice repeatedly on the iPhone + UIDevice* currentDevice = [UIDevice currentDevice]; + + NSString *rawModel = [currentDevice model]; + NSString *model = GTMCleanedUserAgentString(rawModel); + + NSString *systemVersion = [currentDevice systemVersion]; + + savedSystemString = [[NSString alloc] initWithFormat:@"%@/%@", + model, systemVersion]; // "iPod_Touch/2.2" + } + systemString = savedSystemString; + +#elif (GTL_IPHONE || GDATA_IPHONE) + // Compiling iOS libraries against the Mac SDK + systemString = @"iPhone/x.x"; + +#elif defined(_SYS_UTSNAME_H) + // Foundation-only build + struct utsname unameRecord; + uname(&unameRecord); + + systemString = [NSString stringWithFormat:@"%s/%s", + unameRecord.sysname, unameRecord.release]; // "Darwin/8.11.1" +#endif + + return systemString; +} + +// Return a generic name and version for the current application; this avoids +// anonymous server transactions. +NSString *GTMApplicationIdentifier(NSBundle *bundle) { + static NSString *sAppID = nil; + if (sAppID != nil) return sAppID; + + // If there's a bundle ID, use that; otherwise, use the process name + if (bundle == nil) { + bundle = [NSBundle mainBundle]; + } + + NSString *identifier; + NSString *bundleID = [bundle bundleIdentifier]; + if ([bundleID length] > 0) { + identifier = bundleID; + } else { + // Fall back on the procname, prefixed by "proc" to flag that it's + // autogenerated and perhaps unreliable + NSString *procName = [[NSProcessInfo processInfo] processName]; + identifier = [NSString stringWithFormat:@"proc_%@", procName]; + } + + // Clean up whitespace and special characters + identifier = GTMCleanedUserAgentString(identifier); + + // If there's a version number, append that + NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + if ([version length] == 0) { + version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; + } + + // Clean up whitespace and special characters + version = GTMCleanedUserAgentString(version); + + // Glue the two together (cleanup done above or else cleanup would strip the + // slash) + if ([version length] > 0) { + identifier = [identifier stringByAppendingFormat:@"/%@", version]; + } + + sAppID = [identifier copy]; + return sAppID; +} diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherLogging.h b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherLogging.h new file mode 100644 index 00000000..eb259751 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherLogging.h @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherLogging.m b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherLogging.m new file mode 100644 index 00000000..2b959830 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherLogging.m @@ -0,0 +1,1042 @@ +/* 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. + */ + +#if !STRIP_GTM_FETCH_LOGGING + +#include +#include + +#import "GTMHTTPFetcherLogging.h" + +// Sensitive credential strings are replaced in logs with _snip_ +// +// Apps that must see the contents of sensitive tokens can set this to 1 +#ifndef SKIP_GTM_FETCH_LOGGING_SNIPPING +#define SKIP_GTM_FETCH_LOGGING_SNIPPING 0 +#endif + +// If GTMReadMonitorInputStream is available, it can be used for +// capturing uploaded streams of data +// +// We locally declare methods of GTMReadMonitorInputStream so we +// do not need to import the header, as some projects may not have it available +@interface GTMReadMonitorInputStream : NSInputStream ++ (id)inputStreamWithStream:(NSInputStream *)input; +@property (assign) id readDelegate; +@property (assign) SEL readSelector; +@property (retain) NSArray *runLoopModes; +@end + +// If GTMNSJSONSerialization is available, it is used for formatting JSON +@interface GTMNSJSONSerialization : NSObject ++ (NSData *)dataWithJSONObject:(id)obj options:(NSUInteger)opt error:(NSError **)error; ++ (id)JSONObjectWithData:(NSData *)data options:(NSUInteger)opt error:(NSError **)error; +@end + +// Otherwise, if SBJSON is available, it is used for formatting JSON +@interface GTMFetcherSBJSON +- (void)setHumanReadable:(BOOL)flag; +- (NSString*)stringWithObject:(id)value error:(NSError**)error; +- (id)objectWithString:(NSString*)jsonrep error:(NSError**)error; +@end + +@interface GTMHTTPFetcher (GTMHTTPFetcherLoggingUtilities) ++ (NSString *)headersStringForDictionary:(NSDictionary *)dict; + +- (void)inputStream:(GTMReadMonitorInputStream *)stream + readIntoBuffer:(void *)buffer + length:(NSUInteger)length; + +// internal file utilities for logging ++ (BOOL)fileOrDirExistsAtPath:(NSString *)path; ++ (BOOL)makeDirectoryUpToPath:(NSString *)path; ++ (BOOL)removeItemAtPath:(NSString *)path; ++ (BOOL)createSymbolicLinkAtPath:(NSString *)newPath + withDestinationPath:(NSString *)targetPath; + ++ (NSString *)snipSubstringOfString:(NSString *)originalStr + betweenStartString:(NSString *)startStr + endString:(NSString *)endStr; + ++ (id)JSONObjectWithData:(NSData *)data; ++ (id)stringWithJSONObject:(id)obj; +@end + +@implementation GTMHTTPFetcher (GTMHTTPFetcherLogging) + +// fetchers come and fetchers go, but statics are forever +static BOOL gIsLoggingEnabled = NO; +static BOOL gIsLoggingToFile = YES; +static NSString *gLoggingDirectoryPath = nil; +static NSString *gLoggingDateStamp = nil; +static NSString* gLoggingProcessName = nil; + ++ (void)setLoggingDirectory:(NSString *)path { + [gLoggingDirectoryPath autorelease]; + gLoggingDirectoryPath = [path copy]; +} + ++ (NSString *)loggingDirectory { + + if (!gLoggingDirectoryPath) { + NSArray *arr = nil; +#if GTM_IPHONE && TARGET_IPHONE_SIMULATOR + // default to a directory called GTMHTTPDebugLogs into a sandbox-safe + // directory that a developer can find easily, the application home + arr = [NSArray arrayWithObject:NSHomeDirectory()]; +#elif GTM_IPHONE + // Neither ~/Desktop nor ~/Home is writable on an actual iPhone device. + // Put it in ~/Documents. + arr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, YES); +#else + // default to a directory called GTMHTTPDebugLogs in the desktop folder + arr = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, + NSUserDomainMask, YES); +#endif + + if ([arr count] > 0) { + NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs"; + + NSString *desktopPath = [arr objectAtIndex:0]; + NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName]; + + BOOL doesFolderExist = [[self class] fileOrDirExistsAtPath:logsFolderPath]; + + if (!doesFolderExist) { + // make the directory + doesFolderExist = [self makeDirectoryUpToPath:logsFolderPath]; + } + + if (doesFolderExist) { + // it's there; store it in the global + gLoggingDirectoryPath = [logsFolderPath copy]; + } + } + } + return gLoggingDirectoryPath; +} + ++ (void)setLoggingEnabled:(BOOL)flag { + gIsLoggingEnabled = flag; +} + ++ (BOOL)isLoggingEnabled { + return gIsLoggingEnabled; +} + ++ (void)setLoggingToFileEnabled:(BOOL)flag { + gIsLoggingToFile = flag; +} + ++ (BOOL)isLoggingToFileEnabled { + return gIsLoggingToFile; +} + ++ (void)setLoggingProcessName:(NSString *)str { + [gLoggingProcessName release]; + gLoggingProcessName = [str copy]; +} + ++ (NSString *)loggingProcessName { + + // get the process name (once per run) replacing spaces with underscores + if (!gLoggingProcessName) { + + NSString *procName = [[NSProcessInfo processInfo] processName]; + NSMutableString *loggingProcessName; + loggingProcessName = [[NSMutableString alloc] initWithString:procName]; + + [loggingProcessName replaceOccurrencesOfString:@" " + withString:@"_" + options:0 + range:NSMakeRange(0, [gLoggingProcessName length])]; + gLoggingProcessName = loggingProcessName; + } + return gLoggingProcessName; +} + ++ (void)setLoggingDateStamp:(NSString *)str { + [gLoggingDateStamp release]; + gLoggingDateStamp = [str copy]; +} + ++ (NSString *)loggingDateStamp { + // we'll pick one date stamp per run, so a run that starts at a later second + // will get a unique results html file + if (!gLoggingDateStamp) { + // produce a string like 08-21_01-41-23PM + + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:@"M-dd_hh-mm-ssa"]; + + gLoggingDateStamp = [[formatter stringFromDate:[NSDate date]] retain] ; + } + return gLoggingDateStamp; +} + +// formattedStringFromData returns a prettyprinted string for XML or JSON input, +// and a plain string for other input data +- (NSString *)formattedStringFromData:(NSData *)inputData + contentType:(NSString *)contentType + JSON:(NSDictionary **)outJSON { + if (inputData == nil) return nil; + + // if the content type is JSON and we have the parsing class available, + // use that + if ([contentType hasPrefix:@"application/json"] + && [inputData length] > 5) { + // convert from JSON string to NSObjects and back to a formatted string + NSMutableDictionary *obj = [[self class] JSONObjectWithData:inputData]; + if (obj) { + if (outJSON) *outJSON = obj; + if ([obj isKindOfClass:[NSMutableDictionary class]]) { + // for security and privacy, omit OAuth 2 response access and refresh + // tokens + if ([obj valueForKey:@"refresh_token"] != nil) { + [obj setObject:@"_snip_" forKey:@"refresh_token"]; + } + if ([obj valueForKey:@"access_token"] != nil) { + [obj setObject:@"_snip_" forKey:@"access_token"]; + } + } + NSString *formatted = [[self class] stringWithJSONObject:obj]; + if (formatted) return formatted; + } + } + +#if !GTM_FOUNDATION_ONLY && !GTM_SKIP_LOG_XMLFORMAT + // verify that this data starts with the bytes indicating XML + + NSString *const kXMLLintPath = @"/usr/bin/xmllint"; + static BOOL hasCheckedAvailability = NO; + static BOOL isXMLLintAvailable; + + if (!hasCheckedAvailability) { + isXMLLintAvailable = [[self class] fileOrDirExistsAtPath:kXMLLintPath]; + hasCheckedAvailability = YES; + } + + if (isXMLLintAvailable + && [inputData length] > 5 + && strncmp([inputData bytes], " 0) { + // success + inputData = formattedData; + } + } +#else + // we can't call external tasks on the iPhone; leave the XML unformatted +#endif + + NSString *dataStr = [[[NSString alloc] initWithData:inputData + encoding:NSUTF8StringEncoding] autorelease]; + return dataStr; +} + +- (void)setupStreamLogging { + // if logging is enabled, it needs a buffer to accumulate data from any + // NSInputStream used for uploading. Logging will wrap the input + // stream with a stream that lets us keep a copy the data being read. + if ([GTMHTTPFetcher isLoggingEnabled] + && loggedStreamData_ == nil + && postStream_ != nil) { + loggedStreamData_ = [[NSMutableData alloc] init]; + + BOOL didCapture = [self logCapturePostStream]; + if (!didCapture) { + // upload stream logging requires the class + // GTMReadMonitorInputStream be available + NSString const *str = @"<>"; + [loggedStreamData_ setData:[str dataUsingEncoding:NSUTF8StringEncoding]]; + } + } +} + +// stringFromStreamData creates a string given the supplied data +// +// If NSString can create a UTF-8 string from the data, then that is returned. +// +// Otherwise, this routine tries to find a MIME boundary at the beginning of +// the data block, and uses that to break up the data into parts. Each part +// will be used to try to make a UTF-8 string. For parts that fail, a +// replacement string showing the part header and <> is supplied +// in place of the binary data. + +- (NSString *)stringFromStreamData:(NSData *)data + contentType:(NSString *)contentType { + + if (data == nil) return nil; + + // optimistically, see if the whole data block is UTF-8 + NSString *streamDataStr = [self formattedStringFromData:data + contentType:contentType + JSON:NULL]; + if (streamDataStr) return streamDataStr; + + // Munge a buffer by replacing non-ASCII bytes with underscores, + // and turn that munged buffer an NSString. That gives us a string + // we can use with NSScanner. + NSMutableData *mutableData = [NSMutableData dataWithData:data]; + unsigned char *bytes = [mutableData mutableBytes]; + + for (unsigned int idx = 0; idx < [mutableData length]; idx++) { + if (bytes[idx] > 0x7F || bytes[idx] == 0) { + bytes[idx] = '_'; + } + } + + NSString *mungedStr = [[[NSString alloc] initWithData:mutableData + encoding:NSUTF8StringEncoding] autorelease]; + if (mungedStr != nil) { + + // scan for the boundary string + NSString *boundary = nil; + NSScanner *scanner = [NSScanner scannerWithString:mungedStr]; + + if ([scanner scanUpToString:@"\r\n" intoString:&boundary] + && [boundary hasPrefix:@"--"]) { + + // we found a boundary string; use it to divide the string into parts + NSArray *mungedParts = [mungedStr componentsSeparatedByString:boundary]; + + // look at each of the munged parts in the original string, and try to + // convert those into UTF-8 + NSMutableArray *origParts = [NSMutableArray array]; + NSUInteger offset = 0; + for (NSString *mungedPart in mungedParts) { + NSUInteger partSize = [mungedPart length]; + + NSRange range = NSMakeRange(offset, partSize); + NSData *origPartData = [data subdataWithRange:range]; + + NSString *origPartStr = [[[NSString alloc] initWithData:origPartData + encoding:NSUTF8StringEncoding] autorelease]; + if (origPartStr) { + // we could make this original part into UTF-8; use the string + [origParts addObject:origPartStr]; + } else { + // this part can't be made into UTF-8; scan the header, if we can + NSString *header = nil; + NSScanner *headerScanner = [NSScanner scannerWithString:mungedPart]; + if (![headerScanner scanUpToString:@"\r\n\r\n" intoString:&header]) { + // we couldn't find a header + header = @""; + } + + // make a part string with the header and <> + NSString *binStr = [NSString stringWithFormat:@"\r%@\r<<%lu bytes>>\r", + header, (long)(partSize - [header length])]; + [origParts addObject:binStr]; + } + offset += partSize + [boundary length]; + } + + // rejoin the original parts + streamDataStr = [origParts componentsJoinedByString:boundary]; + } + } + + if (!streamDataStr) { + // give up; just make a string showing the uploaded bytes + streamDataStr = [NSString stringWithFormat:@"<<%u bytes>>", + (unsigned int)[data length]]; + } + return streamDataStr; +} + +// logFetchWithError is called following a successful or failed fetch attempt +// +// This method does all the work for appending to and creating log files + +- (void)logFetchWithError:(NSError *)error { + + if (![[self class] isLoggingEnabled]) return; + + // TODO: (grobbins) add Javascript to display response data formatted in hex + + NSString *parentDir = [[self class] loggingDirectory]; + NSString *processName = [[self class] loggingProcessName]; + NSString *dateStamp = [[self class] loggingDateStamp]; + + // make a directory for this run's logs, like + // SyncProto_logs_10-16_01-56-58PM + NSString *dirName = [NSString stringWithFormat:@"%@_log_%@", + processName, dateStamp]; + NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName]; + + if (gIsLoggingToFile) { + // be sure that the first time this app runs, it's not writing to + // a preexisting folder + static BOOL shouldReuseFolder = NO; + if (!shouldReuseFolder) { + shouldReuseFolder = YES; + NSString *origLogDir = logDirectory; + for (int ctr = 2; ctr < 20; ctr++) { + if (![[self class] fileOrDirExistsAtPath:logDirectory]) break; + + // append a digit + logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr]; + } + } + if (![[self class] makeDirectoryUpToPath:logDirectory]) return; + } + // each response's NSData goes into its own xml or txt file, though all + // responses for this run of the app share a main html file. This + // counter tracks all fetch responses for this run of the app. + // + // we'll use a local variable since this routine may be reentered while + // waiting for XML formatting to be completed by an external task + static int zResponseCounter = 0; + int responseCounter = ++zResponseCounter; + + // file name for an image data file + NSString *responseDataFileName = nil; + NSUInteger responseDataLength; + if (downloadFileHandle_) { + responseDataLength = (NSUInteger) [downloadFileHandle_ offsetInFile]; + } else { + responseDataLength = [downloadedData_ length]; + } + + NSURLResponse *response = [self response]; + NSDictionary *responseHeaders = [self responseHeaders]; + + NSString *responseBaseName = nil; + NSString *responseDataStr = nil; + NSDictionary *responseJSON = nil; + + // if there's response data, decide what kind of file to put it in based + // on the first bytes of the file or on the mime type supplied by the server + NSString *responseMIMEType = [response MIMEType]; + BOOL isResponseImage = NO; + NSData *dataToWrite = nil; + + if (responseDataLength > 0) { + NSString *responseDataExtn = nil; + + // generate a response file base name like + responseBaseName = [NSString stringWithFormat:@"fetch_%d_response", + responseCounter]; + + NSString *responseType = [responseHeaders valueForKey:@"Content-Type"]; + responseDataStr = [self formattedStringFromData:downloadedData_ + contentType:responseType + JSON:&responseJSON]; + if (responseDataStr) { + // we were able to make a UTF-8 string from the response data + if ([responseMIMEType isEqual:@"application/atom+xml"] + || [responseMIMEType hasSuffix:@"/xml"]) { + responseDataExtn = @"xml"; + dataToWrite = [responseDataStr dataUsingEncoding:NSUTF8StringEncoding]; + } + } else if ([responseMIMEType isEqual:@"image/jpeg"]) { + responseDataExtn = @"jpg"; + dataToWrite = downloadedData_; + isResponseImage = YES; + } else if ([responseMIMEType isEqual:@"image/gif"]) { + responseDataExtn = @"gif"; + dataToWrite = downloadedData_; + isResponseImage = YES; + } else if ([responseMIMEType isEqual:@"image/png"]) { + responseDataExtn = @"png"; + dataToWrite = downloadedData_; + isResponseImage = YES; + } else { + // add more non-text types here + } + + // if we have an extension, save the raw data in a file with that + // extension + if (responseDataExtn && dataToWrite) { + responseDataFileName = [responseBaseName stringByAppendingPathExtension:responseDataExtn]; + NSString *responseDataFilePath = [logDirectory stringByAppendingPathComponent:responseDataFileName]; + + NSError *downloadedError = nil; + if (gIsLoggingToFile + && ![dataToWrite writeToFile:responseDataFilePath + options:0 + error:&downloadedError]) { + NSLog(@"%@ logging write error:%@ (%@)", + [self class], downloadedError, responseDataFileName); + } + } + } + + // we'll have one main html file per run of the app + NSString *htmlName = @"aperçu_http_log.html"; + NSString *htmlPath =[logDirectory stringByAppendingPathComponent:htmlName]; + + // if the html file exists (from logging previous fetches) we don't need + // to re-write the header or the scripts + BOOL didFileExist = [[self class] fileOrDirExistsAtPath:htmlPath]; + + NSMutableString* outputHTML = [NSMutableString string]; + NSURLRequest *request = [self mutableRequest]; + + // we need a header to say we'll have UTF-8 text + if (!didFileExist) { + [outputHTML appendFormat:@"%@ HTTP fetch log %@", + processName, dateStamp]; + } + + // now write the visible html elements + NSString *copyableFileName = [NSString stringWithFormat:@"fetch_%d.txt", + responseCounter]; + + // write the date & time, the comment, and the link to the plain-text + // (copyable) log + NSString *dateLineFormat = @"%@      "; + [outputHTML appendFormat:dateLineFormat, [NSDate date]]; + + NSString *comment = [self comment]; + if (comment) { + NSString *commentFormat = @"%@      "; + [outputHTML appendFormat:commentFormat, comment]; + } + + NSString *reqRespFormat = @"request/response log
"; + [outputHTML appendFormat:reqRespFormat, copyableFileName]; + + // write the request URL + NSString *requestMethod = [request HTTPMethod]; + NSURL *requestURL = [request URL]; + [outputHTML appendFormat:@"request: %@ %@
\n", + requestMethod, requestURL]; + + // write the request headers + NSDictionary *requestHeaders = [request allHTTPHeaderFields]; + NSUInteger numberOfRequestHeaders = [requestHeaders count]; + if (numberOfRequestHeaders > 0) { + // Indicate if the request is authorized; warn if the request is + // authorized but non-SSL + NSString *auth = [requestHeaders objectForKey:@"Authorization"]; + NSString *headerDetails = @""; + if (auth) { + headerDetails = @"   authorized"; + BOOL isInsecure = [[requestURL scheme] isEqual:@"http"]; + if (isInsecure) { + headerDetails = @"   authorized, non-SSL" + " "; // 26A0 = âš  + } + } + NSString *cookiesHdr = [requestHeaders objectForKey:@"Cookie"]; + if (cookiesHdr) { + headerDetails = [headerDetails stringByAppendingString: + @"   cookies"]; + } + NSString *matchHdr = [requestHeaders objectForKey:@"If-Match"]; + if (matchHdr) { + headerDetails = [headerDetails stringByAppendingString: + @"   if-match"]; + } + matchHdr = [requestHeaders objectForKey:@"If-None-Match"]; + if (matchHdr) { + headerDetails = [headerDetails stringByAppendingString: + @"   if-none-match"]; + } + [outputHTML appendFormat:@"   headers: %d %@
", + (int)numberOfRequestHeaders, headerDetails]; + } else { + [outputHTML appendFormat:@"   headers: none
"]; + } + + // write the request post data, toggleable + NSData *postData; + if (loggedStreamData_) { + postData = loggedStreamData_; + } else if (postData_) { + postData = postData_; + } else { + postData = [request_ HTTPBody]; + } + + NSString *postDataStr = nil; + NSUInteger postDataLength = [postData length]; + NSString *postType = [requestHeaders valueForKey:@"Content-Type"]; + + if (postDataLength > 0) { + [outputHTML appendFormat:@"   data: %d bytes, %@
\n", + (int)postDataLength, postType ? postType : @""]; + + postDataStr = [self stringFromStreamData:postData + contentType:postType]; + if (postDataStr) { + // remove OAuth 2 client secret and refresh token + postDataStr = [[self class] snipSubstringOfString:postDataStr + betweenStartString:@"client_secret=" + endString:@"&"]; + + postDataStr = [[self class] snipSubstringOfString:postDataStr + betweenStartString:@"refresh_token=" + endString:@"&"]; + + // remove ClientLogin password + postDataStr = [[self class] snipSubstringOfString:postDataStr + betweenStartString:@"&Passwd=" + endString:@"&"]; + } + } else { + // no post data + } + + // write the response status, MIME type, URL + NSInteger status = [self statusCode]; + if (response) { + NSString *statusString = @""; + if (status != 0) { + if (status == 200 || status == 201) { + statusString = [NSString stringWithFormat:@"%ld", (long)status]; + + // report any JSON-RPC error + if ([responseJSON isKindOfClass:[NSDictionary class]]) { + NSDictionary *jsonError = [responseJSON objectForKey:@"error"]; + if ([jsonError isKindOfClass:[NSDictionary class]]) { + NSString *jsonCode = [[jsonError valueForKey:@"code"] description]; + NSString *jsonMessage = [jsonError valueForKey:@"message"]; + if (jsonCode || jsonMessage) { + NSString *jsonErrFmt = @"   JSON error: %@ %@  ⚑"; // 2691 = âš‘ + statusString = [statusString stringByAppendingFormat:jsonErrFmt, + jsonCode ? jsonCode : @"", + jsonMessage ? jsonMessage : @""]; + } + } + } + } else { + // purple for anything other than 200 or 201 + NSString *flag = (status >= 400 ? @" ⚑" : @""); // 2691 = âš‘ + NSString *statusFormat = @"%ld %@"; + statusString = [NSString stringWithFormat:statusFormat, + (long)status, flag]; + } + } + + // show the response URL only if it's different from the request URL + NSString *responseURLStr = @""; + NSURL *responseURL = [response URL]; + + if (responseURL && ![responseURL isEqual:[request URL]]) { + NSString *responseURLFormat = @"response URL:" + " %@
\n"; + responseURLStr = [NSString stringWithFormat:responseURLFormat, + [responseURL absoluteString]]; + } + + [outputHTML appendFormat:@"response:  status %@
\n%@", + statusString, responseURLStr]; + + // Write the response headers + NSUInteger numberOfResponseHeaders = [responseHeaders count]; + if (numberOfResponseHeaders > 0) { + // Indicate if the server is setting cookies + NSString *cookiesSet = [responseHeaders valueForKey:@"Set-Cookie"]; + NSString *cookiesStr = (cookiesSet ? @"  " + "sets cookies" : @""); + // Indicate if the server is redirecting + NSString *location = [responseHeaders valueForKey:@"Location"]; + BOOL isRedirect = (status >= 300 && status <= 399 && location != nil); + NSString *redirectsStr = (isRedirect ? @"  " + "redirects" : @""); + + [outputHTML appendFormat:@"   headers: %d %@ %@
\n", + (int)numberOfResponseHeaders, cookiesStr, redirectsStr]; + } else { + [outputHTML appendString:@"   headers: none
\n"]; + } + } + + // error + if (error) { + [outputHTML appendFormat:@"Error: %@
\n", [error description]]; + } + + // Write the response data + if (responseDataFileName) { + NSString *escapedResponseFile = [responseDataFileName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + if (isResponseImage) { + // Make a small inline image that links to the full image file + [outputHTML appendFormat:@"   data: %d bytes, %@
", + (int)responseDataLength, responseMIMEType]; + NSString *fmt = @"image\n"; + [outputHTML appendFormat:fmt, + escapedResponseFile, escapedResponseFile]; + } else { + // The response data was XML; link to the xml file + NSString *fmt = @"   data: %d bytes, " + "%@   %@\n"; + [outputHTML appendFormat:fmt, + (int)responseDataLength, responseMIMEType, + escapedResponseFile, [escapedResponseFile pathExtension]]; + } + } else { + // The response data was not an image; just show the length and MIME type + [outputHTML appendFormat:@"   data: %d bytes, %@\n", + (int)responseDataLength, responseMIMEType]; + } + + // Make a single string of the request and response, suitable for copying + // to the clipboard and pasting into a bug report + NSMutableString *copyable = [NSMutableString string]; + if (comment) { + [copyable appendFormat:@"%@\n\n", comment]; + } + [copyable appendFormat:@"%@\n", [NSDate date]]; + [copyable appendFormat:@"Request: %@ %@\n", requestMethod, requestURL]; + if ([requestHeaders count] > 0) { + [copyable appendFormat:@"Request headers:\n%@\n", + [[self class] headersStringForDictionary:requestHeaders]]; + } + + if (postDataLength > 0) { + [copyable appendFormat:@"Request body: (%u bytes)\n", + (unsigned int) postDataLength]; + if (postDataStr) { + [copyable appendFormat:@"%@\n", postDataStr]; + } + [copyable appendString:@"\n"]; + } + + if (response) { + [copyable appendFormat:@"Response: status %d\n", (int) status]; + [copyable appendFormat:@"Response headers:\n%@\n", + [[self class] headersStringForDictionary:responseHeaders]]; + [copyable appendFormat:@"Response body: (%u bytes)\n", + (unsigned int) responseDataLength]; + if (responseDataLength > 0) { + if (responseDataStr != nil) { + [copyable appendFormat:@"%@\n", responseDataStr]; + } else if (status >= 400 && [temporaryDownloadPath_ length] > 0) { + // Try to read in the saved data, which is probably a server error + // message + NSStringEncoding enc; + responseDataStr = [NSString stringWithContentsOfFile:temporaryDownloadPath_ + usedEncoding:&enc + error:NULL]; + if ([responseDataStr length] > 0) { + [copyable appendFormat:@"%@\n", responseDataStr]; + } else { + [copyable appendFormat:@"<<%u bytes to file>>\n", + (unsigned int) responseDataLength]; + } + } else { + // Even though it's redundant, we'll put in text to indicate that all + // the bytes are binary + [copyable appendFormat:@"<<%u bytes>>\n", + (unsigned int) responseDataLength]; + } + } + } + + if (error) { + [copyable appendFormat:@"Error: %@\n", error]; + } + + // Save to log property before adding the separator + self.log = copyable; + + [copyable appendString:@"-----------------------------------------------------------\n"]; + + + // Write the copyable version to another file (linked to at the top of the + // html file, above) + // + // Ideally, something to just copy this to the clipboard like + // Copy here." + // would work everywhere, but it only works in Safari as of 8/2010 + if (gIsLoggingToFile) { + NSString *copyablePath = [logDirectory stringByAppendingPathComponent:copyableFileName]; + NSError *copyableError = nil; + if (![copyable writeToFile:copyablePath + atomically:NO + encoding:NSUTF8StringEncoding + error:©ableError]) { + // Error writing to file + NSLog(@"%@ logging write error:%@ (%@)", + [self class], copyableError, copyablePath); + } + + [outputHTML appendString:@"

"]; + + // Append the HTML to the main output file + const char* htmlBytes = [outputHTML UTF8String]; + NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:htmlPath + append:YES]; + [stream open]; + [stream write:(const uint8_t *) htmlBytes maxLength:strlen(htmlBytes)]; + [stream close]; + + // Make a symlink to the latest html + NSString *symlinkName = [NSString stringWithFormat:@"%@_log_newest.html", + processName]; + NSString *symlinkPath = [parentDir stringByAppendingPathComponent:symlinkName]; + + [[self class] removeItemAtPath:symlinkPath]; + [[self class] createSymbolicLinkAtPath:symlinkPath + withDestinationPath:htmlPath]; + +#if GTM_IPHONE + static BOOL gReportedLoggingPath = NO; + if (!gReportedLoggingPath) { + gReportedLoggingPath = YES; + NSLog(@"GTMHTTPFetcher logging to \"%@\"", parentDir); + } +#endif + } +} + +- (BOOL)logCapturePostStream { + // This is called when beginning a fetch. The caller should have already + // verified that logging is enabled, and should have allocated + // loggedStreamData_ as a mutable object. + + // If the class GTMReadMonitorInputStream is not available, bail now, since + // we cannot capture this upload stream + Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream"); + if (!monitorClass) return NO; + + // If we're logging, we need to wrap the upload stream with our monitor + // stream that will call us back with the bytes being read from the stream + + // Our wrapper will retain the old post stream + [postStream_ autorelease]; + + postStream_ = [monitorClass inputStreamWithStream:postStream_]; + [postStream_ retain]; + + [(GTMReadMonitorInputStream *)postStream_ setReadDelegate:self]; + [(GTMReadMonitorInputStream *)postStream_ setRunLoopModes:[self runLoopModes]]; + + SEL readSel = @selector(inputStream:readIntoBuffer:length:); + [(GTMReadMonitorInputStream *)postStream_ setReadSelector:readSel]; + + return YES; +} + +@end + +@implementation GTMHTTPFetcher (GTMHTTPFetcherLoggingUtilities) + +- (void)inputStream:(GTMReadMonitorInputStream *)stream + readIntoBuffer:(void *)buffer + length:(NSUInteger)length { + // append the captured data + [loggedStreamData_ appendBytes:buffer length:length]; +} + +#pragma mark Internal file routines + +// We implement plain Unix versions of NSFileManager methods to avoid +// NSFileManager's issues with being used from multiple threads + ++ (BOOL)fileOrDirExistsAtPath:(NSString *)path { + struct stat buffer; + int result = stat([path fileSystemRepresentation], &buffer); + return (result == 0); +} + ++ (BOOL)makeDirectoryUpToPath:(NSString *)path { + int result = 0; + + // Recursively create the parent directory of the requested path + NSString *parent = [path stringByDeletingLastPathComponent]; + if (![self fileOrDirExistsAtPath:parent]) { + result = [self makeDirectoryUpToPath:parent]; + } + + // Make the leaf directory + if (result == 0 && ![self fileOrDirExistsAtPath:path]) { + result = mkdir([path fileSystemRepresentation], S_IRWXU); // RWX for owner + } + return (result == 0); +} + ++ (BOOL)removeItemAtPath:(NSString *)path { + int result = unlink([path fileSystemRepresentation]); + return (result == 0); +} + ++ (BOOL)createSymbolicLinkAtPath:(NSString *)newPath + withDestinationPath:(NSString *)targetPath { + int result = symlink([targetPath fileSystemRepresentation], + [newPath fileSystemRepresentation]); + return (result == 0); +} + +#pragma mark Fomatting Utilities + ++ (NSString *)snipSubstringOfString:(NSString *)originalStr + betweenStartString:(NSString *)startStr + endString:(NSString *)endStr { +#if SKIP_GTM_FETCH_LOGGING_SNIPPING + return originalStr; +#else + if (originalStr == nil) return nil; + + // Find the start string, and replace everything between it + // and the end string (or the end of the original string) with "_snip_" + NSRange startRange = [originalStr rangeOfString:startStr]; + if (startRange.location == NSNotFound) return originalStr; + + // We found the start string + NSUInteger originalLength = [originalStr length]; + NSUInteger startOfTarget = NSMaxRange(startRange); + NSRange targetAndRest = NSMakeRange(startOfTarget, + originalLength - startOfTarget); + NSRange endRange = [originalStr rangeOfString:endStr + options:0 + range:targetAndRest]; + NSRange replaceRange; + if (endRange.location == NSNotFound) { + // Found no end marker so replace to end of string + replaceRange = targetAndRest; + } else { + // Replace up to the endStr + replaceRange = NSMakeRange(startOfTarget, + endRange.location - startOfTarget); + } + + NSString *result = [originalStr stringByReplacingCharactersInRange:replaceRange + withString:@"_snip_"]; + return result; +#endif // SKIP_GTM_FETCH_LOGGING_SNIPPING +} + ++ (NSString *)headersStringForDictionary:(NSDictionary *)dict { + // Format the dictionary in http header style, like + // Accept: application/json + // Cache-Control: no-cache + // Content-Type: application/json; charset=utf-8 + // + // Pad the key names, but not beyond 16 chars, since long custom header + // keys just create too much whitespace + NSArray *keys = [[dict allKeys] sortedArrayUsingSelector:@selector(compare:)]; + + NSMutableString *str = [NSMutableString string]; + for (NSString *key in keys) { + NSString *value = [dict valueForKey:key]; + if ([key isEqual:@"Authorization"]) { + // Remove OAuth 1 token + value = [[self class] snipSubstringOfString:value + betweenStartString:@"oauth_token=\"" + endString:@"\""]; + + // Remove OAuth 2 bearer token (draft 16, and older form) + value = [[self class] snipSubstringOfString:value + betweenStartString:@"Bearer " + endString:@"\n"]; + value = [[self class] snipSubstringOfString:value + betweenStartString:@"OAuth " + endString:@"\n"]; + + // Remove Google ClientLogin + value = [[self class] snipSubstringOfString:value + betweenStartString:@"GoogleLogin auth=" + endString:@"\n"]; + } + [str appendFormat:@" %@: %@\n", key, value]; + } + return str; +} + ++ (id)JSONObjectWithData:(NSData *)data { + Class serializer = NSClassFromString(@"NSJSONSerialization"); + if (serializer) { + const NSUInteger kOpts = (1UL << 0); // NSJSONReadingMutableContainers + NSMutableDictionary *obj; + obj = [serializer JSONObjectWithData:data + options:kOpts + error:NULL]; + return obj; + } else { + // Try SBJsonParser or SBJSON + Class jsonParseClass = NSClassFromString(@"SBJsonParser"); + if (!jsonParseClass) { + jsonParseClass = NSClassFromString(@"SBJSON"); + } + if (jsonParseClass) { + GTMFetcherSBJSON *parser = [[[jsonParseClass alloc] init] autorelease]; + NSString *jsonStr = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + if (jsonStr) { + NSMutableDictionary *obj = [parser objectWithString:jsonStr error:NULL]; + return obj; + } + } + } + return nil; +} + ++ (id)stringWithJSONObject:(id)obj { + Class serializer = NSClassFromString(@"NSJSONSerialization"); + if (serializer) { + const NSUInteger kOpts = (1UL << 0); // NSJSONWritingPrettyPrinted + NSData *data; + data = [serializer dataWithJSONObject:obj + options:kOpts + error:NULL]; + if (data) { + NSString *jsonStr = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + return jsonStr; + } + } else { + // Try SBJsonParser or SBJSON + Class jsonWriterClass = NSClassFromString(@"SBJsonWriter"); + if (!jsonWriterClass) { + jsonWriterClass = NSClassFromString(@"SBJSON"); + } + if (jsonWriterClass) { + GTMFetcherSBJSON *writer = [[[jsonWriterClass alloc] init] autorelease]; + [writer setHumanReadable:YES]; + NSString *jsonStr = [writer stringWithObject:obj error:NULL]; + return jsonStr; + } + } + return nil; +} + +@end + +#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherService.h b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherService.h new file mode 100644 index 00000000..e3837933 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherService.h @@ -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 { + @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 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 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherService.m b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherService.m new file mode 100644 index 00000000..78c5f6f5 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMHTTPFetcherService.m @@ -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 )authorizer { + return authorizer_; +} + +- (void)setAuthorizer:(id )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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMLogger.h b/External/google-plus-ios-sdk/OpenSource/GTMLogger.h new file mode 100644 index 00000000..c4fd1402 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMLogger.h @@ -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 +#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= +// +// 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 writer_; + id formatter_; + id 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)writer + formatter:(id)formatter + filter:(id)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)writer + formatter:(id)formatter + filter:(id)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)writer; +- (void)setWriter:(id)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)formatter; +- (void)setFormatter:(id)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)filter; +- (void)setFilter:(id)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 +// 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) +// 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) +@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) +@end // GTMLoggerLogWriter + + +// +// Log Formatters +// + +// Protocol to be implemented by a GTMLogFormatter instance. +@protocol GTMLogFormatter +// 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 + +// 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 +// 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 +@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 +@end // GTMLogNoFilter + + +// Base class for custom level filters. Not for direct use, use the minimum +// or maximum level subclasses below. +@interface GTMLogAllowedLevelFilter : NSObject { + @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 + diff --git a/External/google-plus-ios-sdk/OpenSource/GTMLogger.m b/External/google-plus-ios-sdk/OpenSource/GTMLogger.m new file mode 100644 index 00000000..0263aa1f --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMLogger.m @@ -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 +#import +#import +#import + + +// 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 writer = [NSFileHandle fileHandleWithStandardOutput]; + id fr = [[[GTMLogStandardFormatter alloc] init] + autorelease]; + id 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)writer + formatter:(id)formatter + filter:(id)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)writer + formatter:(id)formatter + filter:(id)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)writer { + return [[writer_ retain] autorelease]; +} + +- (void)setWriter:(id)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)formatter { + return [[formatter_ retain] autorelease]; +} + +- (void)setFormatter:(id)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)filter { + return [[filter_ retain] autorelease]; +} + +- (void)setFilter:(id)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 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMMethodCheck.h b/External/google-plus-ios-sdk/OpenSource/GTMMethodCheck.h new file mode 100644 index 00000000..7b0919b0 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMMethodCheck.h @@ -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 +#import +#import + +/// 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMMethodCheck.m b/External/google-plus-ios-sdk/OpenSource/GTMMethodCheck.m new file mode 100644 index 00000000..801278cb --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMMethodCheck.m @@ -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 + +// 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMNSDictionary+URLArguments.h b/External/google-plus-ios-sdk/OpenSource/GTMNSDictionary+URLArguments.h new file mode 100644 index 00000000..b0944111 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMNSDictionary+URLArguments.h @@ -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 + +/// 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMNSDictionary+URLArguments.m b/External/google-plus-ios-sdk/OpenSource/GTMNSDictionary+URLArguments.m new file mode 100644 index 00000000..4799b2de --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMNSDictionary+URLArguments.m @@ -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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMNSString+URLArguments.h b/External/google-plus-ios-sdk/OpenSource/GTMNSString+URLArguments.h new file mode 100644 index 00000000..d4c7e09a --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMNSString+URLArguments.h @@ -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 + +/// 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 +/// ) 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMNSString+URLArguments.m b/External/google-plus-ios-sdk/OpenSource/GTMNSString+URLArguments.m new file mode 100644 index 00000000..46d2c99e --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMNSString+URLArguments.m @@ -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 + // () + 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMOAuth2Authentication.h b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2Authentication.h new file mode 100644 index 00000000..50fd188d --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2Authentication.h @@ -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 + +// 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 { + @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 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 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMOAuth2Authentication.m b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2Authentication.m new file mode 100644 index 00000000..6c067f6b --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2Authentication.m @@ -0,0 +1,1167 @@ +/* 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 GTMOAUTH2AUTHENTICATION_DEFINE_GLOBALS 1 +#import "GTMOAuth2Authentication.h" + +// standard OAuth keys +static NSString *const kOAuth2AccessTokenKey = @"access_token"; +static NSString *const kOAuth2RefreshTokenKey = @"refresh_token"; +static NSString *const kOAuth2ClientIDKey = @"client_id"; +static NSString *const kOAuth2ClientSecretKey = @"client_secret"; +static NSString *const kOAuth2RedirectURIKey = @"redirect_uri"; +static NSString *const kOAuth2ResponseTypeKey = @"response_type"; +static NSString *const kOAuth2ScopeKey = @"scope"; +static NSString *const kOAuth2ErrorKey = @"error"; +static NSString *const kOAuth2TokenTypeKey = @"token_type"; +static NSString *const kOAuth2ExpiresInKey = @"expires_in"; +static NSString *const kOAuth2CodeKey = @"code"; +static NSString *const kOAuth2AssertionKey = @"assertion"; + +// additional persistent keys +static NSString *const kServiceProviderKey = @"serviceProvider"; +static NSString *const kUserEmailKey = @"email"; +static NSString *const kUserEmailIsVerifiedKey = @"isVerified"; + +// fetcher keys +static NSString *const kTokenFetchDelegateKey = @"delegate"; +static NSString *const kTokenFetchSelectorKey = @"sel"; + +static NSString *const kRefreshFetchArgsKey = @"requestArgs"; + +// If GTMNSJSONSerialization is available, it is used for formatting JSON +@interface GTMNSJSONSerialization : NSObject ++ (id)JSONObjectWithData:(NSData *)data options:(NSUInteger)opt error:(NSError **)error; +@end + +@interface GTMOAuth2ParserClass : NSObject +// just enough of SBJSON to be able to parse +- (id)objectWithString:(NSString*)repr error:(NSError**)error; +@end + +// wrapper class for requests needing authorization and their callbacks +@interface GTMOAuth2AuthorizationArgs : NSObject { + @private + NSMutableURLRequest *request_; + id delegate_; + SEL sel_; + id completionHandler_; + NSThread *thread_; + NSError *error_; +} + +@property (retain) NSMutableURLRequest *request; +@property (retain) id delegate; +@property (assign) SEL selector; +@property (copy) id completionHandler; +@property (retain) NSThread *thread; +@property (retain) NSError *error; + ++ (GTMOAuth2AuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req + delegate:(id)delegate + selector:(SEL)sel + completionHandler:(id)completionHandler + thread:(NSThread *)thread; +@end + +@implementation GTMOAuth2AuthorizationArgs + +@synthesize request = request_, + delegate = delegate_, + selector = sel_, + completionHandler = completionHandler_, + thread = thread_, + error = error_; + ++ (GTMOAuth2AuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req + delegate:(id)delegate + selector:(SEL)sel + completionHandler:(id)completionHandler + thread:(NSThread *)thread { + GTMOAuth2AuthorizationArgs *obj; + obj = [[[GTMOAuth2AuthorizationArgs alloc] init] autorelease]; + obj.request = req; + obj.delegate = delegate; + obj.selector = sel; + obj.completionHandler = completionHandler; + obj.thread = thread; + return obj; +} + +- (void)dealloc { + [request_ release]; + [delegate_ release]; + [completionHandler_ release]; + [thread_ release]; + [error_ release]; + + [super dealloc]; +} +@end + + +@interface GTMOAuth2Authentication () + +@property (retain) NSMutableArray *authorizationQueue; + +- (void)setKeysForResponseJSONData:(NSData *)data; + +- (BOOL)authorizeRequestArgs:(GTMOAuth2AuthorizationArgs *)args; + +- (BOOL)authorizeRequestImmediateArgs:(GTMOAuth2AuthorizationArgs *)args; + +- (BOOL)shouldRefreshAccessToken; + +- (void)updateExpirationDate; + +- (NSDictionary *)dictionaryWithJSONData:(NSData *)data; + +- (void)tokenFetcher:(GTMHTTPFetcher *)fetcher + finishedWithData:(NSData *)data + error:(NSError *)error; + +- (void)auth:(GTMOAuth2Authentication *)auth +finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher + error:(NSError *)error; + +- (void)invokeCallbackArgs:(GTMOAuth2AuthorizationArgs *)args; + ++ (void)invokeDelegate:(id)delegate + selector:(SEL)sel + object:(id)obj1 + object:(id)obj2 + object:(id)obj3; + ++ (NSString *)unencodedOAuthParameterForString:(NSString *)str; ++ (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict; + ++ (NSDictionary *)dictionaryWithResponseData:(NSData *)data; + +@end + +@implementation GTMOAuth2Authentication + +@synthesize clientID = clientID_, + clientSecret = clientSecret_, + redirectURI = redirectURI_, + parameters = parameters_, + tokenURL = tokenURL_, + expirationDate = expirationDate_, + additionalTokenRequestParameters = additionalTokenRequestParameters_, + refreshFetcher = refreshFetcher_, + fetcherService = fetcherService_, + parserClass = parserClass_, + shouldAuthorizeAllRequests = shouldAuthorizeAllRequests_, + userData = userData_, + properties = properties_, + authorizationQueue = authorizationQueue_; + +// Response parameters +@dynamic accessToken, + refreshToken, + code, + assertion, + errorString, + tokenType, + scope, + expiresIn, + serviceProvider, + userEmail, + userEmailIsVerified; + +@dynamic canAuthorize; + ++ (id)authenticationWithServiceProvider:(NSString *)serviceProvider + tokenURL:(NSURL *)tokenURL + redirectURI:(NSString *)redirectURI + clientID:(NSString *)clientID + clientSecret:(NSString *)clientSecret { + GTMOAuth2Authentication *obj = [[[self alloc] init] autorelease]; + obj.serviceProvider = serviceProvider; + obj.tokenURL = tokenURL; + obj.redirectURI = redirectURI; + obj.clientID = clientID; + obj.clientSecret = clientSecret; + return obj; +} + +- (id)init { + self = [super init]; + if (self) { + authorizationQueue_ = [[NSMutableArray alloc] init]; + parameters_ = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (NSString *)description { + NSArray *props = [NSArray arrayWithObjects:@"accessToken", @"refreshToken", + @"code", @"assertion", @"expirationDate", @"errorString", + nil]; + NSMutableString *valuesStr = [NSMutableString string]; + NSString *separator = @""; + for (NSString *prop in props) { + id result = [self valueForKey:prop]; + if (result) { + [valuesStr appendFormat:@"%@%@=\"%@\"", separator, prop, result]; + separator = @", "; + } + } + + return [NSString stringWithFormat:@"%@ %p: {%@}", + [self class], self, valuesStr]; +} + +- (void)dealloc { + [clientID_ release]; + [clientSecret_ release]; + [redirectURI_ release]; + [parameters_ release]; + [tokenURL_ release]; + [expirationDate_ release]; + [additionalTokenRequestParameters_ release]; + [refreshFetcher_ release]; + [authorizationQueue_ release]; + [userData_ release]; + [properties_ release]; + + [super dealloc]; +} + +#pragma mark - + +- (void)setKeysForResponseDictionary:(NSDictionary *)dict { + if (dict == nil) return; + + // If a new code or access token is being set, remove the old expiration + NSString *newCode = [dict objectForKey:kOAuth2CodeKey]; + NSString *newAccessToken = [dict objectForKey:kOAuth2AccessTokenKey]; + if (newCode || newAccessToken) { + self.expiresIn = nil; + } + + BOOL didRefreshTokenChange = NO; + NSString *refreshToken = [dict objectForKey:kOAuth2RefreshTokenKey]; + if (refreshToken) { + NSString *priorRefreshToken = self.refreshToken; + + if (priorRefreshToken != refreshToken + && (priorRefreshToken == nil + || ![priorRefreshToken isEqual:refreshToken])) { + didRefreshTokenChange = YES; + } + } + + [self.parameters addEntriesFromDictionary:dict]; + [self updateExpirationDate]; + + if (didRefreshTokenChange) { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:kGTMOAuth2RefreshTokenChanged + object:self + userInfo:nil]; + } + // NSLog(@"keys set ----------------------------\n%@", dict); +} + +- (void)setKeysForResponseString:(NSString *)str { + NSDictionary *dict = [[self class] dictionaryWithResponseString:str]; + [self setKeysForResponseDictionary:dict]; +} + +- (void)setKeysForResponseJSONData:(NSData *)data { + NSDictionary *dict = [self dictionaryWithJSONData:data]; + [self setKeysForResponseDictionary:dict]; +} + +- (NSDictionary *)dictionaryWithJSONData:(NSData *)data { + NSMutableDictionary *obj = nil; + NSError *error = nil; + + Class serializer = NSClassFromString(@"NSJSONSerialization"); + if (serializer) { + const NSUInteger kOpts = (1UL << 0); // NSJSONReadingMutableContainers + obj = [serializer JSONObjectWithData:data + options:kOpts + error:&error]; +#if DEBUG + if (error) { + NSString *str = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + NSLog(@"NSJSONSerialization error %@ parsing %@", + error, str); + } +#endif + return obj; + } else { + // try SBJsonParser or SBJSON + Class jsonParseClass = NSClassFromString(@"SBJsonParser"); + if (!jsonParseClass) { + jsonParseClass = NSClassFromString(@"SBJSON"); + } + if (jsonParseClass) { + GTMOAuth2ParserClass *parser = [[[jsonParseClass alloc] init] autorelease]; + NSString *jsonStr = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + if (jsonStr) { + obj = [parser objectWithString:jsonStr error:&error]; +#if DEBUG + if (error) { + NSLog(@"%@ error %@ parsing %@", NSStringFromClass(jsonParseClass), + error, jsonStr); + } +#endif + return obj; + } + } else { +#if DEBUG + NSAssert(0, @"GTMOAuth2Authentication: No parser available"); +#endif + } + } + return nil; +} + +#pragma mark Authorizing Requests + +// General entry point for authorizing requests + +#if NS_BLOCKS_AVAILABLE +// Authorizing with a completion block +- (void)authorizeRequest:(NSMutableURLRequest *)request + completionHandler:(void (^)(NSError *error))handler { + + GTMOAuth2AuthorizationArgs *args; + args = [GTMOAuth2AuthorizationArgs argsWithRequest:request + delegate:nil + selector:NULL + completionHandler:handler + thread:[NSThread currentThread]]; + [self authorizeRequestArgs:args]; +} +#endif + +// Authorizing with a callback selector +// +// Selector has the signature +// - (void)authentication:(GTMOAuth2Authentication *)auth +// request:(NSMutableURLRequest *)request +// finishedWithError:(NSError *)error; +- (void)authorizeRequest:(NSMutableURLRequest *)request + delegate:(id)delegate + didFinishSelector:(SEL)sel { + GTMAssertSelectorNilOrImplementedWithArgs(delegate, sel, + @encode(GTMOAuth2Authentication *), + @encode(NSMutableURLRequest *), + @encode(NSError *), 0); + + GTMOAuth2AuthorizationArgs *args; + args = [GTMOAuth2AuthorizationArgs argsWithRequest:request + delegate:delegate + selector:sel + completionHandler:nil + thread:[NSThread currentThread]]; + [self authorizeRequestArgs:args]; +} + +// Internal routine common to delegate and block invocations +- (BOOL)authorizeRequestArgs:(GTMOAuth2AuthorizationArgs *)args { + BOOL didAttempt = NO; + + @synchronized(authorizationQueue_) { + + BOOL shouldRefresh = [self shouldRefreshAccessToken]; + + if (shouldRefresh) { + // attempt to refresh now; once we have a fresh access token, we will + // authorize the request and call back to the user + didAttempt = YES; + + if (self.refreshFetcher == nil) { + // there's not already a refresh pending + SEL finishedSel = @selector(auth:finishedRefreshWithFetcher:error:); + self.refreshFetcher = [self beginTokenFetchWithDelegate:self + didFinishSelector:finishedSel]; + if (self.refreshFetcher) { + [authorizationQueue_ addObject:args]; + } + } else { + // there's already a refresh pending + [authorizationQueue_ addObject:args]; + } + } + + if (!shouldRefresh || self.refreshFetcher == nil) { + // we're not fetching a new access token, so we can authorize the request + // now + didAttempt = [self authorizeRequestImmediateArgs:args]; + } + } + return didAttempt; +} + +- (void)auth:(GTMOAuth2Authentication *)auth +finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher + error:(NSError *)error { + @synchronized(authorizationQueue_) { + // If there's an error, we want to try using the old access token anyway, + // in case it's a backend problem preventing refresh, in which case + // access tokens past their expiration date may still work + + self.refreshFetcher = nil; + + // Swap in a new auth queue in case the callbacks try to immediately auth + // another request + NSArray *pendingAuthQueue = [NSArray arrayWithArray:authorizationQueue_]; + [authorizationQueue_ removeAllObjects]; + + BOOL hasAccessToken = ([self.accessToken length] > 0); + + if (hasAccessToken && error == nil) { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:kGTMOAuth2AccessTokenRefreshed + object:self + userInfo:nil]; + } + + for (GTMOAuth2AuthorizationArgs *args in pendingAuthQueue) { + if (!hasAccessToken && args.error == nil) { + args.error = error; + } + + [self authorizeRequestImmediateArgs:args]; + } + } +} + +- (BOOL)isAuthorizingRequest:(NSURLRequest *)request { + BOOL wasFound = NO; + @synchronized(authorizationQueue_) { + for (GTMOAuth2AuthorizationArgs *args in authorizationQueue_) { + if ([args request] == request) { + wasFound = YES; + break; + } + } + } + return wasFound; +} + +- (BOOL)isAuthorizedRequest:(NSURLRequest *)request { + NSString *authStr = [request valueForHTTPHeaderField:@"Authorization"]; + return ([authStr length] > 0); +} + +- (void)stopAuthorization { + @synchronized(authorizationQueue_) { + [authorizationQueue_ removeAllObjects]; + + [self.refreshFetcher stopFetching]; + self.refreshFetcher = nil; + } +} + +- (BOOL)authorizeRequestImmediateArgs:(GTMOAuth2AuthorizationArgs *)args { + // This authorization entry point never attempts to refresh the access token, + // but does call the completion routine + + NSMutableURLRequest *request = args.request; + + NSString *scheme = [[request URL] scheme]; + BOOL isAuthorizableRequest = self.shouldAuthorizeAllRequests + || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame; + if (!isAuthorizableRequest) { + // Request is not https, so may be insecure + // + // The NSError will be created below +#if DEBUG + NSLog(@"Cannot authorize request with scheme %@ (%@)", scheme, request); +#endif + } + + NSString *accessToken = self.accessToken; + if (isAuthorizableRequest && [accessToken length] > 0) { + if (request) { + // we have a likely valid access token + NSString *value = [NSString stringWithFormat:@"%s %@", + GTM_OAUTH2_BEARER, accessToken]; + [request setValue:value forHTTPHeaderField:@"Authorization"]; + } + + // We've authorized the request, even if the previous refresh + // failed with an error + args.error = nil; + } else if (args.error == nil) { + NSDictionary *userInfo = nil; + if (request) { + userInfo = [NSDictionary dictionaryWithObject:request + forKey:kGTMOAuth2ErrorRequestKey]; + } + NSInteger code = (isAuthorizableRequest ? + kGTMOAuth2ErrorAuthorizationFailed : + kGTMOAuth2ErrorUnauthorizableRequest); + args.error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain + code:code + userInfo:userInfo]; + } + + // Invoke any callbacks on the proper thread + if (args.delegate || args.completionHandler) { + NSThread *targetThread = args.thread; + BOOL isSameThread = [targetThread isEqual:[NSThread currentThread]]; + + [self performSelector:@selector(invokeCallbackArgs:) + onThread:targetThread + withObject:args + waitUntilDone:isSameThread]; + } + + BOOL didAuth = (args.error == nil); + return didAuth; +} + +- (void)invokeCallbackArgs:(GTMOAuth2AuthorizationArgs *)args { + // Invoke the callbacks + NSError *error = args.error; + + id delegate = args.delegate; + SEL sel = args.selector; + if (delegate && sel) { + NSMutableURLRequest *request = args.request; + + NSMethodSignature *sig = [delegate methodSignatureForSelector:sel]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocation setTarget:delegate]; + [invocation setArgument:&self atIndex:2]; + [invocation setArgument:&request atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + } + +#if NS_BLOCKS_AVAILABLE + id handler = args.completionHandler; + if (handler) { + void (^authCompletionBlock)(NSError *) = handler; + authCompletionBlock(error); + } +#endif +} + +- (BOOL)authorizeRequest:(NSMutableURLRequest *)request { + // Entry point for synchronous authorization mechanisms + GTMOAuth2AuthorizationArgs *args; + args = [GTMOAuth2AuthorizationArgs argsWithRequest:request + delegate:nil + selector:NULL + completionHandler:nil + thread:[NSThread currentThread]]; + return [self authorizeRequestImmediateArgs:args]; +} + +- (BOOL)canAuthorize { + NSString *token = self.refreshToken; + if (token == nil) { + // For services which do not support refresh tokens, we'll just check + // the access token + token = self.accessToken; + } + BOOL canAuth = [token length] > 0; + return canAuth; +} + +- (BOOL)shouldRefreshAccessToken { + // We should refresh the access token when it's missing or nearly expired + // and we have a refresh token + BOOL shouldRefresh = NO; + NSString *accessToken = self.accessToken; + NSString *refreshToken = self.refreshToken; + NSString *assertion = self.assertion; + + BOOL hasRefreshToken = ([refreshToken length] > 0); + BOOL hasAccessToken = ([accessToken length] > 0); + BOOL hasAssertion = ([assertion length] > 0); + + // Determine if we need to refresh the access token + if (hasRefreshToken || hasAssertion) { + if (!hasAccessToken) { + shouldRefresh = YES; + } else { + // We'll consider the token expired if it expires 60 seconds from now + // or earlier + NSDate *expirationDate = self.expirationDate; + NSTimeInterval timeToExpire = [expirationDate timeIntervalSinceNow]; + if (expirationDate == nil || timeToExpire < 60.0) { + // access token has expired, or will in a few seconds + shouldRefresh = YES; + } + } + } + return shouldRefresh; +} + +- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { + // If there is a refresh fetcher pending, wait for it. + // + // This is only intended for unit test or for use in command-line tools. + GTMHTTPFetcher *fetcher = self.refreshFetcher; + [fetcher waitForCompletionWithTimeout:timeoutInSeconds]; +} + +#pragma mark Token Fetch + +- (NSString *)userAgent { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *appID = [bundle bundleIdentifier]; + + NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + if (version == nil) { + version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; + } + + if (appID && version) { + appID = [appID stringByAppendingFormat:@"/%@", version]; + } + + NSString *userAgent = @"gtm-oauth2"; + if (appID) { + userAgent = [userAgent stringByAppendingFormat:@" %@", appID]; + } + return userAgent; +} + +- (GTMHTTPFetcher *)beginTokenFetchWithDelegate:(id)delegate + didFinishSelector:(SEL)finishedSel { + + NSMutableDictionary *paramsDict = [NSMutableDictionary dictionary]; + + NSString *commentTemplate; + NSString *fetchType; + + NSString *refreshToken = self.refreshToken; + NSString *code = self.code; + NSString *assertion = self.assertion; + + if (refreshToken) { + // We have a refresh token + [paramsDict setObject:@"refresh_token" forKey:@"grant_type"]; + [paramsDict setObject:refreshToken forKey:@"refresh_token"]; + + fetchType = kGTMOAuth2FetchTypeRefresh; + commentTemplate = @"refresh token for %@"; + } else if (code) { + // We have a code string + [paramsDict setObject:@"authorization_code" forKey:@"grant_type"]; + [paramsDict setObject:code forKey:@"code"]; + + NSString *redirectURI = self.redirectURI; + if ([redirectURI length] > 0) { + [paramsDict setObject:redirectURI forKey:@"redirect_uri"]; + } + + NSString *scope = self.scope; + if ([scope length] > 0) { + [paramsDict setObject:scope forKey:@"scope"]; + } + + fetchType = kGTMOAuth2FetchTypeToken; + commentTemplate = @"fetch tokens for %@"; + } else if (assertion) { + // We have an assertion string + [paramsDict setObject:assertion forKey:@"assertion"]; + [paramsDict setObject:@"http://oauth.net/grant_type/jwt/1.0/bearer" + forKey:@"grant_type"]; + commentTemplate = @"fetch tokens for %@"; + fetchType = kGTMOAuth2FetchTypeAssertion; + } else { +#if DEBUG + NSAssert(0, @"unexpected lack of code or refresh token for fetching"); +#endif + return nil; + } + + NSString *clientID = self.clientID; + if ([clientID length] > 0) { + [paramsDict setObject:clientID forKey:@"client_id"]; + } + + NSString *clientSecret = self.clientSecret; + if ([clientSecret length] > 0) { + [paramsDict setObject:clientSecret forKey:@"client_secret"]; + } + + NSDictionary *additionalParams = self.additionalTokenRequestParameters; + if (additionalParams) { + [paramsDict addEntriesFromDictionary:additionalParams]; + } + + NSString *paramStr = [[self class] encodedQueryParametersForDictionary:paramsDict]; + NSData *paramData = [paramStr dataUsingEncoding:NSUTF8StringEncoding]; + + NSURL *tokenURL = self.tokenURL; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:tokenURL]; + [request setValue:@"application/x-www-form-urlencoded" + forHTTPHeaderField:@"Content-Type"]; + + NSString *userAgent = [self userAgent]; + [request setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + + GTMHTTPFetcher *fetcher; + id fetcherService = self.fetcherService; + if (fetcherService) { + fetcher = [fetcherService fetcherWithRequest:request]; + + // Don't use an authorizer for an auth token fetch + fetcher.authorizer = nil; + } else { + fetcher = [GTMHTTPFetcher fetcherWithRequest:request]; + } + + [fetcher setCommentWithFormat:commentTemplate, [tokenURL host]]; + fetcher.postData = paramData; + fetcher.retryEnabled = YES; + fetcher.maxRetryInterval = 15.0; + + // Fetcher properties will retain the delegate + [fetcher setProperty:delegate forKey:kTokenFetchDelegateKey]; + if (finishedSel) { + NSString *selStr = NSStringFromSelector(finishedSel); + [fetcher setProperty:selStr forKey:kTokenFetchSelectorKey]; + } + + if ([fetcher beginFetchWithDelegate:self + didFinishSelector:@selector(tokenFetcher:finishedWithData:error:)]) { + // Fetch began + [self notifyFetchIsRunning:YES fetcher:fetcher type:fetchType]; + return fetcher; + } else { + // Failed to start fetching; typically a URL issue + NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain + code:-1 + userInfo:nil]; + [[self class] invokeDelegate:delegate + selector:finishedSel + object:self + object:nil + object:error]; + return nil; + } +} + +- (void)tokenFetcher:(GTMHTTPFetcher *)fetcher + finishedWithData:(NSData *)data + error:(NSError *)error { + [self notifyFetchIsRunning:NO fetcher:fetcher type:nil]; + + NSDictionary *responseHeaders = [fetcher responseHeaders]; + NSString *responseType = [responseHeaders valueForKey:@"Content-Type"]; + BOOL isResponseJSON = [responseType hasPrefix:@"application/json"]; + BOOL hasData = ([data length] > 0); + + if (error) { + // Failed; if the error body is JSON, parse it and add it to the error's + // userInfo dictionary + if (hasData) { + if (isResponseJSON) { + NSDictionary *errorJson = [self dictionaryWithJSONData:data]; + if ([errorJson count] > 0) { +#if DEBUG + NSLog(@"Error %@\nError data:\n%@", error, errorJson); +#endif + // Add the JSON error body to the userInfo of the error + NSMutableDictionary *userInfo; + userInfo = [NSMutableDictionary dictionaryWithObject:errorJson + forKey:kGTMOAuth2ErrorJSONKey]; + NSDictionary *prevUserInfo = [error userInfo]; + if (prevUserInfo) { + [userInfo addEntriesFromDictionary:prevUserInfo]; + } + error = [NSError errorWithDomain:[error domain] + code:[error code] + userInfo:userInfo]; + } + } + } + } else { + // Succeeded; we have an access token +#if DEBUG + NSAssert(hasData, @"data missing in token response"); +#endif + + if (hasData) { + if (isResponseJSON) { + [self setKeysForResponseJSONData:data]; + } else { + // Support for legacy token servers that return form-urlencoded data + NSString *dataStr = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + [self setKeysForResponseString:dataStr]; + } + +#if DEBUG + // Watch for token exchanges that return a non-bearer or unlabeled token + NSString *tokenType = [self tokenType]; + if (tokenType == nil + || [tokenType caseInsensitiveCompare:@"bearer"] != NSOrderedSame) { + NSLog(@"GTMOAuth2: Unexpected token type: %@", tokenType); + } +#endif + } + } + + id delegate = [fetcher propertyForKey:kTokenFetchDelegateKey]; + SEL sel = NULL; + NSString *selStr = [fetcher propertyForKey:kTokenFetchSelectorKey]; + if (selStr) sel = NSSelectorFromString(selStr); + + [[self class] invokeDelegate:delegate + selector:sel + object:self + object:fetcher + object:error]; + + // Prevent a circular reference from retaining the delegate + [fetcher setProperty:nil forKey:kTokenFetchDelegateKey]; +} + +#pragma mark Fetch Notifications + +- (void)notifyFetchIsRunning:(BOOL)isStarting + fetcher:(GTMHTTPFetcher *)fetcher + type:(NSString *)fetchType { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + NSString *name = (isStarting ? kGTMOAuth2FetchStarted : kGTMOAuth2FetchStopped); + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: + fetcher, kGTMOAuth2FetcherKey, + fetchType, kGTMOAuth2FetchTypeKey, // fetchType may be nil + nil]; + [nc postNotificationName:name + object:self + userInfo:dict]; +} + +#pragma mark Persistent Response Strings + +- (void)setKeysForPersistenceResponseString:(NSString *)str { + // All persistence keys can be set directly as if returned by a server + [self setKeysForResponseString:str]; +} + +// This returns a "response string" that can be passed later to +// setKeysForResponseString: to reuse an old access token in a new auth object +- (NSString *)persistenceResponseString { + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:4]; + + NSString *refreshToken = self.refreshToken; + NSString *accessToken = nil; + if (refreshToken == nil) { + // We store the access token only for services that do not support refresh + // tokens; otherwise, we assume the access token is too perishable to + // be worth storing + accessToken = self.accessToken; + } + + // Any nil values will not set a dictionary entry + [dict setValue:refreshToken forKey:kOAuth2RefreshTokenKey]; + [dict setValue:accessToken forKey:kOAuth2AccessTokenKey]; + [dict setValue:self.serviceProvider forKey:kServiceProviderKey]; + [dict setValue:self.userEmail forKey:kUserEmailKey]; + [dict setValue:self.userEmailIsVerified forKey:kUserEmailIsVerifiedKey]; + [dict setValue:self.scope forKey:kOAuth2ScopeKey]; + + NSString *result = [[self class] encodedQueryParametersForDictionary:dict]; + return result; +} + +- (BOOL)primeForRefresh { + if (self.refreshToken == nil) { + // Cannot refresh without a refresh token + return NO; + } + self.accessToken = nil; + self.expiresIn = nil; + self.expirationDate = nil; + self.errorString = nil; + return YES; +} + +- (void)reset { + // Reset all per-authorization values + self.code = nil; + self.accessToken = nil; + self.refreshToken = nil; + self.assertion = nil; + self.expiresIn = nil; + self.errorString = nil; + self.expirationDate = nil; + self.userEmail = nil; + self.userEmailIsVerified = nil; +} + +#pragma mark Accessors for Response Parameters + +- (NSString *)accessToken { + return [self.parameters objectForKey:kOAuth2AccessTokenKey]; +} + +- (void)setAccessToken:(NSString *)str { + [self.parameters setValue:str forKey:kOAuth2AccessTokenKey]; +} + +- (NSString *)refreshToken { + return [self.parameters objectForKey:kOAuth2RefreshTokenKey]; +} + +- (void)setRefreshToken:(NSString *)str { + [self.parameters setValue:str forKey:kOAuth2RefreshTokenKey]; +} + +- (NSString *)code { + return [self.parameters objectForKey:kOAuth2CodeKey]; +} + +- (void)setCode:(NSString *)str { + [self.parameters setValue:str forKey:kOAuth2CodeKey]; +} + +- (NSString *)assertion { + return [self.parameters objectForKey:kOAuth2AssertionKey]; +} + +- (void)setAssertion:(NSString *)str { + [self.parameters setValue:str forKey:kOAuth2AssertionKey]; +} + +- (NSString *)errorString { + return [self.parameters objectForKey:kOAuth2ErrorKey]; +} + +- (void)setErrorString:(NSString *)str { + [self.parameters setValue:str forKey:kOAuth2ErrorKey]; +} + +- (NSString *)tokenType { + return [self.parameters objectForKey:kOAuth2TokenTypeKey]; +} + +- (void)setTokenType:(NSString *)str { + [self.parameters setValue:str forKey:kOAuth2TokenTypeKey]; +} + +- (NSString *)scope { + return [self.parameters objectForKey:kOAuth2ScopeKey]; +} + +- (void)setScope:(NSString *)str { + [self.parameters setValue:str forKey:kOAuth2ScopeKey]; +} + +- (NSNumber *)expiresIn { + return [self.parameters objectForKey:kOAuth2ExpiresInKey]; +} + +- (void)setExpiresIn:(NSNumber *)num { + [self.parameters setValue:num forKey:kOAuth2ExpiresInKey]; + [self updateExpirationDate]; +} + +- (void)updateExpirationDate { + // Update our absolute expiration time to something close to when + // the server expects the expiration + NSDate *date = nil; + NSNumber *expiresIn = self.expiresIn; + if (expiresIn) { + unsigned long deltaSeconds = [expiresIn unsignedLongValue]; + if (deltaSeconds > 0) { + date = [NSDate dateWithTimeIntervalSinceNow:deltaSeconds]; + } + } + self.expirationDate = date; +} + +// +// Keys custom to this class, not part of OAuth 2 +// + +- (NSString *)serviceProvider { + return [self.parameters objectForKey:kServiceProviderKey]; +} + +- (void)setServiceProvider:(NSString *)str { + [self.parameters setValue:str forKey:kServiceProviderKey]; +} + +- (NSString *)userEmail { + return [self.parameters objectForKey:kUserEmailKey]; +} + +- (void)setUserEmail:(NSString *)str { + [self.parameters setValue:str forKey:kUserEmailKey]; +} + +- (NSString *)userEmailIsVerified { + return [self.parameters objectForKey:kUserEmailIsVerifiedKey]; +} + +- (void)setUserEmailIsVerified:(NSString *)str { + [self.parameters setValue:str forKey:kUserEmailIsVerifiedKey]; +} + +#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 Utility Routines + ++ (NSString *)encodedOAuthValueForString:(NSString *)str { + CFStringRef originalString = (CFStringRef) str; + CFStringRef leaveUnescaped = NULL; + CFStringRef forceEscaped = CFSTR("!*'();:@&=+$,/?%#[]"); + + CFStringRef escapedStr = NULL; + if (str) { + escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + originalString, + leaveUnescaped, + forceEscaped, + kCFStringEncodingUTF8); + [(id)CFMakeCollectable(escapedStr) autorelease]; + } + + return (NSString *)escapedStr; +} + ++ (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict { + // Make a string like "cat=fluffy@dog=spot" + NSMutableString *result = [NSMutableString string]; + NSArray *sortedKeys = [[dict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; + NSString *joiner = @""; + for (NSString *key in sortedKeys) { + NSString *value = [dict objectForKey:key]; + NSString *encodedValue = [self encodedOAuthValueForString:value]; + NSString *encodedKey = [self encodedOAuthValueForString:key]; + [result appendFormat:@"%@%@=%@", joiner, encodedKey, encodedValue]; + joiner = @"&"; + } + return result; +} + ++ (void)invokeDelegate:(id)delegate + selector:(SEL)sel + object:(id)obj1 + object:(id)obj2 + object:(id)obj3 { + if (delegate && sel) { + NSMethodSignature *sig = [delegate methodSignatureForSelector:sel]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocation setTarget:delegate]; + [invocation setArgument:&obj1 atIndex:2]; + [invocation setArgument:&obj2 atIndex:3]; + [invocation setArgument:&obj3 atIndex:4]; + [invocation invoke]; + } +} + ++ (NSString *)unencodedOAuthParameterForString:(NSString *)str { + NSString *plainStr = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + return plainStr; +} + ++ (NSDictionary *)dictionaryWithResponseString:(NSString *)responseStr { + // Build a dictionary from a response string of the form + // "cat=fluffy&dog=spot". Missing or empty values are considered + // empty strings; keys and values are percent-decoded. + if (responseStr == nil) return nil; + + NSArray *items = [responseStr componentsSeparatedByString:@"&"]; + + NSMutableDictionary *responseDict = [NSMutableDictionary dictionaryWithCapacity:[items count]]; + + for (NSString *item in items) { + NSString *key = nil; + NSString *value = @""; + + NSRange equalsRange = [item rangeOfString:@"="]; + if (equalsRange.location != NSNotFound) { + // The parameter has at least one '=' + key = [item substringToIndex:equalsRange.location]; + + // There are characters after the '=' + value = [item substringFromIndex:(equalsRange.location + 1)]; + } else { + // The parameter has no '=' + key = item; + } + + NSString *plainKey = [[self class] unencodedOAuthParameterForString:key]; + NSString *plainValue = [[self class] unencodedOAuthParameterForString:value]; + + [responseDict setObject:plainValue forKey:plainKey]; + } + + return responseDict; +} + ++ (NSDictionary *)dictionaryWithResponseData:(NSData *)data { + NSString *responseStr = [[[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding] autorelease]; + NSDictionary *dict = [self dictionaryWithResponseString:responseStr]; + return dict; +} + ++ (NSString *)scopeWithStrings:(NSString *)str, ... { + // concatenate the strings, joined by a single space + NSString *result = @""; + NSString *joiner = @""; + if (str) { + va_list argList; + va_start(argList, str); + while (str) { + result = [result stringByAppendingFormat:@"%@%@", joiner, str]; + joiner = @" "; + str = va_arg(argList, id); + } + va_end(argList); + } + return result; +} + +@end + +#endif // GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES diff --git a/External/google-plus-ios-sdk/OpenSource/GTMOAuth2SignIn.h b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2SignIn.h new file mode 100644 index 00000000..a09be13c --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2SignIn.h @@ -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 +#import + +// 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMOAuth2SignIn.m b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2SignIn.m new file mode 100644 index 00000000..f1a11ee5 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2SignIn.m @@ -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 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 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewControllerTouch.h b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewControllerTouch.h new file mode 100644 index 00000000..0037ab30 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewControllerTouch.h @@ -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 + +#if TARGET_OS_IPHONE + +#import + +#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 { + @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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewControllerTouch.m b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewControllerTouch.m new file mode 100644 index 00000000..99952b0b --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewControllerTouch.m @@ -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 +#import + +#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 = @"

" + @"⌚ ?
System Clock Incorrect
%@" + @"
"; + 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 diff --git a/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewTouch.xib b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewTouch.xib new file mode 100644 index 00000000..f3f65e32 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMOAuth2ViewTouch.xib @@ -0,0 +1,456 @@ + + + + 768 + 10J869 + 851 + 1038.35 + 461.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 141 + + + YES + + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + OAuth + IBCocoaTouchFramework + + + IBCocoaTouchFramework + 1 + + + + 292 + + YES + + + 292 + {30, 30} + + NO + NO + IBCocoaTouchFramework + 0 + 0 + + Helvetica-Bold + 24 + 16 + + {0, -2} + â—€ + + 3 + MQA + + + 2 + MC41OTYwNzg0NiAwLjY4NjI3NDUzIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA + + + + 3 + MC41AA + + + + + 292 + {{30, 0}, {30, 30}} + + NO + NO + IBCocoaTouchFramework + 0 + 0 + + {0, -2} + â–¶ + + + 2 + MC41ODQzMTM3NSAwLjY3NDUwOTgyIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA + + + + + + {60, 30} + + + 3 + MSAwAA + + NO + NO + + 3 + + IBCocoaTouchFramework + + + + 274 + + YES + + + 274 + {320, 460} + + + 1 + MSAxIDEAA + + YES + YES + IBCocoaTouchFramework + 1 + YES + + + {320, 460} + + + 3 + MQA + + 2 + + + IBCocoaTouchFramework + + + + + YES + + + delegate + + + + 9 + + + + rightBarButtonItem + + + + 14 + + + + goBack + + + 7 + + 18 + + + + goForward + + + 7 + + 19 + + + + rightBarButtonItem + + + + 20 + + + + navButtonsView + + + + 22 + + + + backButton + + + + 25 + + + + forwardButton + + + + 26 + + + + view + + + + 28 + + + + webView + + + + 29 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 6 + + + YES + + + + + 10 + + + + + 15 + + + YES + + + + + + + 16 + + + + + 17 + + + + + 27 + + + YES + + + + + + 4 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 10.IBPluginDependency + 15.IBEditorWindowLastContentRect + 15.IBPluginDependency + 16.IBPluginDependency + 17.IBPluginDependency + 27.IBEditorWindowLastContentRect + 27.IBPluginDependency + 4.IBPluginDependency + 6.IBPluginDependency + + + YES + GTMOAuth2ViewControllerTouch + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{34, 1031}, {60, 30}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{214, 696}, {320, 460}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + YES + + + + + YES + + + YES + + + + 29 + + + + YES + + GTMOAuth2ViewControllerTouch + UIViewController + + YES + + YES + backButton + delegate_ + forwardButton + navButtonsView + rightBarButtonItem + userData_ + webView + + + YES + UIButton + id + UIButton + UIView + UIBarButtonItem + id + UIWebView + + + + YES + + YES + backButton + delegate_ + forwardButton + navButtonsView + rightBarButtonItem + userData_ + webView + + + YES + + backButton + UIButton + + + delegate_ + id + + + forwardButton + UIButton + + + navButtonsView + UIView + + + rightBarButtonItem + UIBarButtonItem + + + userData_ + id + + + webView + UIWebView + + + + + IBProjectSource + Touch/GTMOAuth2ViewControllerTouch.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS + + + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + ../GTMOAuth2.xcodeproj + 3 + 141 + + diff --git a/External/google-plus-ios-sdk/OpenSource/GTMObjC2Runtime.h b/External/google-plus-ios-sdk/OpenSource/GTMObjC2Runtime.h new file mode 100644 index 00000000..e4e2ac72 --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMObjC2Runtime.h @@ -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 +#import +#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 +#import +#else +#import +#import +#endif + +#import + +#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) diff --git a/External/google-plus-ios-sdk/OpenSource/GTMObjC2Runtime.m b/External/google-plus-ios-sdk/OpenSource/GTMObjC2Runtime.m new file mode 100644 index 00000000..f284542c --- /dev/null +++ b/External/google-plus-ios-sdk/OpenSource/GTMObjC2Runtime.m @@ -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 +#import + +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) diff --git a/External/google-plus-ios-sdk/README b/External/google-plus-ios-sdk/README new file mode 100644 index 00000000..749afdf6 --- /dev/null +++ b/External/google-plus-ios-sdk/README @@ -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. diff --git a/External/google-plus-ios-sdk/Resources/google_plus_share.png b/External/google-plus-ios-sdk/Resources/google_plus_share.png new file mode 100644 index 00000000..8bafd4cd Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_share.png differ diff --git a/External/google-plus-ios-sdk/Resources/google_plus_share@2x.png b/External/google-plus-ios-sdk/Resources/google_plus_share@2x.png new file mode 100644 index 00000000..9cf3345e Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_share@2x.png differ diff --git a/External/google-plus-ios-sdk/Resources/google_plus_share_large.png b/External/google-plus-ios-sdk/Resources/google_plus_share_large.png new file mode 100644 index 00000000..2ad6cfde Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_share_large.png differ diff --git a/External/google-plus-ios-sdk/Resources/google_plus_share_large@2x.png b/External/google-plus-ios-sdk/Resources/google_plus_share_large@2x.png new file mode 100644 index 00000000..10546b27 Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_share_large@2x.png differ diff --git a/External/google-plus-ios-sdk/Resources/google_plus_sign_in.png b/External/google-plus-ios-sdk/Resources/google_plus_sign_in.png new file mode 100644 index 00000000..b2996c8a Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_sign_in.png differ diff --git a/External/google-plus-ios-sdk/Resources/google_plus_sign_in@2x.png b/External/google-plus-ios-sdk/Resources/google_plus_sign_in@2x.png new file mode 100644 index 00000000..6d62343e Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_sign_in@2x.png differ diff --git a/External/google-plus-ios-sdk/Resources/google_plus_sign_in_wide.png b/External/google-plus-ios-sdk/Resources/google_plus_sign_in_wide.png new file mode 100644 index 00000000..40b02bd8 Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_sign_in_wide.png differ diff --git a/External/google-plus-ios-sdk/Resources/google_plus_sign_in_wide@2x.png b/External/google-plus-ios-sdk/Resources/google_plus_sign_in_wide@2x.png new file mode 100644 index 00000000..b3a4e5c8 Binary files /dev/null and b/External/google-plus-ios-sdk/Resources/google_plus_sign_in_wide@2x.png differ diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSample-Info.plist b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample-Info.plist new file mode 100644 index 00000000..4c97df21 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample-Info.plist @@ -0,0 +1,74 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFiles + + Icon.png + Icon@2x.png + + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconFiles + + Icon.png + Icon@2x.png + + + + CFBundleIdentifier + com.google.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.google.GooglePlusSample + CFBundleURLSchemes + + com.google.GooglePlusSample + + + + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSample-Prefix.pch b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample-Prefix.pch new file mode 100644 index 00000000..bda6af71 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample-Prefix.pch @@ -0,0 +1,14 @@ +// +// Prefix header for all source files of the 'GooglePlusSample' target in the 'GooglePlusSample' project +// + +#import + +#ifndef __IPHONE_4_0 +#warning "This project uses features only available in iOS SDK 4.0 and later." +#endif + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.pbxproj b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c8d5eff4 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.pbxproj @@ -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 = ""; }; + D98254F1159937730060CA47 /* GTLPlusPerson.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusPerson.m; sourceTree = ""; }; + D9EE7431158A8BAE00EC1D05 /* GooglePlusShare.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GooglePlusShare.h; path = ../lib/GooglePlusShare.h; sourceTree = ""; }; + D9EE7432158A8BAE00EC1D05 /* GooglePlusSignIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GooglePlusSignIn.h; path = ../lib/GooglePlusSignIn.h; sourceTree = ""; }; + D9EE7433158A8BAE00EC1D05 /* GooglePlusSignInButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GooglePlusSignInButton.h; path = ../lib/GooglePlusSignInButton.h; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; + D9EE7437158A8BD400EC1D05 /* google_plus_share.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = google_plus_share.png; path = ../Resources/google_plus_share.png; sourceTree = ""; }; + 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 = ""; }; + D9EE7448158A8C0E00EC1D05 /* GTLBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBase64.h; sourceTree = ""; }; + D9EE7449158A8C0E00EC1D05 /* GTLBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBase64.m; sourceTree = ""; }; + D9EE744A158A8C0E00EC1D05 /* GTLBatchQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBatchQuery.h; sourceTree = ""; }; + D9EE744B158A8C0E00EC1D05 /* GTLBatchQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBatchQuery.m; sourceTree = ""; }; + D9EE744C158A8C0E00EC1D05 /* GTLBatchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBatchResult.h; sourceTree = ""; }; + D9EE744D158A8C0E00EC1D05 /* GTLBatchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBatchResult.m; sourceTree = ""; }; + D9EE744E158A8C0E00EC1D05 /* GTLDateTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLDateTime.h; sourceTree = ""; }; + D9EE744F158A8C0E00EC1D05 /* GTLDateTime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLDateTime.m; sourceTree = ""; }; + D9EE7450158A8C0E00EC1D05 /* GTLDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLDefines.h; sourceTree = ""; }; + D9EE7451158A8C0E00EC1D05 /* GTLErrorObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLErrorObject.h; sourceTree = ""; }; + D9EE7452158A8C0E00EC1D05 /* GTLErrorObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLErrorObject.m; sourceTree = ""; }; + D9EE7453158A8C0E00EC1D05 /* GTLFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLFramework.h; sourceTree = ""; }; + D9EE7454158A8C0E00EC1D05 /* GTLFramework.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLFramework.m; sourceTree = ""; }; + D9EE7455158A8C0E00EC1D05 /* GTLJSONParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLJSONParser.h; sourceTree = ""; }; + D9EE7456158A8C0E00EC1D05 /* GTLJSONParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLJSONParser.m; sourceTree = ""; }; + D9EE7457158A8C0E00EC1D05 /* GTLObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLObject.h; sourceTree = ""; }; + D9EE7458158A8C0E00EC1D05 /* GTLObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLObject.m; sourceTree = ""; }; + D9EE745A158A8C0E00EC1D05 /* GTLPlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlus.h; sourceTree = ""; }; + D9EE745B158A8C0E00EC1D05 /* GTLPlusConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusConstants.h; sourceTree = ""; }; + D9EE745C158A8C0E00EC1D05 /* GTLPlusConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusConstants.m; sourceTree = ""; }; + D9EE745D158A8C0E00EC1D05 /* GTLPlusItemScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusItemScope.h; sourceTree = ""; }; + D9EE745E158A8C0E00EC1D05 /* GTLPlusItemScope.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusItemScope.m; sourceTree = ""; }; + D9EE745F158A8C0E00EC1D05 /* GTLPlusMoment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusMoment.h; sourceTree = ""; }; + D9EE7460158A8C0E00EC1D05 /* GTLPlusMoment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusMoment.m; sourceTree = ""; }; + D9EE7463158A8C0E00EC1D05 /* GTLQueryPlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLQueryPlus.h; sourceTree = ""; }; + D9EE7464158A8C0E00EC1D05 /* GTLQueryPlus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLQueryPlus.m; sourceTree = ""; }; + D9EE7465158A8C0E00EC1D05 /* GTLServicePlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLServicePlus.h; sourceTree = ""; }; + D9EE7466158A8C0E00EC1D05 /* GTLServicePlus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLServicePlus.m; sourceTree = ""; }; + D9EE7467158A8C0E00EC1D05 /* GTLQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLQuery.h; sourceTree = ""; }; + D9EE7468158A8C0E00EC1D05 /* GTLQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLQuery.m; sourceTree = ""; }; + D9EE7469158A8C0E00EC1D05 /* GTLRuntimeCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLRuntimeCommon.h; sourceTree = ""; }; + D9EE746A158A8C0E00EC1D05 /* GTLRuntimeCommon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLRuntimeCommon.m; sourceTree = ""; }; + D9EE746B158A8C0E00EC1D05 /* GTLService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLService.h; sourceTree = ""; }; + D9EE746C158A8C0E00EC1D05 /* GTLService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLService.m; sourceTree = ""; }; + D9EE746D158A8C0E00EC1D05 /* GTLTargetNamespace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLTargetNamespace.h; sourceTree = ""; }; + D9EE746E158A8C0E00EC1D05 /* GTLUploadParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLUploadParameters.h; sourceTree = ""; }; + D9EE746F158A8C0E00EC1D05 /* GTLUploadParameters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLUploadParameters.m; sourceTree = ""; }; + D9EE7470158A8C0E00EC1D05 /* GTLUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLUtilities.h; sourceTree = ""; }; + D9EE7471158A8C0E00EC1D05 /* GTLUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLUtilities.m; sourceTree = ""; }; + D9EE7472158A8C0E00EC1D05 /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = ""; }; + D9EE7473158A8C0E00EC1D05 /* GTMGarbageCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGarbageCollection.h; sourceTree = ""; }; + D9EE7474158A8C0E00EC1D05 /* GTMHTTPFetcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcher.h; sourceTree = ""; }; + D9EE7475158A8C0E00EC1D05 /* GTMHTTPFetcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcher.m; sourceTree = ""; }; + D9EE7476158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcherLogging.h; sourceTree = ""; }; + D9EE7477158A8C0E00EC1D05 /* GTMHTTPFetcherLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherLogging.m; sourceTree = ""; }; + D9EE7478158A8C0E00EC1D05 /* GTMHTTPFetcherService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcherService.h; sourceTree = ""; }; + D9EE7479158A8C0E00EC1D05 /* GTMHTTPFetcherService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherService.m; sourceTree = ""; }; + D9EE747A158A8C0E00EC1D05 /* GTMHTTPFetchHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetchHistory.h; sourceTree = ""; }; + D9EE747B158A8C0E00EC1D05 /* GTMHTTPFetchHistory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetchHistory.m; sourceTree = ""; }; + D9EE747C158A8C0E00EC1D05 /* GTMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLogger.h; sourceTree = ""; }; + D9EE747D158A8C0E00EC1D05 /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLogger.m; sourceTree = ""; }; + D9EE747E158A8C0E00EC1D05 /* GTMMethodCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMMethodCheck.h; sourceTree = ""; }; + D9EE747F158A8C0E00EC1D05 /* GTMMethodCheck.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMMethodCheck.m; sourceTree = ""; }; + D9EE7480158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = ""; }; + D9EE7481158A8C0E00EC1D05 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = ""; }; + D9EE7482158A8C0E00EC1D05 /* GTMNSString+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+URLArguments.h"; sourceTree = ""; }; + D9EE7483158A8C0E00EC1D05 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = ""; }; + D9EE7484158A8C0E00EC1D05 /* GTMOAuth2Authentication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2Authentication.h; sourceTree = ""; }; + D9EE7485158A8C0E00EC1D05 /* GTMOAuth2Authentication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2Authentication.m; sourceTree = ""; }; + D9EE7486158A8C0E00EC1D05 /* GTMOAuth2SignIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2SignIn.h; sourceTree = ""; }; + D9EE7487158A8C0E00EC1D05 /* GTMOAuth2SignIn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2SignIn.m; sourceTree = ""; }; + D9EE7488158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2ViewControllerTouch.h; sourceTree = ""; }; + D9EE7489158A8C0E00EC1D05 /* GTMOAuth2ViewControllerTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2ViewControllerTouch.m; sourceTree = ""; }; + D9EE748A158A8C0E00EC1D05 /* GTMOAuth2ViewTouch.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GTMOAuth2ViewTouch.xib; sourceTree = ""; }; + D9EE748B158A8C0E00EC1D05 /* GTMObjC2Runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMObjC2Runtime.h; sourceTree = ""; }; + D9EE748C158A8C0E00EC1D05 /* GTMObjC2Runtime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMObjC2Runtime.m; sourceTree = ""; }; + D9EE74AD158A8D1E00EC1D05 /* libGooglePlus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libGooglePlus.a; path = ../lib/libGooglePlus.a; sourceTree = ""; }; + D9EE74AE158A8D1E00EC1D05 /* libGooglePlusUniversal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libGooglePlusUniversal.a; path = ../lib/libGooglePlusUniversal.a; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; +/* 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 = ""; + }; + 0043C79B1580045B000DF02E /* Products */ = { + isa = PBXGroup; + children = ( + 0043C79A1580045B000DF02E /* GooglePlusSample.app */, + ); + name = Products; + sourceTree = ""; + }; + 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 = ""; + }; + 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 = ""; + }; + 0043C7A51580045B000DF02E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00F70E82158006DC0077799E /* main.m */, + D9EE74CB158A8E2900EC1D05 /* InfoPlist.strings */, + D9EE74B1158A8E0500EC1D05 /* GooglePlusSample-Info.plist */, + D9EE74B2158A8E0500EC1D05 /* GooglePlusSample-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D98254AB15990DBC0060CA47 /* Resources */ = { + isa = PBXGroup; + children = ( + D98254A615990D8D0060CA47 /* Icon_2x.png */, + D98254A715990D8D0060CA47 /* Icon.png */, + 0C52D6F7158BAB1F001510E6 /* button_background.png */, + ); + name = Resources; + sourceTree = ""; + }; + 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 = ""; + }; + 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 = ""; + }; + 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 = ""; + }; + 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 = ""; + }; + 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 = ""; + }; +/* 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 = ""; + }; +/* 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 */; +} diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..87d7bd1d --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.xcworkspace/xcuserdata/xiangtian.xcuserdatad/UserInterfaceState.xcuserstate b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.xcworkspace/xcuserdata/xiangtian.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..c4253c19 Binary files /dev/null and b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/project.xcworkspace/xcuserdata/xiangtian.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/xcuserdata/xiangtian.xcuserdatad/xcschemes/GooglePlusSample.xcscheme b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/xcuserdata/xiangtian.xcuserdatad/xcschemes/GooglePlusSample.xcscheme new file mode 100644 index 00000000..da367a26 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/xcuserdata/xiangtian.xcuserdatad/xcschemes/GooglePlusSample.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/xcuserdata/xiangtian.xcuserdatad/xcschemes/xcschememanagement.plist b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/xcuserdata/xiangtian.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..b6fa4a06 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSample.xcodeproj/xcuserdata/xiangtian.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + GooglePlusSample.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 0043C7991580045B000DF02E + + primary + + + + + diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleAppDelegate.h b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleAppDelegate.h new file mode 100644 index 00000000..7da9a72a --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleAppDelegate.h @@ -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 + +@class GooglePlusShare; +@class GooglePlusSignInButton; +@class GTMOAuth2Authentication; + +@interface GooglePlusSampleAppDelegate : UIResponder + +// 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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleAppDelegate.m b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleAppDelegate.m new file mode 100644 index 00000000..f4024ad1 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleAppDelegate.m @@ -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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.h b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.h new file mode 100644 index 00000000..2d3b166f --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.h @@ -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 + +@interface GooglePlusSampleMasterViewController : UITableViewController + +@end diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.m b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.m new file mode 100644 index 00000000..b15ab22a --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.m @@ -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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.xib b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.xib new file mode 100644 index 00000000..7f4463c7 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMasterViewController.xib @@ -0,0 +1,251 @@ + + + + 1280 + 10K549 + 1938 + 1038.36 + 461.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 933 + + + IBUINavigationItem + IBUITableView + IBUITableViewController + IBUINavigationController + IBUINavigationBar + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + {{0, 20}, {320, 460}} + + + 3 + MQA + + YES + + IBCocoaTouchFramework + YES + 1 + 0 + YES + 44 + 22 + 22 + + + + + 1 + 1 + + IBCocoaTouchFramework + NO + + + 256 + {0, 0} + NO + YES + YES + IBCocoaTouchFramework + + + + + + 274 + {{0, 64}, {320, 416}} + + + + + NO + YES + NO + IBCocoaTouchFramework + YES + 1 + 0 + YES + 44 + 22 + 22 + + + + Google Plus Sample App + IBCocoaTouchFramework + + + + + 1 + 1 + + IBCocoaTouchFramework + NO + + + + + + + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + delegate + + + + 12 + + + + dataSource + + + + 13 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + 6 + + + + + + + + + 7 + + + + + 10 + + + + + + + + + 11 + + + + + 14 + + + + + + + GooglePlusSampleMasterViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 14 + + + + + GooglePlusSampleMasterViewController + UITableViewController + + IBProjectSource + ./Classes/GooglePlusSampleMasterViewController.h + + + + + 0 + IBCocoaTouchFramework + YES + 3 + 933 + + diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.h b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.h new file mode 100644 index 00000000..2e0ad98d --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.h @@ -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 + +@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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.m b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.m new file mode 100644 index 00000000..d4a5172f --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.m @@ -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 +#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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.xib b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.xib new file mode 100644 index 00000000..791b6beb --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleMomentsViewController.xib @@ -0,0 +1,462 @@ + + + + 1280 + 10K549 + 1938 + 1038.36 + 461.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 933 + + + IBUITextField + IBUITableView + IBUIButton + IBUIView + IBUILabel + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 290 + {{10, 5}, {296, 21}} + + + + NO + YES + 7 + NO + IBCocoaTouchFramework + Select an activity (scroll for more) + + 1 + MCAwIDAAA + + + 1 + 10 + + 1 + 17 + + + Helvetica + 17 + 16 + + + + + 274 + {{0, 32}, {320, 132}} + + + + + 3 + MQA + + YES + IBCocoaTouchFramework + YES + 1 + 0 + YES + 44 + 22 + 22 + + + + 266 + + + + 266 + {{11, 20}, {296, 21}} + + + + NO + YES + 7 + NO + IBCocoaTouchFramework + Enter a Moment URL + + + 1 + 10 + + + + + + 266 + {{11, 51}, {291, 31}} + + + + NO + YES + IBCocoaTouchFramework + 0 + + 3 + + 3 + MAA + + 2 + + + YES + 17 + + IBCocoaTouchFramework + + + 1 + 14 + + + Helvetica + 14 + 16 + + + + + 264 + {{12, 113}, {142, 37}} + + + + NO + IBCocoaTouchFramework + 0 + 0 + Add Moment + + 1 + MC40MzkyMTU2ODYzIDAuMTI1NDkwMTk2MSAwLjA2Mjc0NTA5ODA0AA + + + 1 + MC42MzUyOTQxMTc2IDAuMzIxNTY4NjI3NSAwLjI1ODgyMzUyOTQAA + + + 3 + MC41AA + + + NSImage + button_background.png + + + 2 + 15 + + + Helvetica-Bold + 15 + 16 + + + + + 266 + {{11, 180}, {290, 21}} + + + + NO + YES + 7 + NO + IBCocoaTouchFramework + Status: + + + 1 + 11 + + + + + {{0, 172}, {320, 244}} + + + + + IBCocoaTouchFramework + + + {{0, 64}, {320, 416}} + + + + + 3 + MQA + + + + + + NO + + IBCocoaTouchFramework + + + + + + + view + + + + 3 + + + + momentsTable + + + + 17 + + + + momentStatus + + + + 25 + + + + addButton + + + + 28 + + + + momentURL + + + + 30 + + + + dataSource + + + + 18 + + + + delegate + + + + 19 + + + + delegate + + + + 31 + + + + momentButton: + + + 7 + + 20 + + + + + + 0 + + + + + + 1 + + + + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 7 + + + + + + 26 + + + + + 27 + + + + + + + + + + + 13 + + + + + 12 + + + + + 16 + + + + + 22 + + + + + + + GooglePlusSampleMomentsViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 31 + + + + + GooglePlusSampleMomentsViewController + UIViewController + + momentButton: + id + + + momentButton: + + momentButton: + id + + + + UIButton + UILabel + UITextField + UITableView + + + + addButton + UIButton + + + momentStatus + UILabel + + + momentURL + UITextField + + + momentsTable + UITableView + + + + IBProjectSource + ./Classes/GooglePlusSampleMomentsViewController.h + + + + + 0 + IBCocoaTouchFramework + YES + 3 + + button_background.png + {1, 1} + + 933 + + diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.h b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.h new file mode 100644 index 00000000..3feca972 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.h @@ -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 +#import +#import +#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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.m b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.m new file mode 100644 index 00000000..2e93562e --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.m @@ -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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.xib b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.xib new file mode 100644 index 00000000..de51b338 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleShareViewController.xib @@ -0,0 +1,493 @@ + + + + 1280 + 10K549 + 1938 + 1038.36 + 461.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 933 + + + IBUIView + IBProxyObject + IBUILabel + IBUIToolbar + IBUIBarButtonItem + IBUITextField + IBUIButton + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 292 + {{20, 55}, {280, 31}} + + + + NO + YES + IBCocoaTouchFramework + 0 + http://developers.google.com/ + 3 + + 3 + MAA + + 2 + + + YES + 17 + + IBCocoaTouchFramework + + + 1 + 14 + + + Helvetica + 14 + 16 + + + + + 292 + {{20, 144}, {280, 31}} + + + + NO + YES + IBCocoaTouchFramework + 0 + Welcome to Google+ Platform + 3 + + 3 + MAA + + + YES + 17 + + IBCocoaTouchFramework + + + + + + + 292 + {{20, 26}, {179, 21}} + + + + NO + YES + 7 + NO + IBCocoaTouchFramework + URL to Share (optional) + + 1 + MCAwIDAAA + + + 1 + 10 + + 1 + 17 + + + Helvetica + 17 + 16 + + + + + 292 + {{20, 115}, {156, 21}} + + + + NO + YES + 7 + NO + IBCocoaTouchFramework + Prefill Text (optional) + + + 1 + 10 + + + + + + 292 + {{21, 209}, {112, 32}} + + + + NO + IBCocoaTouchFramework + 0 + 0 + + 3 + MQA + + + 1 + MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA + + + 3 + MC41AA + + + NSImage + google_plus_share_large.png + + + 2 + 15 + + + Helvetica-Bold + 15 + 16 + + + + + 290 + {{20, 283}, {280, 21}} + + + + NO + YES + 7 + NO + IBCocoaTouchFramework + Status: + + + 1 + 15 + + + + + + 266 + {{0, 372}, {320, 44}} + + + + NO + NO + IBCocoaTouchFramework + + + IBCocoaTouchFramework + + 5 + + + IBCocoaTouchFramework + 1 + + 9 + + + + + {{0, 64}, {320, 416}} + + + + + 3 + MQA + + + + + NO + + IBCocoaTouchFramework + + + + + + + view + + + + 3 + + + + sharePrefillText + + + + 11 + + + + shareStatus + + + + 14 + + + + shareToolbar + + + + 19 + + + + shareURL + + + + 21 + + + + delegate + + + + 23 + + + + delegate + + + + 22 + + + + shareButton: + + + 7 + + 10 + + + + shareToolbar: + + + + 20 + + + + + + 0 + + + + + + 1 + + + + + + + + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + 9 + + + + + 13 + + + + + 15 + + + + + + + + + 17 + + + + + 18 + + + + + + + GooglePlusSampleShareViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 23 + + + + + GooglePlusSampleShareViewController + UIViewController + + id + id + + + + shareButton: + id + + + shareToolbar: + id + + + + UITextField + UILabel + UIToolbar + UITextField + + + + sharePrefillText + UITextField + + + shareStatus + UILabel + + + shareToolbar + UIToolbar + + + shareURL + UITextField + + + + IBProjectSource + ./Classes/GooglePlusSampleShareViewController.h + + + + + 0 + IBCocoaTouchFramework + YES + 3 + + google_plus_share_large.png + {112, 32} + + 933 + + diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.h b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.h new file mode 100644 index 00000000..942fbb75 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.h @@ -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 +#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 diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.m b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.m new file mode 100644 index 00000000..a9e78412 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.m @@ -0,0 +1,110 @@ +// +// GooglePlusSignInViewController.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 "GooglePlusSampleSignInViewController.h" + +#import +#import "GooglePlusSampleAppDelegate.h" +#import "GooglePlusSignIn.h" +#import "GooglePlusSignInButton.h" + +@implementation GooglePlusSampleSignInViewController + +@synthesize signInButton = signInButton_; +@synthesize signInAuthStatus = signInAuthStatus_; +@synthesize signOutButton = signOutButton_; + +- (void)dealloc { + [signInButton_ release]; + [signInAuthStatus_ release]; + [signOutButton_ release]; + [super dealloc]; +} + +#pragma mark - View lifecycle + +- (void)reportAuthStatus { + GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *) + [[UIApplication sharedApplication] delegate]; + if (appDelegate.auth) { + signInAuthStatus_.text = @"Status: Authenticated"; + } else { + // To authenticate, use Google+ sign-in button. + signInAuthStatus_.text = @"Status: Not authenticated"; + } +} + +- (void)viewDidLoad { + // Set up sign-out button. + [[signOutButton_ layer] setCornerRadius:5]; + [[signOutButton_ layer] setMasksToBounds:YES]; + CGColorRef borderColor = [[UIColor colorWithWhite:203.0/255.0 + alpha:1.0] CGColor]; + [[signOutButton_ layer] setBorderColor:borderColor]; + [[signOutButton_ layer] setBorderWidth:1.0]; + + // Set up sample view of Google+ sign-in. + signInButton_.delegate = self; + signInButton_.clientID = [GooglePlusSampleAppDelegate clientID]; + signInButton_.scope = [NSArray arrayWithObjects: + @"https://www.googleapis.com/auth/plus.moments.write", + @"https://www.googleapis.com/auth/plus.me", + nil]; + + GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *) + [[UIApplication sharedApplication] delegate]; + appDelegate.signInButton = signInButton_; + [self reportAuthStatus]; + [super viewDidLoad]; +} + +- (void)viewDidUnload { + GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *) + [[UIApplication sharedApplication] delegate]; + appDelegate.signInButton = nil; + [super viewDidUnload]; +} + +#pragma mark - GooglePlusSignInDelegate + +- (void)finishedWithAuth:(GTMOAuth2Authentication *)auth + error:(NSError *)error { + if (error) { + signInAuthStatus_.text = + [NSString stringWithFormat:@"Status: Authentication error: %@", error]; + return; + } + GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *) + [[UIApplication sharedApplication] delegate]; + appDelegate.auth = auth; + [self reportAuthStatus]; +} + +#pragma mark - IBActions + +- (IBAction)signOut:(id)sender { + [[signInButton_ googlePlusSignIn] signOut]; + + GooglePlusSampleAppDelegate *appDelegate = (GooglePlusSampleAppDelegate *) + [[UIApplication sharedApplication] delegate]; + appDelegate.auth = nil; + + [self reportAuthStatus]; +} + +@end diff --git a/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.xib b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.xib new file mode 100644 index 00000000..e4e828f7 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/GooglePlusSampleSignInViewController.xib @@ -0,0 +1,256 @@ + + + + 1280 + 10K549 + 1938 + 1038.36 + 461.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 933 + + + IBUIButton + IBUIView + IBUILabel + IBProxyObject + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 292 + {{19, 93}, {119, 35}} + + + + NO + IBCocoaTouchFramework + 0 + 0 + Sign out + + 1 + MC42MzUyOTQxMTc2IDAuMzIxNTY4NjI3NSAwLjI1ODgyMzUyOTQAA + + + 1 + MC42MzUyOTQxMTc2IDAuMzIxNTY4NjI3NSAwLjI1ODgyMzUyOTQAA + + + 3 + MC41AA + + + NSImage + button_background.png + + + 2 + 15 + + + Helvetica-Bold + 15 + 16 + + + + + 292 + {{18, 34}, {118, 32}} + + + + + 3 + MQA + + 2 + + + IBCocoaTouchFramework + + + + 290 + {{20, 163}, {280, 21}} + + + + NO + YES + 7 + NO + IBCocoaTouchFramework + Status: + + 1 + MCAwIDAAA + + + 1 + 13 + + 1 + 17 + + + Helvetica + 17 + 16 + + + + {{0, 20}, {320, 460}} + + + + + 3 + MQA + + + + IBCocoaTouchFramework + + + + + + + view + + + + 4 + + + + signInButton + + + + 14 + + + + signInAuthStatus + + + + 23 + + + + signOutButton + + + + 24 + + + + signOut: + + + 7 + + 26 + + + + + + 0 + + + + + + 1 + + + + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 7 + + + + + 6 + + + + + 22 + + + + + + + GooglePlusSampleSignInViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + GooglePlusSignInButton + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 26 + + + 0 + IBCocoaTouchFramework + YES + 3 + + button_background.png + {1, 1} + + 933 + + diff --git a/External/google-plus-ios-sdk/SampleCode/Resources/Icon.png b/External/google-plus-ios-sdk/SampleCode/Resources/Icon.png new file mode 100644 index 00000000..8b72f13f Binary files /dev/null and b/External/google-plus-ios-sdk/SampleCode/Resources/Icon.png differ diff --git a/External/google-plus-ios-sdk/SampleCode/Resources/Icon_2x.png b/External/google-plus-ios-sdk/SampleCode/Resources/Icon_2x.png new file mode 100644 index 00000000..4c308280 Binary files /dev/null and b/External/google-plus-ios-sdk/SampleCode/Resources/Icon_2x.png differ diff --git a/External/google-plus-ios-sdk/SampleCode/Resources/button_background.png b/External/google-plus-ios-sdk/SampleCode/Resources/button_background.png new file mode 100644 index 00000000..b63e8719 Binary files /dev/null and b/External/google-plus-ios-sdk/SampleCode/Resources/button_background.png differ diff --git a/External/google-plus-ios-sdk/SampleCode/en.lproj/InfoPlist.strings b/External/google-plus-ios-sdk/SampleCode/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..477b28ff --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/External/google-plus-ios-sdk/SampleCode/main.m b/External/google-plus-ios-sdk/SampleCode/main.m new file mode 100644 index 00000000..98dfc495 --- /dev/null +++ b/External/google-plus-ios-sdk/SampleCode/main.m @@ -0,0 +1,28 @@ +// +// main.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 + +#import "GooglePlusSampleAppDelegate.h" + +int main(int argc, char *argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, + NSStringFromClass([GooglePlusSampleAppDelegate class])); + } +} diff --git a/External/google-plus-ios-sdk/lib/GooglePlusShare.h b/External/google-plus-ios-sdk/lib/GooglePlusShare.h new file mode 100644 index 00000000..90fe2ae9 --- /dev/null +++ b/External/google-plus-ios-sdk/lib/GooglePlusShare.h @@ -0,0 +1,112 @@ +// +// GooglePlusShare.h +// Google+ iOS SDK +// +// Copyright 2012 Google Inc. +// +// Usage of this SDK is subject to the Google+ Platform Terms of Service: +// https://developers.google.com/+/terms +// + +// To allow a user to share with Google+, please follow these steps: +// +// 0. Create a project on Google APIs console, +// https://code.google.com/apis/console . Under "API Access", create a +// client ID as "Installed application" with the type "iOS", and +// register the bundle ID of your application. +// +// 1. Initialize a GooglePlusShare instance with your registered client ID: +// +// GooglePlusShare *googlePlusShare = +// [[GooglePlusShare alloc] initWithClientID:myClientID]; +// +// 2. In the code where the share dialog is to be opened: +// +// [[googlePlusShare shareDialog] open]; +// +// You may optionally call |setURLToShare:| and/or |setPrefillText:| before +// calling |open|, if there is a particular URL resource to be shared, or +// you want to set text to prefill user comment in the share dialog, e.g. +// +// NSURL *urlToShare = [NSURL URLWithString:@"http://www.google.com/"]; +// NSString *prefillText = @"You probably already know this site..."; +// [[[[googlePlusShare shareDialog] setURLToShare:urlToShare] +// setPrefillText:prefillText] open]; +// +// 3. In the 'YourApp-info.plist' settings for your application, add a URL +// type to be handled by your application. Make the URL scheme the same as +// the bundle ID of your application. +// +// 4. In your application delegate, implement +// - (BOOL)application:(NSString*)application +// openURL:(NSURL *)url +// sourceApplication:(NSString*)sourceApplication +// annotation:(id)annotation { +// if ([googlePlusShare handleURL:url +// sourceApplication:sourceApplication +// annotation:annotation]) { +// return YES; +// } +// // Other handling code here... +// } +// +// 5. Optionally, if you want to be notified of the result of the share action, +// have a delegate class implement |GooglePlusShareDelegate|, e.g. +// +// @interface MyDelegateClass : NSObject; +// +// - (void)finishedSharing:(BOOL)shared { +// // The share action was successful if |shared| is YES. +// } +// +// MyDelegateClass *myDelegate = [[MyDelegateClass alloc] init]; +// googlePlusShare.delegate = myDelegate; + +#import + +// Protocol to receive the result of the share action. +@protocol GooglePlusShareDelegate + +// Reports the status of the share action, |shared| is |YES| if user has +// successfully shared her post, |NO| otherwise, e.g. user canceled the post. +- (void)finishedSharing:(BOOL)shared; + +@end + +// The builder protocol to open the share dialog. +@protocol GooglePlusShareBuilder + +// Sets the URL resource to be shared. +- (id)setURLToShare:(NSURL *)urlToShare; + +// Sets the text to prefill user comment in the share dialog. +- (id)setPrefillText:(NSString *)prefillText; + +// Opens the share dialog. +- (void)open; + +@end + +// The primary class for the share action on Google+. +@interface GooglePlusShare : NSObject + +// The object to be notified when the share action has finished. +@property (nonatomic, assign) id delegate; + +// All Google+ objects must be initialized with a client ID registered +// in the Google APIs console, https://code.google.com/apis/console/ +// with their corresponding bundle ID before they can be used. +- (id)initWithClientID:(NSString *)clientID; + +// Returns a share dialog builder instance. Call its |open| method to +// create the dialog after setting the parameters as needed. +- (id)shareDialog; + +// This method should be called from your |UIApplicationDelegate|'s +// |application:openURL:sourceApplication:annotation|. Returns |YES| if +// |GooglePlusShare| handled this URL. +- (BOOL)handleURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation; + +@end diff --git a/External/google-plus-ios-sdk/lib/GooglePlusSignIn.h b/External/google-plus-ios-sdk/lib/GooglePlusSignIn.h new file mode 100644 index 00000000..d68edc67 --- /dev/null +++ b/External/google-plus-ios-sdk/lib/GooglePlusSignIn.h @@ -0,0 +1,67 @@ +// +// GooglePlusSignIn.h +// Google+ iOS SDK +// +// Copyright 2012 Google Inc. +// +// Usage of this SDK is subject to the Google+ Platform Terms of Service: +// https://developers.google.com/+/terms +// + +#import + +@class GTMOAuth2Authentication; +@class GTMOAuth2ViewControllerTouch; + +// Protocol implemented by the client of GooglePlusSignIn to receive a refresh +// token or an error. It is up to the client to present the OAuth 2.0 view +// controller if single sign-on is disabled via |attemptSSO| in |authenticate|. +@protocol GooglePlusSignInDelegate + +// Authorization has finished and is successful if |error| is |nil|. +- (void)finishedWithAuth:(GTMOAuth2Authentication *)auth + error:(NSError *)error; + +@end + +// |GooglePlusSignIn| signs the user in with Google+. It provides single sign-on +// via the Google+ app, if installed, or Mobile Safari. +// Here is sample code to use GooglePlusSignIn: +// 1) GooglePlusSignIn *signIn = +// [[GooglePlusSignIn alloc] initForClientID:clientID +// language:@"en" +// scope:@"https://www.googleapis.com/auth/plus.me" +// keychainName:nil]; +// [signIn setDelegate:self]; +// 2) Setup delegate methods |finishedWithAuth|, etc. +// 3) Call |handleURL| from |application:openUrl:...| in your app delegate. +// 4) [auth authenticate:YES]; +@interface GooglePlusSignIn : NSObject + +// The object to be notified when authentication is finished. +@property (nonatomic, assign) id delegate; + +// Initializes with your |clientID| from the Google APIs console. Set |scope| to +// an array of your API scopes. Set |keychainName| to |nil| to use the default +// name. +- (id)initWithClientID:(NSString *)clientID + language:(NSString *)language + scope:(NSArray *)scope + keychainName:(NSString *)keychainName; + +// Starts the authentication process. Set |attemptSSO| to try single sign-on. +// Set |clearKeychain| to remove previously stored authentication in the +// keychain. +- (void)authenticate:(BOOL)attemptSSO clearKeychain:(BOOL)clearKeychain; + +// This method should be called from your |UIApplicationDelegate|'s +// |application:openURL:sourceApplication:annotation|. Returns |YES| if +// |GooglePlusSignIn| handled this URL. +- (BOOL)handleURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation; + +// Removes the OAuth 2.0 token from the keychain. +- (void)signOut; + +@end diff --git a/External/google-plus-ios-sdk/lib/GooglePlusSignInButton.h b/External/google-plus-ios-sdk/lib/GooglePlusSignInButton.h new file mode 100644 index 00000000..b73f8c83 --- /dev/null +++ b/External/google-plus-ios-sdk/lib/GooglePlusSignInButton.h @@ -0,0 +1,52 @@ +// +// GooglePlusSignInButton.h +// Google+ iOS SDK +// +// Copyright 2012 Google Inc. +// +// Usage of this SDK is subject to the Google+ Platform Terms of Service: +// https://developers.google.com/+/terms +// + +#import + +@class GooglePlusSignIn; +@protocol GooglePlusSignInDelegate; + +// The various visual styles supported by the GooglePlusSignInButton. +typedef enum { + kGooglePlusSignInButtonStyleNormal, + kGooglePlusSignInButtonStyleWide +} GooglePlusSignInButtonStyle; + +// A view that displays the Google+ sign-in button. You can instantiate this +// class programmatically or from a NIB file. Once instantiated, you should +// set the client ID and delegate properties and add this view to your own view +// hierarchy. +@interface GooglePlusSignInButton : UIView + +// The OAuth 2.0 client ID of the application. +@property(nonatomic, copy) NSString *clientID; + +// See GooglePlusSignIn.h for details on this delegate. +@property(nonatomic, assign) id delegate; + +// Actually does the work of signing in with Google+. +@property(nonatomic, readonly) GooglePlusSignIn *googlePlusSignIn; + +// The OAuth 2.0 scopes for the APIs that you are using. This is used to fetch +// an OAuth 2.0 token. By default, this is set to the +// https://www.googleapis.com/auth/plus.me scope. +@property(nonatomic, copy) NSArray *scope; + +// Sets the sign-in button. The default style is normal. +- (void)setStyle:(GooglePlusSignInButtonStyle)style; + +// This method should be called from your |UIApplicationDelegate|'s +// |application:openURL:sourceApplication:annotation|. Returns |YES| if +// |GooglePlusSignInButton| handled this URL. +- (BOOL)handleURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation; + +@end diff --git a/External/google-plus-ios-sdk/lib/libGooglePlus.a b/External/google-plus-ios-sdk/lib/libGooglePlus.a new file mode 100644 index 00000000..1824ec72 Binary files /dev/null and b/External/google-plus-ios-sdk/lib/libGooglePlus.a differ diff --git a/External/google-plus-ios-sdk/lib/libGooglePlusUniversal.a b/External/google-plus-ios-sdk/lib/libGooglePlusUniversal.a new file mode 100644 index 00000000..47d4862b Binary files /dev/null and b/External/google-plus-ios-sdk/lib/libGooglePlusUniversal.a differ diff --git a/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword-iOS.xcodeproj/project.pbxproj index 1674e1e4..10468019 100644 --- a/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -9,8 +9,13 @@ /* Begin PBXBuildFile section */ 93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E81EFABC6085AC8AE69 /* MPKey.m */; }; 93D392B30CE6C58A9A905E0A /* MPAlgorithmV0.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */; }; + 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; }; 93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */; }; + 93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; }; + 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; }; + 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E9D7B9005211E7D5262 /* MPAlgorithmV1.m */; }; + 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; }; DA0A1D0515690A9A0092735D /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0315690A9A0092735D /* Default.png */; }; DA0A1D0615690A9A0092735D /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D0415690A9A0092735D /* Default@2x.png */; }; @@ -37,9 +42,63 @@ DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4425CB1557BED40052177D /* libiCloudStoreManager.a */; }; DA46826F15AB843200FB09E7 /* tip_basic_black_bottom_right.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */; }; DA46827015AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */; }; + DA497B9815E8C90E00B52167 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; + DA497BED15E8C94300B52167 /* GTLBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BA915E8C94300B52167 /* GTLBase64.m */; }; + DA497BEE15E8C94300B52167 /* GTLBatchQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BAB15E8C94300B52167 /* GTLBatchQuery.m */; }; + DA497BEF15E8C94300B52167 /* GTLBatchResult.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BAD15E8C94300B52167 /* GTLBatchResult.m */; }; + DA497BF015E8C94300B52167 /* GTLDateTime.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BAF15E8C94300B52167 /* GTLDateTime.m */; }; + DA497BF115E8C94300B52167 /* GTLErrorObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BB215E8C94300B52167 /* GTLErrorObject.m */; }; + DA497BF215E8C94300B52167 /* GTLFramework.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BB415E8C94300B52167 /* GTLFramework.m */; }; + DA497BF315E8C94300B52167 /* GTLJSONParser.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BB615E8C94300B52167 /* GTLJSONParser.m */; }; + DA497BF415E8C94300B52167 /* GTLObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BB815E8C94300B52167 /* GTLObject.m */; }; + DA497BF515E8C94300B52167 /* GTLPlusConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BBC15E8C94300B52167 /* GTLPlusConstants.m */; }; + DA497BF615E8C94300B52167 /* GTLPlusItemScope.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BBE15E8C94300B52167 /* GTLPlusItemScope.m */; }; + DA497BF715E8C94300B52167 /* GTLPlusMoment.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BC015E8C94300B52167 /* GTLPlusMoment.m */; }; + DA497BF815E8C94300B52167 /* GTLPlusPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BC215E8C94300B52167 /* GTLPlusPerson.m */; }; + DA497BF915E8C94300B52167 /* GTLQueryPlus.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BC415E8C94300B52167 /* GTLQueryPlus.m */; }; + DA497BFA15E8C94300B52167 /* GTLServicePlus.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BC615E8C94300B52167 /* GTLServicePlus.m */; }; + DA497BFB15E8C94300B52167 /* GTLQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BC815E8C94300B52167 /* GTLQuery.m */; }; + DA497BFC15E8C94300B52167 /* GTLRuntimeCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BCA15E8C94300B52167 /* GTLRuntimeCommon.m */; }; + DA497BFD15E8C94300B52167 /* GTLService.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BCC15E8C94300B52167 /* GTLService.m */; }; + DA497BFE15E8C94300B52167 /* GTLUploadParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BCF15E8C94300B52167 /* GTLUploadParameters.m */; }; + DA497BFF15E8C94300B52167 /* GTLUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BD115E8C94300B52167 /* GTLUtilities.m */; }; + DA497C0015E8C94300B52167 /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BD515E8C94300B52167 /* GTMHTTPFetcher.m */; }; + DA497C0115E8C94300B52167 /* GTMHTTPFetcherLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BD715E8C94300B52167 /* GTMHTTPFetcherLogging.m */; }; + DA497C0215E8C94300B52167 /* GTMHTTPFetcherService.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BD915E8C94300B52167 /* GTMHTTPFetcherService.m */; }; + DA497C0315E8C94300B52167 /* GTMHTTPFetchHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BDB15E8C94300B52167 /* GTMHTTPFetchHistory.m */; }; + DA497C0415E8C94300B52167 /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BDD15E8C94300B52167 /* GTMLogger.m */; }; + DA497C0515E8C94300B52167 /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BDF15E8C94300B52167 /* GTMMethodCheck.m */; }; + DA497C0615E8C94300B52167 /* GTMNSDictionary+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BE115E8C94300B52167 /* GTMNSDictionary+URLArguments.m */; }; + DA497C0715E8C94300B52167 /* GTMNSString+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BE315E8C94300B52167 /* GTMNSString+URLArguments.m */; }; + DA497C0815E8C94300B52167 /* GTMOAuth2Authentication.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BE515E8C94300B52167 /* GTMOAuth2Authentication.m */; }; + DA497C0915E8C94300B52167 /* GTMOAuth2SignIn.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BE715E8C94300B52167 /* GTMOAuth2SignIn.m */; }; + DA497C0A15E8C94300B52167 /* GTMOAuth2ViewControllerTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BE915E8C94300B52167 /* GTMOAuth2ViewControllerTouch.m */; }; + DA497C0B15E8C94300B52167 /* GTMObjC2Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = DA497BEC15E8C94300B52167 /* GTMObjC2Runtime.m */; }; + DA497C0C15E8C95700B52167 /* libGoogle+.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA497B9715E8C90E00B52167 /* libGoogle+.a */; }; + DA497C0F15E8C9CF00B52167 /* libGooglePlusUniversal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5587FC15E8B7B200860B4F /* libGooglePlusUniversal.a */; }; DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6326C148680650075AEA5 /* libjrswizzle.a */; }; DA4DA1DA1564471F00F6F596 /* libuicolor-utilities.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */; }; DA4DA1DB1564475E00F6F596 /* libscryptenc-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA79A9BB1557DB6F00BAA07A /* libscryptenc-ios.a */; }; + DA55873F15E81D9E00860B4F /* iTunesArtwork-Rounded-73.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55873E15E81D9E00860B4F /* iTunesArtwork-Rounded-73.png */; }; + DA55878015E82C0300860B4F /* FacebookSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA55877F15E82C0300860B4F /* FacebookSDK.framework */; }; + DA55878215E82C2B00860B4F /* FacebookSDKResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DA55878115E82C2B00860B4F /* FacebookSDKResources.bundle */; }; + DA5587EB15E83C3200860B4F /* social-facebook.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587E515E83C3200860B4F /* social-facebook.png */; }; + DA5587EC15E83C3200860B4F /* social-facebook@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587E615E83C3200860B4F /* social-facebook@2x.png */; }; + DA5587ED15E83C3200860B4F /* social-google+.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587E715E83C3200860B4F /* social-google+.png */; }; + DA5587EE15E83C3200860B4F /* social-google+@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587E815E83C3200860B4F /* social-google+@2x.png */; }; + DA5587EF15E83C3200860B4F /* social-twitter.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587E915E83C3200860B4F /* social-twitter.png */; }; + DA5587F015E83C3200860B4F /* social-twitter@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587EA15E83C3200860B4F /* social-twitter@2x.png */; }; + DA5587F415E8418200860B4F /* iTunesArtwork-Rounded-73@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587F115E8418200860B4F /* iTunesArtwork-Rounded-73@2x.png */; }; + DA5587F515E8418200860B4F /* iTunesArtwork-Rounded-256.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587F215E8418200860B4F /* iTunesArtwork-Rounded-256.png */; }; + DA5587F615E8418200860B4F /* iTunesArtwork-Rounded.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5587F315E8418200860B4F /* iTunesArtwork-Rounded.png */; }; + DA55888415E8C0BA00860B4F /* google_plus_share.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55887C15E8C0BA00860B4F /* google_plus_share.png */; }; + DA55888515E8C0BA00860B4F /* google_plus_share@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55887D15E8C0BA00860B4F /* google_plus_share@2x.png */; }; + DA55888615E8C0BA00860B4F /* google_plus_share_large.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55887E15E8C0BA00860B4F /* google_plus_share_large.png */; }; + DA55888715E8C0BA00860B4F /* google_plus_share_large@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55887F15E8C0BA00860B4F /* google_plus_share_large@2x.png */; }; + DA55888815E8C0BA00860B4F /* google_plus_sign_in.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55888015E8C0BA00860B4F /* google_plus_sign_in.png */; }; + DA55888915E8C0BA00860B4F /* google_plus_sign_in@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55888115E8C0BA00860B4F /* google_plus_sign_in@2x.png */; }; + DA55888A15E8C0BA00860B4F /* google_plus_sign_in_wide.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55888215E8C0BA00860B4F /* google_plus_sign_in_wide.png */; }; + DA55888B15E8C0BA00860B4F /* google_plus_sign_in_wide@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA55888315E8C0BA00860B4F /* google_plus_sign_in_wide@2x.png */; }; DA5BFA49147E415C00F98B1E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA48147E415C00F98B1E /* UIKit.framework */; }; DA5BFA4B147E415C00F98B1E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DA5BFA4D147E415C00F98B1E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */; }; @@ -890,9 +949,26 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + DA497B9515E8C90E00B52167 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/${PRODUCT_NAME}"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = ""; }; 93D3938863322199C3E7E2E3 /* MPAlgorithmV0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV0.m; sourceTree = ""; }; + 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = ""; }; + 93D394077F8FAB8167647187 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; }; + 93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = ""; }; 93D398E394E311C545E0A057 /* MPAlgorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithm.h; sourceTree = ""; }; + 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = ""; }; 93D39AAB616A652A4847E4CF /* MPAlgorithmV0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV0.h; sourceTree = ""; }; 93D39B0DF5E3C56355186738 /* MPAlgorithm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithm.m; sourceTree = ""; }; 93D39C68AFA48A13015E4FAC /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = ""; }; @@ -936,6 +1012,133 @@ DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = ""; }; DA46826D15AB843200FB09E7 /* tip_basic_black_bottom_right.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tip_basic_black_bottom_right.png; sourceTree = ""; }; DA46826E15AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tip_basic_black_bottom_right@2x.png"; sourceTree = ""; }; + DA497B9715E8C90E00B52167 /* libGoogle+.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libGoogle+.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + DA497BA815E8C94300B52167 /* GTLBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBase64.h; sourceTree = ""; }; + DA497BA915E8C94300B52167 /* GTLBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBase64.m; sourceTree = ""; }; + DA497BAA15E8C94300B52167 /* GTLBatchQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBatchQuery.h; sourceTree = ""; }; + DA497BAB15E8C94300B52167 /* GTLBatchQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBatchQuery.m; sourceTree = ""; }; + DA497BAC15E8C94300B52167 /* GTLBatchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLBatchResult.h; sourceTree = ""; }; + DA497BAD15E8C94300B52167 /* GTLBatchResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLBatchResult.m; sourceTree = ""; }; + DA497BAE15E8C94300B52167 /* GTLDateTime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLDateTime.h; sourceTree = ""; }; + DA497BAF15E8C94300B52167 /* GTLDateTime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLDateTime.m; sourceTree = ""; }; + DA497BB015E8C94300B52167 /* GTLDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLDefines.h; sourceTree = ""; }; + DA497BB115E8C94300B52167 /* GTLErrorObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLErrorObject.h; sourceTree = ""; }; + DA497BB215E8C94300B52167 /* GTLErrorObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLErrorObject.m; sourceTree = ""; }; + DA497BB315E8C94300B52167 /* GTLFramework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLFramework.h; sourceTree = ""; }; + DA497BB415E8C94300B52167 /* GTLFramework.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLFramework.m; sourceTree = ""; }; + DA497BB515E8C94300B52167 /* GTLJSONParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLJSONParser.h; sourceTree = ""; }; + DA497BB615E8C94300B52167 /* GTLJSONParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLJSONParser.m; sourceTree = ""; }; + DA497BB715E8C94300B52167 /* GTLObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLObject.h; sourceTree = ""; }; + DA497BB815E8C94300B52167 /* GTLObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLObject.m; sourceTree = ""; }; + DA497BBA15E8C94300B52167 /* GTLPlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlus.h; sourceTree = ""; }; + DA497BBB15E8C94300B52167 /* GTLPlusConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusConstants.h; sourceTree = ""; }; + DA497BBC15E8C94300B52167 /* GTLPlusConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusConstants.m; sourceTree = ""; }; + DA497BBD15E8C94300B52167 /* GTLPlusItemScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusItemScope.h; sourceTree = ""; }; + DA497BBE15E8C94300B52167 /* GTLPlusItemScope.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusItemScope.m; sourceTree = ""; }; + DA497BBF15E8C94300B52167 /* GTLPlusMoment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusMoment.h; sourceTree = ""; }; + DA497BC015E8C94300B52167 /* GTLPlusMoment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusMoment.m; sourceTree = ""; }; + DA497BC115E8C94300B52167 /* GTLPlusPerson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLPlusPerson.h; sourceTree = ""; }; + DA497BC215E8C94300B52167 /* GTLPlusPerson.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLPlusPerson.m; sourceTree = ""; }; + DA497BC315E8C94300B52167 /* GTLQueryPlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLQueryPlus.h; sourceTree = ""; }; + DA497BC415E8C94300B52167 /* GTLQueryPlus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLQueryPlus.m; sourceTree = ""; }; + DA497BC515E8C94300B52167 /* GTLServicePlus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLServicePlus.h; sourceTree = ""; }; + DA497BC615E8C94300B52167 /* GTLServicePlus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLServicePlus.m; sourceTree = ""; }; + DA497BC715E8C94300B52167 /* GTLQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLQuery.h; sourceTree = ""; }; + DA497BC815E8C94300B52167 /* GTLQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLQuery.m; sourceTree = ""; }; + DA497BC915E8C94300B52167 /* GTLRuntimeCommon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLRuntimeCommon.h; sourceTree = ""; }; + DA497BCA15E8C94300B52167 /* GTLRuntimeCommon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLRuntimeCommon.m; sourceTree = ""; }; + DA497BCB15E8C94300B52167 /* GTLService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLService.h; sourceTree = ""; }; + DA497BCC15E8C94300B52167 /* GTLService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLService.m; sourceTree = ""; }; + DA497BCD15E8C94300B52167 /* GTLTargetNamespace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLTargetNamespace.h; sourceTree = ""; }; + DA497BCE15E8C94300B52167 /* GTLUploadParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLUploadParameters.h; sourceTree = ""; }; + DA497BCF15E8C94300B52167 /* GTLUploadParameters.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLUploadParameters.m; sourceTree = ""; }; + DA497BD015E8C94300B52167 /* GTLUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTLUtilities.h; sourceTree = ""; }; + DA497BD115E8C94300B52167 /* GTLUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTLUtilities.m; sourceTree = ""; }; + DA497BD215E8C94300B52167 /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = ""; }; + DA497BD315E8C94300B52167 /* GTMGarbageCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGarbageCollection.h; sourceTree = ""; }; + DA497BD415E8C94300B52167 /* GTMHTTPFetcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcher.h; sourceTree = ""; }; + DA497BD515E8C94300B52167 /* GTMHTTPFetcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcher.m; sourceTree = ""; }; + DA497BD615E8C94300B52167 /* GTMHTTPFetcherLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcherLogging.h; sourceTree = ""; }; + DA497BD715E8C94300B52167 /* GTMHTTPFetcherLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherLogging.m; sourceTree = ""; }; + DA497BD815E8C94300B52167 /* GTMHTTPFetcherService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcherService.h; sourceTree = ""; }; + DA497BD915E8C94300B52167 /* GTMHTTPFetcherService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherService.m; sourceTree = ""; }; + DA497BDA15E8C94300B52167 /* GTMHTTPFetchHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetchHistory.h; sourceTree = ""; }; + DA497BDB15E8C94300B52167 /* GTMHTTPFetchHistory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetchHistory.m; sourceTree = ""; }; + DA497BDC15E8C94300B52167 /* GTMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLogger.h; sourceTree = ""; }; + DA497BDD15E8C94300B52167 /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLogger.m; sourceTree = ""; }; + DA497BDE15E8C94300B52167 /* GTMMethodCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMMethodCheck.h; sourceTree = ""; }; + DA497BDF15E8C94300B52167 /* GTMMethodCheck.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMMethodCheck.m; sourceTree = ""; }; + DA497BE015E8C94300B52167 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = ""; }; + DA497BE115E8C94300B52167 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = ""; }; + DA497BE215E8C94300B52167 /* GTMNSString+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+URLArguments.h"; sourceTree = ""; }; + DA497BE315E8C94300B52167 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = ""; }; + DA497BE415E8C94300B52167 /* GTMOAuth2Authentication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2Authentication.h; sourceTree = ""; }; + DA497BE515E8C94300B52167 /* GTMOAuth2Authentication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2Authentication.m; sourceTree = ""; }; + DA497BE615E8C94300B52167 /* GTMOAuth2SignIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2SignIn.h; sourceTree = ""; }; + DA497BE715E8C94300B52167 /* GTMOAuth2SignIn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2SignIn.m; sourceTree = ""; }; + DA497BE815E8C94300B52167 /* GTMOAuth2ViewControllerTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2ViewControllerTouch.h; sourceTree = ""; }; + DA497BE915E8C94300B52167 /* GTMOAuth2ViewControllerTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2ViewControllerTouch.m; sourceTree = ""; }; + DA497BEA15E8C94300B52167 /* GTMOAuth2ViewTouch.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GTMOAuth2ViewTouch.xib; sourceTree = ""; }; + DA497BEB15E8C94300B52167 /* GTMObjC2Runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMObjC2Runtime.h; sourceTree = ""; }; + DA497BEC15E8C94300B52167 /* GTMObjC2Runtime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMObjC2Runtime.m; sourceTree = ""; }; + DA55873E15E81D9E00860B4F /* iTunesArtwork-Rounded-73.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork-Rounded-73.png"; sourceTree = ""; }; + DA55877F15E82C0300860B4F /* FacebookSDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FacebookSDK.framework; path = "External/facebook-ios-sdk/build/FacebookSDK.framework"; sourceTree = ""; }; + DA55878115E82C2B00860B4F /* FacebookSDKResources.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = FacebookSDKResources.bundle; path = "External/facebook-ios-sdk/build/FacebookSDK.framework/Versions/A/Resources/FacebookSDKResources.bundle"; sourceTree = ""; }; + DA5587B815E838CD00860B4F /* Facebook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Facebook.h; sourceTree = ""; }; + DA5587B915E838CD00860B4F /* FacebookSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FacebookSDK.h; sourceTree = ""; }; + DA5587BA15E838CD00860B4F /* FBCacheDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBCacheDescriptor.h; sourceTree = ""; }; + DA5587BB15E838CD00860B4F /* FBConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBConnect.h; sourceTree = ""; }; + DA5587BC15E838CD00860B4F /* FBDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBDialog.h; sourceTree = ""; }; + DA5587BD15E838CD00860B4F /* FBError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBError.h; sourceTree = ""; }; + DA5587BE15E838CD00860B4F /* FBFrictionlessRequestSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFrictionlessRequestSettings.h; sourceTree = ""; }; + DA5587BF15E838CD00860B4F /* FBFriendPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFriendPickerViewController.h; sourceTree = ""; }; + DA5587C015E838CD00860B4F /* FBGraphLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphLocation.h; sourceTree = ""; }; + DA5587C115E838CD00860B4F /* FBGraphObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphObject.h; sourceTree = ""; }; + DA5587C215E838CD00860B4F /* FBGraphPlace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphPlace.h; sourceTree = ""; }; + DA5587C315E838CD00860B4F /* FBGraphUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBGraphUser.h; sourceTree = ""; }; + DA5587C415E838CD00860B4F /* FBLoginDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBLoginDialog.h; sourceTree = ""; }; + DA5587C515E838CD00860B4F /* FBLoginView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBLoginView.h; sourceTree = ""; }; + DA5587C615E838CD00860B4F /* FBOpenGraphAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBOpenGraphAction.h; sourceTree = ""; }; + DA5587C715E838CD00860B4F /* FBPlacePickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBPlacePickerViewController.h; sourceTree = ""; }; + DA5587C815E838CD00860B4F /* FBProfilePictureView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBProfilePictureView.h; sourceTree = ""; }; + DA5587C915E838CD00860B4F /* FBRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRequest.h; sourceTree = ""; }; + DA5587CA15E838CD00860B4F /* FBRequestConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBRequestConnection.h; sourceTree = ""; }; + DA5587CB15E838CD00860B4F /* FBSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSession.h; sourceTree = ""; }; + DA5587CC15E838CD00860B4F /* FBSessionManualTokenCachingStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSessionManualTokenCachingStrategy.h; sourceTree = ""; }; + DA5587CD15E838CD00860B4F /* FBSessionTokenCachingStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSessionTokenCachingStrategy.h; sourceTree = ""; }; + DA5587CE15E838CD00860B4F /* FBSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSettings.h; sourceTree = ""; }; + DA5587CF15E838CD00860B4F /* FBTestSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBTestSession.h; sourceTree = ""; }; + DA5587D015E838CD00860B4F /* FBUserSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBUserSettingsViewController.h; sourceTree = ""; }; + DA5587D115E838CD00860B4F /* FBViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBViewController.h; sourceTree = ""; }; + DA5587D215E838CD00860B4F /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + DA5587D315E838CD00860B4F /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + DA5587D415E838CD00860B4F /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + DA5587D515E838CD00860B4F /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + DA5587D615E838CD00860B4F /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + DA5587D715E838CD00860B4F /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + DA5587D815E838CD00860B4F /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + DA5587E515E83C3200860B4F /* social-facebook.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "social-facebook.png"; sourceTree = ""; }; + DA5587E615E83C3200860B4F /* social-facebook@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "social-facebook@2x.png"; sourceTree = ""; }; + DA5587E715E83C3200860B4F /* social-google+.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "social-google+.png"; sourceTree = ""; }; + DA5587E815E83C3200860B4F /* social-google+@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "social-google+@2x.png"; sourceTree = ""; }; + DA5587E915E83C3200860B4F /* social-twitter.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "social-twitter.png"; sourceTree = ""; }; + DA5587EA15E83C3200860B4F /* social-twitter@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "social-twitter@2x.png"; sourceTree = ""; }; + DA5587F115E8418200860B4F /* iTunesArtwork-Rounded-73@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork-Rounded-73@2x.png"; sourceTree = ""; }; + DA5587F215E8418200860B4F /* iTunesArtwork-Rounded-256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork-Rounded-256.png"; sourceTree = ""; }; + DA5587F315E8418200860B4F /* iTunesArtwork-Rounded.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork-Rounded.png"; sourceTree = ""; }; + DA5587F815E8B7B200860B4F /* GooglePlusShare.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusShare.h; sourceTree = ""; }; + DA5587F915E8B7B200860B4F /* GooglePlusSignIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusSignIn.h; sourceTree = ""; }; + DA5587FA15E8B7B200860B4F /* GooglePlusSignInButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglePlusSignInButton.h; sourceTree = ""; }; + DA5587FB15E8B7B200860B4F /* libGooglePlus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libGooglePlus.a; sourceTree = ""; }; + DA5587FC15E8B7B200860B4F /* libGooglePlusUniversal.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libGooglePlusUniversal.a; sourceTree = ""; }; + DA55887C15E8C0BA00860B4F /* google_plus_share.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = google_plus_share.png; sourceTree = ""; }; + DA55887D15E8C0BA00860B4F /* google_plus_share@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "google_plus_share@2x.png"; sourceTree = ""; }; + DA55887E15E8C0BA00860B4F /* google_plus_share_large.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = google_plus_share_large.png; sourceTree = ""; }; + DA55887F15E8C0BA00860B4F /* google_plus_share_large@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "google_plus_share_large@2x.png"; sourceTree = ""; }; + DA55888015E8C0BA00860B4F /* google_plus_sign_in.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = google_plus_sign_in.png; sourceTree = ""; }; + DA55888115E8C0BA00860B4F /* google_plus_sign_in@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "google_plus_sign_in@2x.png"; sourceTree = ""; }; + DA55888215E8C0BA00860B4F /* google_plus_sign_in_wide.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = google_plus_sign_in_wide.png; sourceTree = ""; }; + DA55888315E8C0BA00860B4F /* google_plus_sign_in_wide@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "google_plus_sign_in_wide@2x.png"; sourceTree = ""; }; DA5BFA44147E415C00F98B1E /* MasterPassword.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MasterPassword.app; sourceTree = BUILT_PRODUCTS_DIR; }; DA5BFA48147E415C00F98B1E /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -1842,10 +2045,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA497B9415E8C90E00B52167 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DA497B9815E8C90E00B52167 /* Foundation.framework in Frameworks */, + DA497C0F15E8C9CF00B52167 /* libGooglePlusUniversal.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DA5BFA41147E415C00F98B1E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + DA497C0C15E8C95700B52167 /* libGoogle+.a in Frameworks */, DA829E6215984832002417D3 /* libFontReplacer.a in Frameworks */, DA44260A1557D9E40052177D /* libiCloudStoreManager.a in Frameworks */, DAD312C21552A22700A3F9ED /* libsqlite3.dylib in Frameworks */, @@ -1864,6 +2077,8 @@ DA5BFA4F147E415C00F98B1E /* CoreData.framework in Frameworks */, DAD3126715528C9C00A3F9ED /* Crashlytics.framework in Frameworks */, DAD3126915528C9C00A3F9ED /* libTestFlight.a in Frameworks */, + 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */, + DA55878015E82C0300860B4F /* FacebookSDK.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1951,6 +2166,170 @@ path = External/iCloudStoreManager/iCloudStoreManager; sourceTree = ""; }; + DA497BA615E8C94300B52167 /* OpenSource */ = { + isa = PBXGroup; + children = ( + DA497BA715E8C94300B52167 /* GTL */, + DA497BD215E8C94300B52167 /* GTMDefines.h */, + DA497BD315E8C94300B52167 /* GTMGarbageCollection.h */, + DA497BD415E8C94300B52167 /* GTMHTTPFetcher.h */, + DA497BD515E8C94300B52167 /* GTMHTTPFetcher.m */, + DA497BD615E8C94300B52167 /* GTMHTTPFetcherLogging.h */, + DA497BD715E8C94300B52167 /* GTMHTTPFetcherLogging.m */, + DA497BD815E8C94300B52167 /* GTMHTTPFetcherService.h */, + DA497BD915E8C94300B52167 /* GTMHTTPFetcherService.m */, + DA497BDA15E8C94300B52167 /* GTMHTTPFetchHistory.h */, + DA497BDB15E8C94300B52167 /* GTMHTTPFetchHistory.m */, + DA497BDC15E8C94300B52167 /* GTMLogger.h */, + DA497BDD15E8C94300B52167 /* GTMLogger.m */, + DA497BDE15E8C94300B52167 /* GTMMethodCheck.h */, + DA497BDF15E8C94300B52167 /* GTMMethodCheck.m */, + DA497BE015E8C94300B52167 /* GTMNSDictionary+URLArguments.h */, + DA497BE115E8C94300B52167 /* GTMNSDictionary+URLArguments.m */, + DA497BE215E8C94300B52167 /* GTMNSString+URLArguments.h */, + DA497BE315E8C94300B52167 /* GTMNSString+URLArguments.m */, + DA497BE415E8C94300B52167 /* GTMOAuth2Authentication.h */, + DA497BE515E8C94300B52167 /* GTMOAuth2Authentication.m */, + DA497BE615E8C94300B52167 /* GTMOAuth2SignIn.h */, + DA497BE715E8C94300B52167 /* GTMOAuth2SignIn.m */, + DA497BE815E8C94300B52167 /* GTMOAuth2ViewControllerTouch.h */, + DA497BE915E8C94300B52167 /* GTMOAuth2ViewControllerTouch.m */, + DA497BEA15E8C94300B52167 /* GTMOAuth2ViewTouch.xib */, + DA497BEB15E8C94300B52167 /* GTMObjC2Runtime.h */, + DA497BEC15E8C94300B52167 /* GTMObjC2Runtime.m */, + ); + name = OpenSource; + path = "External/google-plus-ios-sdk/OpenSource"; + sourceTree = SOURCE_ROOT; + }; + DA497BA715E8C94300B52167 /* GTL */ = { + isa = PBXGroup; + children = ( + DA497BA815E8C94300B52167 /* GTLBase64.h */, + DA497BA915E8C94300B52167 /* GTLBase64.m */, + DA497BAA15E8C94300B52167 /* GTLBatchQuery.h */, + DA497BAB15E8C94300B52167 /* GTLBatchQuery.m */, + DA497BAC15E8C94300B52167 /* GTLBatchResult.h */, + DA497BAD15E8C94300B52167 /* GTLBatchResult.m */, + DA497BAE15E8C94300B52167 /* GTLDateTime.h */, + DA497BAF15E8C94300B52167 /* GTLDateTime.m */, + DA497BB015E8C94300B52167 /* GTLDefines.h */, + DA497BB115E8C94300B52167 /* GTLErrorObject.h */, + DA497BB215E8C94300B52167 /* GTLErrorObject.m */, + DA497BB315E8C94300B52167 /* GTLFramework.h */, + DA497BB415E8C94300B52167 /* GTLFramework.m */, + DA497BB515E8C94300B52167 /* GTLJSONParser.h */, + DA497BB615E8C94300B52167 /* GTLJSONParser.m */, + DA497BB715E8C94300B52167 /* GTLObject.h */, + DA497BB815E8C94300B52167 /* GTLObject.m */, + DA497BB915E8C94300B52167 /* GTLPlus */, + DA497BC715E8C94300B52167 /* GTLQuery.h */, + DA497BC815E8C94300B52167 /* GTLQuery.m */, + DA497BC915E8C94300B52167 /* GTLRuntimeCommon.h */, + DA497BCA15E8C94300B52167 /* GTLRuntimeCommon.m */, + DA497BCB15E8C94300B52167 /* GTLService.h */, + DA497BCC15E8C94300B52167 /* GTLService.m */, + DA497BCD15E8C94300B52167 /* GTLTargetNamespace.h */, + DA497BCE15E8C94300B52167 /* GTLUploadParameters.h */, + DA497BCF15E8C94300B52167 /* GTLUploadParameters.m */, + DA497BD015E8C94300B52167 /* GTLUtilities.h */, + DA497BD115E8C94300B52167 /* GTLUtilities.m */, + ); + path = GTL; + sourceTree = ""; + }; + DA497BB915E8C94300B52167 /* GTLPlus */ = { + isa = PBXGroup; + children = ( + DA497BBA15E8C94300B52167 /* GTLPlus.h */, + DA497BBB15E8C94300B52167 /* GTLPlusConstants.h */, + DA497BBC15E8C94300B52167 /* GTLPlusConstants.m */, + DA497BBD15E8C94300B52167 /* GTLPlusItemScope.h */, + DA497BBE15E8C94300B52167 /* GTLPlusItemScope.m */, + DA497BBF15E8C94300B52167 /* GTLPlusMoment.h */, + DA497BC015E8C94300B52167 /* GTLPlusMoment.m */, + DA497BC115E8C94300B52167 /* GTLPlusPerson.h */, + DA497BC215E8C94300B52167 /* GTLPlusPerson.m */, + DA497BC315E8C94300B52167 /* GTLQueryPlus.h */, + DA497BC415E8C94300B52167 /* GTLQueryPlus.m */, + DA497BC515E8C94300B52167 /* GTLServicePlus.h */, + DA497BC615E8C94300B52167 /* GTLServicePlus.m */, + ); + path = GTLPlus; + sourceTree = ""; + }; + DA5587B715E838CD00860B4F /* DeprecatedHeaders */ = { + isa = PBXGroup; + children = ( + DA5587B815E838CD00860B4F /* Facebook.h */, + DA5587B915E838CD00860B4F /* FacebookSDK.h */, + DA5587BA15E838CD00860B4F /* FBCacheDescriptor.h */, + DA5587BB15E838CD00860B4F /* FBConnect.h */, + DA5587BC15E838CD00860B4F /* FBDialog.h */, + DA5587BD15E838CD00860B4F /* FBError.h */, + DA5587BE15E838CD00860B4F /* FBFrictionlessRequestSettings.h */, + DA5587BF15E838CD00860B4F /* FBFriendPickerViewController.h */, + DA5587C015E838CD00860B4F /* FBGraphLocation.h */, + DA5587C115E838CD00860B4F /* FBGraphObject.h */, + DA5587C215E838CD00860B4F /* FBGraphPlace.h */, + DA5587C315E838CD00860B4F /* FBGraphUser.h */, + DA5587C415E838CD00860B4F /* FBLoginDialog.h */, + DA5587C515E838CD00860B4F /* FBLoginView.h */, + DA5587C615E838CD00860B4F /* FBOpenGraphAction.h */, + DA5587C715E838CD00860B4F /* FBPlacePickerViewController.h */, + DA5587C815E838CD00860B4F /* FBProfilePictureView.h */, + DA5587C915E838CD00860B4F /* FBRequest.h */, + DA5587CA15E838CD00860B4F /* FBRequestConnection.h */, + DA5587CB15E838CD00860B4F /* FBSession.h */, + DA5587CC15E838CD00860B4F /* FBSessionManualTokenCachingStrategy.h */, + DA5587CD15E838CD00860B4F /* FBSessionTokenCachingStrategy.h */, + DA5587CE15E838CD00860B4F /* FBSettings.h */, + DA5587CF15E838CD00860B4F /* FBTestSession.h */, + DA5587D015E838CD00860B4F /* FBUserSettingsViewController.h */, + DA5587D115E838CD00860B4F /* FBViewController.h */, + DA5587D215E838CD00860B4F /* JSON.h */, + DA5587D315E838CD00860B4F /* NSObject+SBJSON.h */, + DA5587D415E838CD00860B4F /* NSString+SBJSON.h */, + DA5587D515E838CD00860B4F /* SBJSON.h */, + DA5587D615E838CD00860B4F /* SBJsonBase.h */, + DA5587D715E838CD00860B4F /* SBJsonParser.h */, + DA5587D815E838CD00860B4F /* SBJsonWriter.h */, + ); + name = DeprecatedHeaders; + path = "External/facebook-ios-sdk/build/FacebookSDK.framework/Versions/A/DeprecatedHeaders"; + sourceTree = ""; + }; + DA5587F715E8B7B200860B4F /* Google+ */ = { + isa = PBXGroup; + children = ( + DA497BA615E8C94300B52167 /* OpenSource */, + DA55887B15E8C0BA00860B4F /* Resources */, + DA5587F815E8B7B200860B4F /* GooglePlusShare.h */, + DA5587F915E8B7B200860B4F /* GooglePlusSignIn.h */, + DA5587FA15E8B7B200860B4F /* GooglePlusSignInButton.h */, + DA5587FB15E8B7B200860B4F /* libGooglePlus.a */, + DA5587FC15E8B7B200860B4F /* libGooglePlusUniversal.a */, + ); + name = "Google+"; + path = "External/google-plus-ios-sdk/lib"; + sourceTree = ""; + }; + DA55887B15E8C0BA00860B4F /* Resources */ = { + isa = PBXGroup; + children = ( + DA55887C15E8C0BA00860B4F /* google_plus_share.png */, + DA55887D15E8C0BA00860B4F /* google_plus_share@2x.png */, + DA55887E15E8C0BA00860B4F /* google_plus_share_large.png */, + DA55887F15E8C0BA00860B4F /* google_plus_share_large@2x.png */, + DA55888015E8C0BA00860B4F /* google_plus_sign_in.png */, + DA55888115E8C0BA00860B4F /* google_plus_sign_in@2x.png */, + DA55888215E8C0BA00860B4F /* google_plus_sign_in_wide.png */, + DA55888315E8C0BA00860B4F /* google_plus_sign_in_wide@2x.png */, + ); + name = Resources; + path = "External/google-plus-ios-sdk/Resources"; + sourceTree = SOURCE_ROOT; + }; DA5BFA39147E415C00F98B1E = { isa = PBXGroup; children = ( @@ -1965,6 +2344,7 @@ DAD3125E15528C9C00A3F9ED /* Crashlytics */, DAD3126115528C9C00A3F9ED /* TestFlight */, DAD3127315528CD200A3F9ED /* Localytics */, + DA5587F715E8B7B200860B4F /* Google+ */, DA4425D71557BF260052177D /* iCloudStoreManager */, DA829E5D15984812002417D3 /* FontReplacer */, DA3EF17E15A47744003ABF4E /* Tests */, @@ -1985,6 +2365,7 @@ DA4425CB1557BED40052177D /* libiCloudStoreManager.a */, DA829E51159847E0002417D3 /* libFontReplacer.a */, DA3EF17915A47744003ABF4E /* Tests.octest */, + DA497B9715E8C90E00B52167 /* libGoogle+.a */, ); name = Products; sourceTree = ""; @@ -1992,6 +2373,10 @@ DA5BFA47147E415C00F98B1E /* Frameworks */ = { isa = PBXGroup; children = ( + DA5587B715E838CD00860B4F /* DeprecatedHeaders */, + DA55878115E82C2B00860B4F /* FacebookSDKResources.bundle */, + DA55877F15E82C0300860B4F /* FacebookSDK.framework */, + 93D394077F8FAB8167647187 /* Twitter.framework */, DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */, DAD312C01552A20800A3F9ED /* libsqlite3.dylib */, DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */, @@ -2218,6 +2603,16 @@ DAB8D46F15036BF600CED3BC /* Resources */ = { isa = PBXGroup; children = ( + DA5587F115E8418200860B4F /* iTunesArtwork-Rounded-73@2x.png */, + DA5587F215E8418200860B4F /* iTunesArtwork-Rounded-256.png */, + DA5587F315E8418200860B4F /* iTunesArtwork-Rounded.png */, + DA5587E515E83C3200860B4F /* social-facebook.png */, + DA5587E615E83C3200860B4F /* social-facebook@2x.png */, + DA5587E715E83C3200860B4F /* social-google+.png */, + DA5587E815E83C3200860B4F /* social-google+@2x.png */, + DA5587E915E83C3200860B4F /* social-twitter.png */, + DA5587EA15E83C3200860B4F /* social-twitter@2x.png */, + DA55873E15E81D9E00860B4F /* iTunesArtwork-Rounded-73.png */, DAC4149115C53C48007A716E /* dictionary.lst */, DA902BD01576CA4A00C38161 /* keypad.png */, DA902B931576C0FB00C38161 /* Avatars */, @@ -3050,6 +3445,10 @@ DAFE45D715039823003ABA7C /* Pearl */ = { isa = PBXGroup; children = ( + 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */, + 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */, + 93D396D04E57792A54D437AC /* NSArray+Indexing.h */, + 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */, DAFE4A63150399FF003ABA93 /* NSDateFormatter+RFC3339.h */, DAFE4A63150399FF003ABA91 /* NSDateFormatter+RFC3339.m */, DAFE4A63150399FF003ABA87 /* NSObject+PearlKVO.h */, @@ -3284,6 +3683,8 @@ DAFE4A63150399FF003ABA8C /* UIControl+PearlSelect.h in Headers */, DAFE4A63150399FF003ABA90 /* UIScrollView+PearlFlashingIndicators.h in Headers */, DAFE4A63150399FF003ABA94 /* NSDateFormatter+RFC3339.h in Headers */, + 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */, + 93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3338,10 +3739,28 @@ productReference = DA4425CB1557BED40052177D /* libiCloudStoreManager.a */; productType = "com.apple.product-type.library.static"; }; + DA497B9615E8C90E00B52167 /* Google+ */ = { + isa = PBXNativeTarget; + buildConfigurationList = DA497BA015E8C90E00B52167 /* Build configuration list for PBXNativeTarget "Google+" */; + buildPhases = ( + DA497B9315E8C90E00B52167 /* Sources */, + DA497B9415E8C90E00B52167 /* Frameworks */, + DA497B9515E8C90E00B52167 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Google+"; + productName = "Google+"; + productReference = DA497B9715E8C90E00B52167 /* libGoogle+.a */; + productType = "com.apple.product-type.library.static"; + }; DA5BFA43147E415C00F98B1E /* MasterPassword */ = { isa = PBXNativeTarget; buildConfigurationList = DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "MasterPassword" */; buildPhases = ( + DA55878315E82C8500860B4F /* Run Script: FacebookSDK */, DA5BFA40147E415C00F98B1E /* Sources */, DA5BFA41147E415C00F98B1E /* Frameworks */, DA5BFA42147E415C00F98B1E /* Resources */, @@ -3500,6 +3919,7 @@ DA4425CA1557BED40052177D /* iCloudStoreManager */, DA829E50159847E0002417D3 /* FontReplacer */, DA3EF17815A47744003ABF4E /* Tests */, + DA497B9615E8C90E00B52167 /* Google+ */, ); }; /* End PBXProject section */ @@ -4179,6 +4599,25 @@ DA46826F15AB843200FB09E7 /* tip_basic_black_bottom_right.png in Resources */, DA46827015AB843200FB09E7 /* tip_basic_black_bottom_right@2x.png in Resources */, DAC4149215C53C48007A716E /* dictionary.lst in Resources */, + DA55873F15E81D9E00860B4F /* iTunesArtwork-Rounded-73.png in Resources */, + DA55878215E82C2B00860B4F /* FacebookSDKResources.bundle in Resources */, + DA5587EB15E83C3200860B4F /* social-facebook.png in Resources */, + DA5587EC15E83C3200860B4F /* social-facebook@2x.png in Resources */, + DA5587ED15E83C3200860B4F /* social-google+.png in Resources */, + DA5587EE15E83C3200860B4F /* social-google+@2x.png in Resources */, + DA5587EF15E83C3200860B4F /* social-twitter.png in Resources */, + DA5587F015E83C3200860B4F /* social-twitter@2x.png in Resources */, + DA5587F415E8418200860B4F /* iTunesArtwork-Rounded-73@2x.png in Resources */, + DA5587F515E8418200860B4F /* iTunesArtwork-Rounded-256.png in Resources */, + DA5587F615E8418200860B4F /* iTunesArtwork-Rounded.png in Resources */, + DA55888415E8C0BA00860B4F /* google_plus_share.png in Resources */, + DA55888515E8C0BA00860B4F /* google_plus_share@2x.png in Resources */, + DA55888615E8C0BA00860B4F /* google_plus_share_large.png in Resources */, + DA55888715E8C0BA00860B4F /* google_plus_share_large@2x.png in Resources */, + DA55888815E8C0BA00860B4F /* google_plus_sign_in.png in Resources */, + DA55888915E8C0BA00860B4F /* google_plus_sign_in@2x.png in Resources */, + DA55888A15E8C0BA00860B4F /* google_plus_sign_in_wide.png in Resources */, + DA55888B15E8C0BA00860B4F /* google_plus_sign_in_wide@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4198,6 +4637,21 @@ shellPath = /bin/sh; shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; }; + DA55878315E82C8500860B4F /* Run Script: FacebookSDK */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script: FacebookSDK"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "[ -d External/facebook-ios-sdk/build ] || env -i PATH=\"$PATH\" ./External/facebook-ios-sdk/scripts/build_framework.sh"; + showEnvVarsInLog = 0; + }; DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -4247,6 +4701,44 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA497B9315E8C90E00B52167 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DA497BED15E8C94300B52167 /* GTLBase64.m in Sources */, + DA497BEE15E8C94300B52167 /* GTLBatchQuery.m in Sources */, + DA497BEF15E8C94300B52167 /* GTLBatchResult.m in Sources */, + DA497BF015E8C94300B52167 /* GTLDateTime.m in Sources */, + DA497BF115E8C94300B52167 /* GTLErrorObject.m in Sources */, + DA497BF215E8C94300B52167 /* GTLFramework.m in Sources */, + DA497BF315E8C94300B52167 /* GTLJSONParser.m in Sources */, + DA497BF415E8C94300B52167 /* GTLObject.m in Sources */, + DA497BF515E8C94300B52167 /* GTLPlusConstants.m in Sources */, + DA497BF615E8C94300B52167 /* GTLPlusItemScope.m in Sources */, + DA497BF715E8C94300B52167 /* GTLPlusMoment.m in Sources */, + DA497BF815E8C94300B52167 /* GTLPlusPerson.m in Sources */, + DA497BF915E8C94300B52167 /* GTLQueryPlus.m in Sources */, + DA497BFA15E8C94300B52167 /* GTLServicePlus.m in Sources */, + DA497BFB15E8C94300B52167 /* GTLQuery.m in Sources */, + DA497BFC15E8C94300B52167 /* GTLRuntimeCommon.m in Sources */, + DA497BFD15E8C94300B52167 /* GTLService.m in Sources */, + DA497BFE15E8C94300B52167 /* GTLUploadParameters.m in Sources */, + DA497BFF15E8C94300B52167 /* GTLUtilities.m in Sources */, + DA497C0015E8C94300B52167 /* GTMHTTPFetcher.m in Sources */, + DA497C0115E8C94300B52167 /* GTMHTTPFetcherLogging.m in Sources */, + DA497C0215E8C94300B52167 /* GTMHTTPFetcherService.m in Sources */, + DA497C0315E8C94300B52167 /* GTMHTTPFetchHistory.m in Sources */, + DA497C0415E8C94300B52167 /* GTMLogger.m in Sources */, + DA497C0515E8C94300B52167 /* GTMMethodCheck.m in Sources */, + DA497C0615E8C94300B52167 /* GTMNSDictionary+URLArguments.m in Sources */, + DA497C0715E8C94300B52167 /* GTMNSString+URLArguments.m in Sources */, + DA497C0815E8C94300B52167 /* GTMOAuth2Authentication.m in Sources */, + DA497C0915E8C94300B52167 /* GTMOAuth2SignIn.m in Sources */, + DA497C0A15E8C94300B52167 /* GTMOAuth2ViewControllerTouch.m in Sources */, + DA497C0B15E8C94300B52167 /* GTMObjC2Runtime.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DA5BFA40147E415C00F98B1E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4370,6 +4862,8 @@ DAFE4A63150399FF003ABA8A /* UIControl+PearlSelect.m in Sources */, DAFE4A63150399FF003ABA8E /* UIScrollView+PearlFlashingIndicators.m in Sources */, DAFE4A63150399FF003ABA92 /* NSDateFormatter+RFC3339.m in Sources */, + 93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */, + 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4540,6 +5034,42 @@ }; name = AppStore; }; + DA497BA115E8C90E00B52167 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_OBJCPP_ARC_ABI = NO; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/External/google-plus-ios-sdk/lib\"", + ); + }; + name = Debug; + }; + DA497BA215E8C90E00B52167 /* AdHoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_OBJCPP_ARC_ABI = NO; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/External/google-plus-ios-sdk/lib\"", + ); + }; + name = AdHoc; + }; + DA497BA315E8C90E00B52167 /* AppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_OBJCPP_ARC_ABI = NO; + GCC_WARN_INHIBIT_ALL_WARNINGS = YES; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/External/google-plus-ios-sdk/lib\"", + ); + }; + name = AppStore; + }; DA5BFA6B147E415C00F98B1E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4549,7 +5079,9 @@ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_OBJCPP_ARC_ABI = YES; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Maarten Billemont (DWGU95U4ZD)"; COPY_PHASE_STRIP = NO; @@ -4608,7 +5140,9 @@ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_OBJCPP_ARC_ABI = YES; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont"; COPY_PHASE_STRIP = YES; @@ -4666,6 +5200,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Crashlytics\"", + "\"$(SRCROOT)/External/facebook-ios-sdk/build\"", ); GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist"; @@ -4674,6 +5209,7 @@ "$(inherited)", "\"$(SRCROOT)/TestFlight\"", "\"$(SRCROOT)/External/Pearl/Pearl-Crypto\"", + "\"$(SRCROOT)/External/google-plus-ios-sdk/lib\"", ); SKIP_INSTALL = NO; TARGETED_DEVICE_FAMILY = 1; @@ -4688,6 +5224,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Crashlytics\"", + "\"$(SRCROOT)/External/facebook-ios-sdk/build\"", ); GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist"; @@ -4696,6 +5233,7 @@ "$(inherited)", "\"$(SRCROOT)/TestFlight\"", "\"$(SRCROOT)/External/Pearl/Pearl-Crypto\"", + "\"$(SRCROOT)/External/google-plus-ios-sdk/lib\"", ); SKIP_INSTALL = NO; TARGETED_DEVICE_FAMILY = 1; @@ -4754,7 +5292,9 @@ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_OBJCPP_ARC_ABI = YES; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Maarten Billemont"; COPY_PHASE_STRIP = YES; @@ -4812,6 +5352,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/Crashlytics\"", + "\"$(SRCROOT)/External/facebook-ios-sdk/build\"", ); GCC_PREFIX_HEADER = "MasterPassword/iOS/MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword/iOS/MasterPassword-Info.plist"; @@ -4820,6 +5361,7 @@ "$(inherited)", "\"$(SRCROOT)/TestFlight\"", "\"$(SRCROOT)/External/Pearl/Pearl-Crypto\"", + "\"$(SRCROOT)/External/google-plus-ios-sdk/lib\"", ); SKIP_INSTALL = NO; TARGETED_DEVICE_FAMILY = 1; @@ -5007,6 +5549,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = AdHoc; }; + DA497BA015E8C90E00B52167 /* Build configuration list for PBXNativeTarget "Google+" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DA497BA115E8C90E00B52167 /* Debug */, + DA497BA215E8C90E00B52167 /* AdHoc */, + DA497BA315E8C90E00B52167 /* AppStore */, + ); + defaultConfigurationIsVisible = 0; + }; DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/MasterPassword/MPAppDelegate_Shared.m b/MasterPassword/MPAppDelegate_Shared.m index 15119adf..2acb232e 100644 --- a/MasterPassword/MPAppDelegate_Shared.m +++ b/MasterPassword/MPAppDelegate_Shared.m @@ -11,7 +11,7 @@ @interface MPAppDelegate_Shared () -@property (strong) NSManagedObjectID *activeUserID; +@property (strong, nonatomic) NSManagedObjectID *activeUserID; @end diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index 89c91b47..38bd604b 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -177,7 +177,7 @@ Throw(@"Local store was reset, application must be restarted to use it."); } else // Try again. - [self storeManager].persistentStoreCoordinator; + [[self storeManager] persistentStoreCoordinator]; } case UbiquityStoreManagerErrorCauseOpenCloudStore: { wrn(@"iCloud store could not be opened: %@", error); @@ -194,7 +194,7 @@ break; } else // Try again. - [self storeManager].persistentStoreCoordinator; + [[self storeManager] persistentStoreCoordinator]; } } } diff --git a/MasterPassword/iOS/MPAppDelegate.m b/MasterPassword/iOS/MPAppDelegate.m index 7249d631..8ab7e197 100644 --- a/MasterPassword/iOS/MPAppDelegate.m +++ b/MasterPassword/iOS/MPAppDelegate.m @@ -6,6 +6,8 @@ // Copyright (c) 2011 Lyndir. All rights reserved. // +#import + #import "MPAppDelegate.h" #import "MPAppDelegate_Key.h" #import "MPAppDelegate_Store.h" @@ -148,7 +150,7 @@ @{UITextAttributeTextColor: [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f], UITextAttributeTextShadowColor: [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f], UITextAttributeTextShadowOffset: [NSValue valueWithUIOffset:UIOffsetMake(0, 1)], - UITextAttributeFont: [UIFont fontWithName:@"Helvetica-Neue" size:0.0f]} + UITextAttributeFont: [UIFont fontWithName:@"HelveticaNeue" size:0.0f]} forState:UIControlStateNormal]; UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)]; @@ -215,7 +217,25 @@ - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { + // No URL? + if (!url) + return NO; + + // Check if this is a Facebook login URL. + if ([FBSession.activeSession handleOpenURL:url]) + return YES; + + // Arbitrary URL to mpsites data. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSError *error; + NSURLResponse *response; + NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] + returningResponse:&response error:&error]; + if (error) + err(@"While reading imported sites from %@: %@", url, error); + if (!importedSitesData) + return; + PearlAlert *activityAlert = [PearlAlert showAlertWithTitle:@"Importing" message:@"\n\n" viewStyle:UIAlertViewStyleDefault initAlert: ^(UIAlertView *alert, UITextField *firstField) { @@ -226,15 +246,6 @@ } tappedButtonBlock:nil cancelTitle:nil otherTitles:nil]; - NSError *error; - NSURLResponse *response; - NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] - returningResponse:&response error:&error]; - if (error) - err(@"While reading imported sites from %@: %@", url, error); - if (!importedSitesData) - return; - NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) { __block NSString *masterPassword = nil; @@ -325,6 +336,10 @@ [TestFlight passCheckpoint:MPCheckpointActivated]; + if (FBSession.activeSession.state == FBSessionStateCreatedOpening) + // An old Facebook Login session that wasn't finished. Clean it up. + [FBSession.activeSession close]; + [super applicationDidBecomeActive:application]; } @@ -353,6 +368,8 @@ [[LocalyticsSession sharedLocalyticsSession] close]; [[LocalyticsSession sharedLocalyticsSession] upload]; + [FBSession.activeSession close]; + [super applicationWillTerminate:application]; } diff --git a/MasterPassword/iOS/MPMainViewController.h b/MasterPassword/iOS/MPMainViewController.h index b60e7738..ba5683d8 100644 --- a/MasterPassword/iOS/MPMainViewController.h +++ b/MasterPassword/iOS/MPMainViewController.h @@ -13,7 +13,7 @@ @interface MPMainViewController : UIViewController -@property (nonatomic, assign) BOOL siteInfoHidden; +@property (assign, nonatomic) BOOL siteInfoHidden; @property (strong, nonatomic) MPElementEntity *activeElement; @property (strong, nonatomic) IBOutlet MPSearchDelegate *searchDelegate; @property (weak, nonatomic) IBOutlet UITextField *contentField; @@ -48,8 +48,8 @@ @property (weak, nonatomic) IBOutlet UIButton *outdatedAlertCloseButton; -@property (copy) void (^contentTipCleanup)(BOOL finished); -@property (copy) void (^toolTipCleanup)(BOOL finished); +@property (copy, nonatomic) void (^contentTipCleanup)(BOOL finished); +@property (copy, nonatomic) void (^toolTipCleanup)(BOOL finished); - (IBAction)copyContent; - (IBAction)incrementPasswordCounter; diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m index 90c2ccf2..214369f2 100644 --- a/MasterPassword/iOS/MPMainViewController.m +++ b/MasterPassword/iOS/MPMainViewController.m @@ -122,6 +122,9 @@ if ([[MPiOSConfig get].showQuickStart boolValue]) [[MPAppDelegate get] showGuide]; + if (![MPAppDelegate get].activeUser) + [self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"] + animated:animated completion:nil]; if (self.activeElement.user != [MPAppDelegate get].activeUser) self.activeElement = nil; @@ -141,6 +144,8 @@ - (void)viewDidAppear:(BOOL)animated { + // Needed for when we appear after a modal VC dismisses: + // We can't present until the other modal VC has been fully dismissed and presenting in viewDidAppear will fail. if (![MPAppDelegate get].activeUser) [self.navigationController presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"] animated:animated completion:nil]; diff --git a/MasterPassword/iOS/MPUnlockViewController.h b/MasterPassword/iOS/MPUnlockViewController.h index ad57d1c4..3e9ca8ad 100644 --- a/MasterPassword/iOS/MPUnlockViewController.h +++ b/MasterPassword/iOS/MPUnlockViewController.h @@ -29,5 +29,8 @@ @property (nonatomic, strong) UIColor *avatarShadowColor; - (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender; +- (IBAction)facebook:(UIButton *)sender; +- (IBAction)twitter:(UIButton *)sender; +- (IBAction)google:(UIButton *)sender; @end diff --git a/MasterPassword/iOS/MPUnlockViewController.m b/MasterPassword/iOS/MPUnlockViewController.m index 0b52e37b..f0cc5a6d 100644 --- a/MasterPassword/iOS/MPUnlockViewController.m +++ b/MasterPassword/iOS/MPUnlockViewController.m @@ -7,6 +7,10 @@ // #import +#import + +#import "Facebook.h" +#import "GooglePlusShare.h" #import "MPUnlockViewController.h" #import "MPAppDelegate.h" @@ -18,7 +22,8 @@ @property (strong, nonatomic) MPUserEntity *selectedUser; @property (strong, nonatomic) NSMutableDictionary *avatarToUser; @property (nonatomic) BOOL wordWallAnimating; -@property (nonatomic, strong) NSArray *wordList; +@property (nonatomic, strong) NSArray *wordList; +@property (nonatomic, strong) NSOperationQueue *fbOperationQueue; @end @@ -43,6 +48,7 @@ @synthesize avatarShadowColor = _avatarShadowColor; @synthesize wordWallAnimating = _wordWallAnimating; @synthesize wordList = _wordList; +@synthesize fbOperationQueue = _fbOperationQueue; - (void)initializeAvatarAlert:(UIAlertView *)alert forUser:(MPUserEntity *)user { @@ -122,7 +128,9 @@ - (void)viewDidLoad { - self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3]; + self.avatarToUser = [NSMutableDictionary dictionaryWithCapacity:3]; + self.fbOperationQueue = [NSOperationQueue new]; + [self.fbOperationQueue setSuspended:YES]; self.avatarsView.decelerationRate = UIScrollViewDecelerationRateFast; self.avatarsView.clipsToBounds = NO; @@ -283,7 +291,7 @@ if ([self.selectedUser.objectID isEqual:user.objectID]) { self.selectedUser = user; - avatar.selected = YES; + avatar.selected = YES; } return avatar; @@ -407,9 +415,9 @@ if (self.selectedUser && !self.passwordView.alpha) { // User was just selected. self.passwordView.alpha = 1; - self.avatarsView.center = CGPointMake(160, 170); + self.avatarsView.center = CGPointMake(160, 180); self.avatarsView.scrollEnabled = NO; - self.nameLabel.center = CGPointMake(160, 84); + self.nameLabel.center = CGPointMake(160, 94); self.nameLabel.backgroundColor = [UIColor blackColor]; self.oldNameLabel.center = self.nameLabel.center; self.avatarShadowColor = [UIColor whiteColor]; @@ -764,4 +772,81 @@ } cancelTitle:[PearlStrings get].commonButtonCancel destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil]; } + +- (IBAction)facebook:(UIButton *)sender { + + [self.fbOperationQueue addOperationWithBlock:^{ + Facebook *facebook = [[Facebook alloc] initWithAppId:FBSession.activeSession.appID andDelegate:nil]; + facebook.accessToken = FBSession.activeSession.accessToken; + facebook.expirationDate = FBSession.activeSession.expirationDate; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [[self.view findFirstResponderInHierarchy] resignFirstResponder]; + [facebook dialog:@"feed" andParams:[@{ + @"link": @"http://masterpasswordapp.com", + @"picture": @"http://masterpasswordapp.com/img/iTunesArtwork-Rounded.png", + @"name": @"Master Password", + @"description": @"Actually secure passwords that cannot get lost.", + @"ref": @"iOS_Unlock" + } mutableCopy] andDelegate:nil]; + }]; + }]; + if ([self.fbOperationQueue isSuspended]) + [self openSessionWithAllowLoginUI:YES]; +} + +- (IBAction)twitter:(UIButton *)sender { + + if (![TWTweetComposeViewController canSendTweet]) { + [PearlAlert showAlertWithTitle:@"Twitter Not Enabled" message:@"To send tweets, configure Twitter from Settings." + viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil cancelTitle:nil otherTitles:@"OK", nil]; + return; + } + + TWTweetComposeViewController *vc = [TWTweetComposeViewController new]; + [vc addImage:[UIImage imageNamed:@"iTunesArtwork-Rounded-73"]]; + [vc setInitialText:@"I've secured my accounts with Master Password: masterpasswordapp.com"]; + [self presentViewController:vc animated:YES completion:nil]; +} + +- (IBAction)google:(UIButton *)sender { + + GooglePlusShare *share = [[GooglePlusShare alloc] initWithClientID:[[PearlInfoPlist get] objectForKeyPath:@"GooglePlusClientID"]]; + [[[[share shareDialog] + setURLToShare:[NSURL URLWithString:@"http://masterpasswordapp.com"]] + setPrefillText:@"I've secured my accounts with Master Password: Actually secure passwords that cannot get lost."] + open]; +} + +- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState)state error:(NSError *)error { + + switch (state) { + case FBSessionStateOpen: + if (!error) { + [self.fbOperationQueue setSuspended:NO]; + return; + } + + break; + case FBSessionStateClosed: + case FBSessionStateClosedLoginFailed: + [FBSession.activeSession closeAndClearTokenInformation]; + break; + default: + break; + } + [self.fbOperationQueue setSuspended:YES]; + + if (error) + [PearlAlert showError:error.localizedDescription]; +} + +- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI { + + return [FBSession openActiveSessionWithPermissions:nil allowLoginUI:allowLoginUI + completionHandler:^(FBSession *session, FBSessionState state, NSError *error) { + [self sessionStateChanged:session state:state error:error]; + }]; +} + @end diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard index 48662911..1ccf1f5f 100644 --- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard +++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard @@ -1,6 +1,7 @@ - + + @@ -1143,7 +1144,7 @@ Pink fluffy door frame. - + @@ -1306,7 +1307,7 @@ Pink fluffy door frame. - + + + @@ -1823,6 +1869,9 @@ You could use the word wall for inspiration in finding a memorable master passw + + + @@ -1925,7 +1974,10 @@ You could use the word wall for inspiration in finding a memorable master passw + + + diff --git a/MasterPassword/iOS/MasterPassword-Info.plist b/MasterPassword/iOS/MasterPassword-Info.plist index c8d25870..5e85d481 100644 --- a/MasterPassword/iOS/MasterPassword-Info.plist +++ b/MasterPassword/iOS/MasterPassword-Info.plist @@ -61,8 +61,35 @@ [auto] CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + facebook + CFBundleURLSchemes + + fb257095917745237 + + + + CFBundleTypeRole + Editor + CFBundleURLName + com.lyndir.lhunath.MasterPassword + CFBundleURLSchemes + + com.lyndir.lhunath.MasterPassword + + + CFBundleVersion [auto] + FacebookAppID + 257095917745237 + GooglePlusClientID + 1098891429568.apps.googleusercontent.com LSRequiresIPhoneOS NSHumanReadableCopyright diff --git a/Resources/iTunesArtwork-Rounded-73.png b/Resources/iTunesArtwork-Rounded-73.png new file mode 100644 index 00000000..c6abaced Binary files /dev/null and b/Resources/iTunesArtwork-Rounded-73.png differ diff --git a/Resources/iTunesArtwork-Rounded-73@2x.png b/Resources/iTunesArtwork-Rounded-73@2x.png new file mode 100644 index 00000000..c62b617d Binary files /dev/null and b/Resources/iTunesArtwork-Rounded-73@2x.png differ diff --git a/Resources/iTunesArtwork-Rounded.png b/Resources/iTunesArtwork-Rounded.png index 37cc5022..132d0178 100644 Binary files a/Resources/iTunesArtwork-Rounded.png and b/Resources/iTunesArtwork-Rounded.png differ diff --git a/Resources/social-facebook.png b/Resources/social-facebook.png new file mode 100644 index 00000000..5cbebd0b Binary files /dev/null and b/Resources/social-facebook.png differ diff --git a/Resources/social-facebook@2x.png b/Resources/social-facebook@2x.png new file mode 100644 index 00000000..999b26bc Binary files /dev/null and b/Resources/social-facebook@2x.png differ diff --git a/Resources/social-google+.png b/Resources/social-google+.png new file mode 100644 index 00000000..8dbdbfc9 Binary files /dev/null and b/Resources/social-google+.png differ diff --git a/Resources/social-google+@2x.png b/Resources/social-google+@2x.png new file mode 100644 index 00000000..33e1f75d Binary files /dev/null and b/Resources/social-google+@2x.png differ diff --git a/Resources/social-twitter.png b/Resources/social-twitter.png new file mode 100644 index 00000000..754a51b8 Binary files /dev/null and b/Resources/social-twitter.png differ diff --git a/Resources/social-twitter@2x.png b/Resources/social-twitter@2x.png new file mode 100644 index 00000000..a75452d0 Binary files /dev/null and b/Resources/social-twitter@2x.png differ