teachers-assistant

[RADIOACTIVE] oh boy did i make bad apps back in the day
git clone git://git.figbert.com/teachers-assistant.git
Log | Files | Refs

commit ceabaff16c95b9fd37947063fabbd3b042a6eb79
parent c333c18e2bd88a33b2b4586bf9da93ee4a4871e3
Author: Naomi Welner <naomi@Naomis-MacBook-Air.local>
Date:   Tue, 12 Mar 2019 13:07:22 -0700

Adding existing files

Diffstat:
M.DS_Store | 0
MPodfile | 2+-
MPodfile.lock | 21+++++----------------
DPods/GTMSessionFetcher/LICENSE | 202-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/README.md | 23-----------------------
DPods/GTMSessionFetcher/Source/GTMGatherInputStream.h | 52----------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMGatherInputStream.m | 185-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMMIMEDocument.h | 148-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMMIMEDocument.m | 631-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h | 49-------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m | 190-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionFetcher.h | 1305-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionFetcher.m | 4579-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h | 112-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m | 982-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionFetcherService.h | 193-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionFetcherService.m | 1365-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h | 166-------------------------------------------------------------------------------
DPods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m | 1954-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/LICENSE | 202-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/README.md | 48------------------------------------------------
DPods/GoogleAPIClientForREST/Source/GTLRDefines.h | 109-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRBatchQuery.h | 85-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRBatchQuery.m | 179-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRBatchResult.h | 78------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRBatchResult.m | 168-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRDateTime.h | 115-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRDateTime.m | 373-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRDuration.h | 83-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRDuration.m | 222-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRErrorObject.h | 116-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRErrorObject.m | 140-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRObject.h | 317-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRObject.m | 760-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRQuery.h | 253-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRQuery.m | 313-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.h | 73-------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.m | 1060-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRService.h | 879-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRService.m | 2883-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRUploadParameters.h | 124-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Objects/GTLRUploadParameters.m | 119-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRBase64.h | 29-----------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRBase64.m | 143-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRFramework.h | 34----------------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRFramework.m | 44--------------------------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRURITemplate.h | 48------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRURITemplate.m | 511-------------------------------------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRUtilities.h | 52----------------------------------------------------
DPods/GoogleAPIClientForREST/Source/Utilities/GTLRUtilities.m | 117-------------------------------------------------------------------------------
MPods/Manifest.lock | 21+++++----------------
MPods/Pods.xcodeproj/project.pbxproj | 793++++++++++++++++---------------------------------------------------------------
DPods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme | 60------------------------------------------------------------
DPods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/GoogleAPIClientForREST.xcscheme | 60------------------------------------------------------------
MPods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/Pods-TeachersAssistant.xcscheme | 4++--
APods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/SheetsTranslator.xcscheme | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MPods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/xcschememanagement.plist | 17+++++------------
APods/SheetsTranslator/LICENSE | 21+++++++++++++++++++++
APods/SheetsTranslator/sheets | 0
DPods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist | 26--------------------------
DPods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m | 5-----
DPods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch | 12------------
DPods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h | 23-----------------------
DPods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap | 6------
DPods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.xcconfig | 9---------
DPods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-Info.plist | 26--------------------------
DPods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-dummy.m | 5-----
DPods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-prefix.pch | 12------------
DPods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-umbrella.h | 31-------------------------------
DPods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap | 6------
DPods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.xcconfig | 9---------
MPods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-acknowledgements.markdown | 423++++---------------------------------------------------------------------------
MPods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-acknowledgements.plist | 439+++++--------------------------------------------------------------------------
DPods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-frameworks.sh | 165-------------------------------------------------------------------------------
MPods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.debug.xcconfig | 3---
MPods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.release.xcconfig | 3---
MPods/Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.debug.xcconfig | 3---
MPods/Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.release.xcconfig | 3---
APods/Target Support Files/SheetsTranslator/SheetsTranslator.xcconfig | 8++++++++
MTeachers' Assistant/AppDelegate.swift | 16+++++++++++++++-
MTeachers' Assistant/ViewController.swift | 1-
MTeachers' Assistant/ViewControllerHome.swift | 3+--
MTeachers' Assistant/ViewControllerSettings.swift | 1-
MTeachersAssistant.xcodeproj/project.pbxproj | 81+++++++++++++++++++++++++++++--------------------------------------------------
MTeachersAssistant.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/xcschememanagement.plist | 2+-
MTeachersAssistant.xcworkspace/xcuserdata/naomi.xcuserdatad/UserInterfaceState.xcuserstate | 0
86 files changed, 352 insertions(+), 23841 deletions(-)

diff --git a/.DS_Store b/.DS_Store Binary files differ. diff --git a/Podfile b/Podfile @@ -4,7 +4,7 @@ platform :ios, '9.0' target 'TeachersAssistant' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! - pod 'GoogleAPIClientForREST', '~> 1.3.8' + pod 'SheetsTranslator', '~> 0.3' target 'TeachersAssistantTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock @@ -1,27 +1,16 @@ PODS: - - GoogleAPIClientForREST (1.3.8): - - GoogleAPIClientForREST/Core (= 1.3.8) - - GTMSessionFetcher (>= 1.1.7) - - GoogleAPIClientForREST/Core (1.3.8): - - GTMSessionFetcher (>= 1.1.7) - - GTMSessionFetcher (1.2.1): - - GTMSessionFetcher/Full (= 1.2.1) - - GTMSessionFetcher/Core (1.2.1) - - GTMSessionFetcher/Full (1.2.1): - - GTMSessionFetcher/Core (= 1.2.1) + - SheetsTranslator (0.3) DEPENDENCIES: - - GoogleAPIClientForREST (~> 1.3.8) + - SheetsTranslator (~> 0.3) SPEC REPOS: https://github.com/cocoapods/specs.git: - - GoogleAPIClientForREST - - GTMSessionFetcher + - SheetsTranslator SPEC CHECKSUMS: - GoogleAPIClientForREST: 5447a194eae517986cafe6421a5330b80b820591 - GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca + SheetsTranslator: eb4971995a2b9c5324724e51dc183137d9654ee8 -PODFILE CHECKSUM: dbc5c2766ede4673e953e610de8390e5215f6c55 +PODFILE CHECKSUM: f6bd879d2a8c3815ad086d6049bd0ec6a3540bf6 COCOAPODS: 1.6.1 diff --git a/Pods/GTMSessionFetcher/LICENSE b/Pods/GTMSessionFetcher/LICENSE @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/Pods/GTMSessionFetcher/README.md b/Pods/GTMSessionFetcher/README.md @@ -1,23 +0,0 @@ -# Google Toolbox for Mac - Session Fetcher # - -**Project site** <https://github.com/google/gtm-session-fetcher><br> -**Discussion group** <http://groups.google.com/group/google-toolbox-for-mac> - -[![Build Status](https://travis-ci.org/google/gtm-session-fetcher.svg?branch=master)](https://travis-ci.org/google/gtm-session-fetcher) - -`GTMSessionFetcher` makes it easy for Cocoa applications to perform http -operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its -behavior is asynchronous and uses operating-system settings on iOS and Mac OS X. - -Features include: -- Simple to build; only one source/header file pair is required -- Simple to use: takes just two lines of code to fetch a request -- Supports upload and download sessions -- Flexible cookie storage -- Automatic retry on errors, with exponential backoff -- Support for generating multipart MIME upload streams -- Easy, convenient logging of http requests and responses -- Supports plug-in authentication such as with GTMAppAuth -- Easily testable; self-mocking -- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class -- Fully independent of other projects diff --git a/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h @@ -1,52 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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. - */ - -// The GTMGatherInput stream is an input stream implementation that is to be -// instantiated with an NSArray of NSData objects. It works in the traditional -// scatter/gather vector I/O model. Rather than allocating a big NSData object -// to hold all of the data and performing a copy into that object, the -// GTMGatherInputStream will maintain a reference to the NSArray and read from -// each NSData in turn as the read method is called. You should not alter the -// underlying set of NSData objects until all read operations on this input -// stream have completed. - -#import <Foundation/Foundation.h> - -#ifndef GTM_NONNULL - #if defined(__has_attribute) - #if __has_attribute(nonnull) - #define GTM_NONNULL(x) __attribute__((nonnull x)) - #else - #define GTM_NONNULL(x) - #endif - #else - #define GTM_NONNULL(x) - #endif -#endif - -// Avoid multiple declaration of this class. -// -// Note: This should match the declaration of GTMGatherInputStream in GTMMIMEDocument.m - -#ifndef GTM_GATHERINPUTSTREAM_DECLARED -#define GTM_GATHERINPUTSTREAM_DECLARED - -@interface GTMGatherInputStream : NSInputStream <NSStreamDelegate> - -+ (NSInputStream *)streamWithArray:(NSArray *)dataArray GTM_NONNULL((1)); - -@end - -#endif // GTM_GATHERINPUTSTREAM_DECLARED diff --git a/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m @@ -1,185 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#import "GTMGatherInputStream.h" - -@implementation GTMGatherInputStream { - NSArray *_dataArray; // NSDatas that should be "gathered" and streamed. - NSUInteger _arrayIndex; // Index in the array of the current NSData. - long long _dataOffset; // Offset in the current NSData we are processing. - NSStreamStatus _streamStatus; - id<NSStreamDelegate> __weak _delegate; // Stream delegate, defaults to self. -} - -+ (NSInputStream *)streamWithArray:(NSArray *)dataArray { - return [(GTMGatherInputStream *)[self alloc] initWithArray:dataArray]; -} - -- (instancetype)initWithArray:(NSArray *)dataArray { - self = [super init]; - if (self) { - _dataArray = dataArray; - _delegate = self; // An NSStream's default delegate should be self. - } - return self; -} - -#pragma mark - NSStream - -- (void)open { - _arrayIndex = 0; - _dataOffset = 0; - _streamStatus = NSStreamStatusOpen; -} - -- (void)close { - _streamStatus = NSStreamStatusClosed; -} - -- (id<NSStreamDelegate>)delegate { - return _delegate; -} - -- (void)setDelegate:(id<NSStreamDelegate>)delegate { - if (delegate == nil) { - _delegate = self; - } else { - _delegate = delegate; - } -} - -- (id)propertyForKey:(NSString *)key { - if ([key isEqual:NSStreamFileCurrentOffsetKey]) { - return @([self absoluteOffset]); - } - return nil; -} - -- (BOOL)setProperty:(id)property forKey:(NSString *)key { - if ([key isEqual:NSStreamFileCurrentOffsetKey]) { - NSNumber *absoluteOffsetNumber = property; - [self setAbsoluteOffset:absoluteOffsetNumber.longLongValue]; - return YES; - } - return NO; -} - -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { -} - -- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { -} - -- (NSStreamStatus)streamStatus { - return _streamStatus; -} - -- (NSError *)streamError { - return nil; -} - -#pragma mark - NSInputStream - -- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { - NSInteger bytesRead = 0; - NSUInteger bytesRemaining = len; - - // Read bytes from the currently-indexed array. - while ((bytesRemaining > 0) && (_arrayIndex < _dataArray.count)) { - NSData *data = [_dataArray objectAtIndex:_arrayIndex]; - - NSUInteger dataLen = data.length; - NSUInteger dataBytesLeft = dataLen - (NSUInteger)_dataOffset; - - NSUInteger bytesToCopy = MIN(bytesRemaining, dataBytesLeft); - NSRange range = NSMakeRange((NSUInteger) _dataOffset, bytesToCopy); - - [data getBytes:(buffer + bytesRead) range:range]; - - bytesRead += bytesToCopy; - _dataOffset += bytesToCopy; - bytesRemaining -= bytesToCopy; - - if (_dataOffset == (long long)dataLen) { - _dataOffset = 0; - _arrayIndex++; - } - } - if (_arrayIndex >= _dataArray.count) { - _streamStatus = NSStreamStatusAtEnd; - } - return bytesRead; -} - -- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { - return NO; // We don't support this style of reading. -} - -- (BOOL)hasBytesAvailable { - // If we return no, the read never finishes, even if we've already delivered all the bytes. - return YES; -} - -#pragma mark - NSStreamDelegate - -- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { - id<NSStreamDelegate> delegate = _delegate; - if (delegate != self) { - [delegate stream:self handleEvent:streamEvent]; - } -} - -#pragma mark - Private - -- (long long)absoluteOffset { - long long absoluteOffset = 0; - NSUInteger index = 0; - for (NSData *data in _dataArray) { - if (index >= _arrayIndex) { - break; - } - absoluteOffset += data.length; - ++index; - } - absoluteOffset += _dataOffset; - return absoluteOffset; -} - -- (void)setAbsoluteOffset:(long long)absoluteOffset { - if (absoluteOffset < 0) { - absoluteOffset = 0; - } - _arrayIndex = 0; - _dataOffset = absoluteOffset; - for (NSData *data in _dataArray) { - long long dataLen = (long long) data.length; - if (dataLen > _dataOffset) { - break; - } - _arrayIndex++; - _dataOffset -= dataLen; - } - if (_arrayIndex == _dataArray.count) { - if (_dataOffset > 0) { - _dataOffset = 0; - } - } -} - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h @@ -1,148 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// This is a simple class to create or parse a MIME document. - -// To create a MIME document, allocate a new GTMMIMEDocument and start adding parts. -// When you are done adding parts, call generateInputStream or generateDispatchData. -// -// A good reference for MIME is http://en.wikipedia.org/wiki/MIME - -#import <Foundation/Foundation.h> - -#ifndef GTM_NONNULL - #if defined(__has_attribute) - #if __has_attribute(nonnull) - #define GTM_NONNULL(x) __attribute__((nonnull x)) - #else - #define GTM_NONNULL(x) - #endif - #else - #define GTM_NONNULL(x) - #endif -#endif - -#ifndef GTM_DECLARE_GENERICS - #if __has_feature(objc_generics) - #define GTM_DECLARE_GENERICS 1 - #else - #define GTM_DECLARE_GENERICS 0 - #endif -#endif - -#ifndef GTM_NSArrayOf - #if GTM_DECLARE_GENERICS - #define GTM_NSArrayOf(value) NSArray<value> - #define GTM_NSDictionaryOf(key, value) NSDictionary<key, value> - #else - #define GTM_NSArrayOf(value) NSArray - #define GTM_NSDictionaryOf(key, value) NSDictionary - #endif // GTM_DECLARE_GENERICS -#endif // GTM_NSArrayOf - - -// GTMMIMEDocumentPart represents a part of a MIME document. -// -// +[GTMMIMEDocument MIMEPartsWithBoundary:data:] returns an array of these. -@interface GTMMIMEDocumentPart : NSObject - -@property(nonatomic, readonly) GTM_NSDictionaryOf(NSString *, NSString *) *headers; -@property(nonatomic, readonly) NSData *headerData; -@property(nonatomic, readonly) NSData *body; -@property(nonatomic, readonly) NSUInteger length; - -+ (instancetype)partWithHeaders:(NSDictionary *)headers body:(NSData *)body; - -@end - -@interface GTMMIMEDocument : NSObject - -// Get or set the unique boundary for the parts that have been added. -// -// When creating a MIME document from parts, this is typically calculated -// automatically after all parts have been added. -@property(nonatomic, copy) NSString *boundary; - -#pragma mark - Methods for Creating a MIME Document - -+ (instancetype)MIMEDocument; - -// Adds a new part to this mime document with the given headers and body. -// The headers keys and values should be NSStrings. -// Adding a part may cause the boundary string to change. -- (void)addPartWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers - body:(NSData *)body GTM_NONNULL((1,2)); - -// An inputstream that can be used to efficiently read the contents of the MIME document. -// -// Any parameter may be null if the result is not wanted. -- (void)generateInputStream:(NSInputStream **)outStream - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary; - -// A dispatch_data_t with the contents of the MIME document. -// -// Note: dispatch_data_t is one-way toll-free bridged so the result -// may be cast directly to NSData *. -// -// Any parameter may be null if the result is not wanted. -- (void)generateDispatchData:(dispatch_data_t *)outDispatchData - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary; - -// Utility method for making a header section, including trailing newlines. -+ (NSData *)dataWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers; - -#pragma mark - Methods for Parsing a MIME Document - -// Method for parsing out an array of MIME parts from a MIME document. -// -// Returns an array of GTMMIMEDocumentParts. Returns nil if no part can -// be found. -+ (GTM_NSArrayOf(GTMMIMEDocumentPart *) *)MIMEPartsWithBoundary:(NSString *)boundary - data:(NSData *)fullDocumentData; - -// Utility method for efficiently searching possibly discontiguous NSData -// for occurrences of target byte. This method does not "flatten" an NSData -// that is composed of discontiguous blocks. -// -// The byte offsets of non-overlapping occurrences of the target are returned as -// NSNumbers in the array. -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets; - -// Utility method to parse header bytes into an NSDictionary. -+ (GTM_NSDictionaryOf(NSString *, NSString *) *)headersWithData:(NSData *)data; - -// ------ UNIT TESTING ONLY BELOW ------ - -// Internal methods, exposed for unit testing only. -- (void)seedRandomWith:(u_int32_t)seed; - -+ (NSUInteger)findBytesWithNeedle:(const unsigned char *)needle - needleLength:(NSUInteger)needleLength - haystack:(const unsigned char *)haystack - haystackLength:(NSUInteger)haystackLength - foundOffset:(NSUInteger *)foundOffset; - -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets - foundBlockNumbers:(GTM_NSArrayOf(NSNumber *) **)outFoundBlockNumbers; - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m @@ -1,631 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#import "GTMMIMEDocument.h" - -// Avoid a hard dependency on GTMGatherInputStream. -#ifndef GTM_GATHERINPUTSTREAM_DECLARED -#define GTM_GATHERINPUTSTREAM_DECLARED - -@interface GTMGatherInputStream : NSInputStream <NSStreamDelegate> - -+ (NSInputStream *)streamWithArray:(NSArray *)dataArray GTM_NONNULL((1)); - -@end -#endif // GTM_GATHERINPUTSTREAM_DECLARED - -// FindBytes -// -// Helper routine to search for the existence of a set of bytes (needle) within -// a presumed larger set of bytes (haystack). Can find the first part of the -// needle at the very end of the haystack. -// -// Returns the needle length on complete success, the number of bytes matched -// if a partial needle was found at the end of the haystack, and 0 on failure. -static NSUInteger FindBytes(const unsigned char *needle, NSUInteger needleLen, - const unsigned char *haystack, NSUInteger haystackLen, - NSUInteger *foundOffset); - -// SearchDataForBytes -// -// This implements the functionality of the +searchData: methods below. See the documentation -// for those methods. -static void SearchDataForBytes(NSData *data, const void *targetBytes, NSUInteger targetLength, - NSMutableArray *foundOffsets, NSMutableArray *foundBlockNumbers); - -@implementation GTMMIMEDocumentPart { - NSDictionary *_headers; - NSData *_headerData; // Header content including the ending "\r\n". - NSData *_bodyData; -} - -@synthesize headers = _headers, - headerData = _headerData, - body = _bodyData; - -@dynamic length; - -+ (instancetype)partWithHeaders:(NSDictionary *)headers body:(NSData *)body { - return [[self alloc] initWithHeaders:headers body:body]; -} - -- (instancetype)initWithHeaders:(NSDictionary *)headers body:(NSData *)body { - self = [super init]; - if (self) { - _bodyData = body; - _headers = headers; - } - return self; -} - -// Returns true if the part's header or data contain the given set of bytes. -// -// NOTE: We assume that the 'bytes' we are checking for do not contain "\r\n", -// so we don't need to check the concatenation of the header and body bytes. -- (BOOL)containsBytes:(const unsigned char *)bytes length:(NSUInteger)length { - // This uses custom search code rather than strcpy because the encoded data may contain - // null values. - NSData *headerData = self.headerData; - return (FindBytes(bytes, length, headerData.bytes, headerData.length, NULL) == length || - FindBytes(bytes, length, _bodyData.bytes, _bodyData.length, NULL) == length); -} - -- (NSData *)headerData { - if (!_headerData) { - _headerData = [GTMMIMEDocument dataWithHeaders:_headers]; - } - return _headerData; -} - -- (NSData *)body { - return _bodyData; -} - -- (NSUInteger)length { - return _headerData.length + _bodyData.length; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p (headers %lu keys, body %lu bytes)", - [self class], self, (unsigned long)_headers.count, - (unsigned long)_bodyData.length]; -} - -- (BOOL)isEqual:(GTMMIMEDocumentPart *)other { - if (self == other) return YES; - if (![other isKindOfClass:[GTMMIMEDocumentPart class]]) return NO; - return ((_bodyData == other->_bodyData || [_bodyData isEqual:other->_bodyData]) - && (_headers == other->_headers || [_headers isEqual:other->_headers])); -} - -- (NSUInteger)hash { - return _bodyData.hash | _headers.hash; -} - -@end - -@implementation GTMMIMEDocument { - NSMutableArray *_parts; // Ordered array of GTMMIMEDocumentParts. - unsigned long long _length; // Length in bytes of the document. - NSString *_boundary; - u_int32_t _randomSeed; // For testing. -} - -+ (instancetype)MIMEDocument { - return [[self alloc] init]; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _parts = [[NSMutableArray alloc] init]; - } - return self; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p (%lu parts)", - [self class], self, (unsigned long)_parts.count]; -} - -#pragma mark - Joining Parts - -// Adds a new part to this mime document with the given headers and body. -- (void)addPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { - GTMMIMEDocumentPart *part = [GTMMIMEDocumentPart partWithHeaders:headers body:body]; - [_parts addObject:part]; - _boundary = nil; -} - -// For unit testing only, seeds the random number generator so that we will -// have reproducible boundary strings. -- (void)seedRandomWith:(u_int32_t)seed { - _randomSeed = seed; - _boundary = nil; -} - -- (u_int32_t)random { - if (_randomSeed) { - // For testing only. - return _randomSeed++; - } else { - return arc4random(); - } -} - -// Computes the mime boundary to use. This should only be called -// after all the desired document parts have been added since it must compute -// a boundary that does not exist in the document data. -- (NSString *)boundary { - if (_boundary) { - return _boundary; - } - - // Use an easily-readable boundary string. - NSString *const kBaseBoundary = @"END_OF_PART"; - - _boundary = kBaseBoundary; - - // If the boundary isn't unique, append random numbers, up to 10 attempts; - // if that's still not unique, use a random number sequence instead, and call it good. - BOOL didCollide = NO; - - const int maxTries = 10; // Arbitrarily chosen maximum attempts. - for (int tries = 0; tries < maxTries; ++tries) { - - NSData *data = [_boundary dataUsingEncoding:NSUTF8StringEncoding]; - const void *dataBytes = data.bytes; - NSUInteger dataLen = data.length; - - for (GTMMIMEDocumentPart *part in _parts) { - didCollide = [part containsBytes:dataBytes length:dataLen]; - if (didCollide) break; - } - - if (!didCollide) break; // We're fine, no more attempts needed. - - // Try again with a random number appended. - _boundary = [NSString stringWithFormat:@"%@_%08x", kBaseBoundary, [self random]]; - } - - if (didCollide) { - // Fallback... two random numbers. - _boundary = [NSString stringWithFormat:@"%08x_tedborg_%08x", [self random], [self random]]; - } - return _boundary; -} - -- (void)setBoundary:(NSString *)str { - _boundary = [str copy]; -} - -// Internal method. -- (void)generateDataArray:(NSMutableArray *)dataArray - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary { - - // The input stream is of the form: - // --boundary - // [part_1_headers] - // [part_1_data] - // --boundary - // [part_2_headers] - // [part_2_data] - // --boundary-- - - // First we set up our boundary NSData objects. - NSString *boundary = self.boundary; - - NSString *mainBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n", boundary]; - NSString *endBoundary = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary]; - - NSData *mainBoundaryData = [mainBoundary dataUsingEncoding:NSUTF8StringEncoding]; - NSData *endBoundaryData = [endBoundary dataUsingEncoding:NSUTF8StringEncoding]; - - // Now we add them all in proper order to our dataArray. - unsigned long long length = 0; - - for (GTMMIMEDocumentPart *part in _parts) { - [dataArray addObject:mainBoundaryData]; - [dataArray addObject:part.headerData]; - [dataArray addObject:part.body]; - - length += part.length + mainBoundaryData.length; - } - - [dataArray addObject:endBoundaryData]; - length += endBoundaryData.length; - - if (outLength) *outLength = length; - if (outBoundary) *outBoundary = boundary; -} - -- (void)generateInputStream:(NSInputStream **)outStream - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary { - NSMutableArray *dataArray = outStream ? [NSMutableArray array] : nil; - [self generateDataArray:dataArray - length:outLength - boundary:outBoundary]; - - if (outStream) { - Class streamClass = NSClassFromString(@"GTMGatherInputStream"); - NSAssert(streamClass != nil, @"GTMGatherInputStream not available."); - - *outStream = [streamClass streamWithArray:dataArray]; - } -} - -- (void)generateDispatchData:(dispatch_data_t *)outDispatchData - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary { - NSMutableArray *dataArray = outDispatchData ? [NSMutableArray array] : nil; - [self generateDataArray:dataArray - length:outLength - boundary:outBoundary]; - - if (outDispatchData) { - // Create an empty data accumulator. - dispatch_data_t dataAccumulator; - - dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - - for (NSData *partData in dataArray) { - __block NSData *immutablePartData = [partData copy]; - dispatch_data_t newDataPart = - dispatch_data_create(immutablePartData.bytes, immutablePartData.length, bgQueue, ^{ - // We want the data retained until this block executes. - immutablePartData = nil; - }); - - if (dataAccumulator == nil) { - // First part. - dataAccumulator = newDataPart; - } else { - // Append the additional part. - dataAccumulator = dispatch_data_create_concat(dataAccumulator, newDataPart); - } - } - *outDispatchData = dataAccumulator; - } -} - -+ (NSData *)dataWithHeaders:(NSDictionary *)headers { - // Generate the header data by coalescing the dictionary as lines of "key: value\r\n". - NSMutableString* headerString = [NSMutableString string]; - - // Sort the header keys so we have a deterministic order for unit testing. - SEL sortSel = @selector(caseInsensitiveCompare:); - NSArray *sortedKeys = [headers.allKeys sortedArrayUsingSelector:sortSel]; - - for (NSString *key in sortedKeys) { - NSString *value = [headers objectForKey:key]; - -#if DEBUG - // Look for troublesome characters in the header keys & values. - NSCharacterSet *badKeyChars = [NSCharacterSet characterSetWithCharactersInString:@":\r\n"]; - NSCharacterSet *badValueChars = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"]; - - NSRange badRange = [key rangeOfCharacterFromSet:badKeyChars]; - NSAssert(badRange.location == NSNotFound, @"invalid key: %@", key); - - badRange = [value rangeOfCharacterFromSet:badValueChars]; - NSAssert(badRange.location == NSNotFound, @"invalid value: %@", value); -#endif - - [headerString appendFormat:@"%@: %@\r\n", key, value]; - } - // Headers end with an extra blank line. - [headerString appendString:@"\r\n"]; - - NSData *result = [headerString dataUsingEncoding:NSUTF8StringEncoding]; - return result; -} - -#pragma mark - Separating Parts - -+ (NSArray *)MIMEPartsWithBoundary:(NSString *)boundary - data:(NSData *)fullDocumentData { - // In MIME documents, the boundary is preceded by CRLF and two dashes, and followed - // at the end by two dashes. - NSData *boundaryData = [boundary dataUsingEncoding:NSUTF8StringEncoding]; - NSUInteger boundaryLength = boundaryData.length; - - NSMutableArray *foundBoundaryOffsets; - [self searchData:fullDocumentData - targetBytes:boundaryData.bytes - targetLength:boundaryLength - foundOffsets:&foundBoundaryOffsets]; - - // According to rfc1341, ignore anything before the first boundary, or after the last, though two - // dashes are expected to follow the last boundary. - if (foundBoundaryOffsets.count < 2) { - return nil; - } - - // Wrap the full document data with a dispatch_data_t for more efficient slicing - // and dicing. - dispatch_data_t dataWrapper; - if ([fullDocumentData conformsToProtocol:@protocol(OS_dispatch_data)]) { - dataWrapper = (dispatch_data_t)fullDocumentData; - } else { - // A no-op self invocation on fullDocumentData will keep it retained until the block is invoked. - dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dataWrapper = dispatch_data_create(fullDocumentData.bytes, - fullDocumentData.length, - bgQueue, ^{ [fullDocumentData self]; }); - } - NSMutableArray *parts; - NSInteger previousBoundaryOffset = -1; - NSInteger partCounter = -1; - NSInteger numberOfPartsWithHeaders = 0; - for (NSNumber *currentBoundaryOffset in foundBoundaryOffsets) { - ++partCounter; - if (previousBoundaryOffset == -1) { - // This is the first boundary. - previousBoundaryOffset = currentBoundaryOffset.integerValue; - continue; - } else { - // Create a part data subrange between the previous boundary and this one. - // - // The last four bytes before a boundary are CRLF--. - // The first two bytes following a boundary are either CRLF or, for the last boundary, --. - NSInteger previousPartDataStartOffset = - previousBoundaryOffset + (NSInteger)boundaryLength + 2; - NSInteger previousPartDataEndOffset = currentBoundaryOffset.integerValue - 4; - NSInteger previousPartDataLength = previousPartDataEndOffset - previousPartDataStartOffset; - - if (previousPartDataLength < 2) { - // The preceding part was too short to be useful. -#if DEBUG - NSLog(@"MIME part %ld has %ld bytes", (long)partCounter - 1, - (long)previousPartDataLength); -#endif - } else { - if (!parts) parts = [NSMutableArray array]; - - dispatch_data_t partData = - dispatch_data_create_subrange(dataWrapper, - (size_t)previousPartDataStartOffset, (size_t)previousPartDataLength); - // Scan the part data for the separator between headers and body. After the CRLF, - // either the headers start immediately, or there's another CRLF and there are no headers. - // - // We need to map the part data to get the first two bytes. (Or we could cast it to - // NSData and get the bytes pointer of that.) If we're concerned that a single part - // data may be expensive to map, we could make a subrange here for just the first two bytes, - // and map that two-byte subrange. - const void *partDataBuffer; - size_t partDataBufferSize; - dispatch_data_t mappedPartData NS_VALID_UNTIL_END_OF_SCOPE = - dispatch_data_create_map(partData, &partDataBuffer, &partDataBufferSize); - dispatch_data_t bodyData; - NSDictionary *headers; - BOOL hasAnotherCRLF = (((char *)partDataBuffer)[0] == '\r' - && ((char *)partDataBuffer)[1] == '\n'); - mappedPartData = nil; - - if (hasAnotherCRLF) { - // There are no headers; skip the CRLF to get to the body, and leave headers nil. - bodyData = dispatch_data_create_subrange(partData, 2, (size_t)previousPartDataLength - 2); - } else { - // There are part headers. They are separated from body data by CRLFCRLF. - NSArray *crlfOffsets; - [self searchData:(NSData *)partData - targetBytes:"\r\n\r\n" - targetLength:4 - foundOffsets:&crlfOffsets]; - if (crlfOffsets.count == 0) { -#if DEBUG - // We could not distinguish body and headers. - NSLog(@"MIME part %ld lacks a header separator: %@", (long)partCounter - 1, - [[NSString alloc] initWithData:(NSData *)partData encoding:NSUTF8StringEncoding]); -#endif - } else { - NSInteger headerSeparatorOffset = ((NSNumber *)crlfOffsets.firstObject).integerValue; - dispatch_data_t headerData = - dispatch_data_create_subrange(partData, 0, (size_t)headerSeparatorOffset); - headers = [self headersWithData:(NSData *)headerData]; - - bodyData = dispatch_data_create_subrange(partData, (size_t)headerSeparatorOffset + 4, - (size_t)(previousPartDataLength - (headerSeparatorOffset + 4))); - - numberOfPartsWithHeaders++; - } // crlfOffsets.count == 0 - } // hasAnotherCRLF - GTMMIMEDocumentPart *part = [GTMMIMEDocumentPart partWithHeaders:headers - body:(NSData *)bodyData]; - [parts addObject:part]; - } // previousPartDataLength < 2 - previousBoundaryOffset = currentBoundaryOffset.integerValue; - } - } -#if DEBUG - // In debug builds, warn if a reasonably long document lacks any CRLF characters. - if (numberOfPartsWithHeaders == 0) { - NSUInteger length = fullDocumentData.length; - if (length > 20) { // Reasonably long. - NSMutableArray *foundCRLFs; - [self searchData:fullDocumentData - targetBytes:"\r\n" - targetLength:2 - foundOffsets:&foundCRLFs]; - if (foundCRLFs.count == 0) { - // Parts were logged above (due to lacking header separators.) - NSLog(@"Warning: MIME document lacks any headers (may have wrong line endings)"); - } - } - } -#endif // DEBUG - return parts; -} - -// Efficiently search the supplied data for the target bytes. -// -// This uses enumerateByteRangesUsingBlock: to scan for bytes. It can find -// the target even if it spans multiple separate byte ranges. -// -// Returns an array of found byte offset values, as NSNumbers. -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets { - NSMutableArray *foundOffsets = [NSMutableArray array]; - SearchDataForBytes(data, targetBytes, targetLength, foundOffsets, NULL); - *outFoundOffsets = foundOffsets; -} - - -// This version of searchData: also returns the block numbers (0-based) where the -// target was found, used for testing that the supplied dispatch_data buffer -// has not been flattened. -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets - foundBlockNumbers:(GTM_NSArrayOf(NSNumber *) **)outFoundBlockNumbers { - NSMutableArray *foundOffsets = [NSMutableArray array]; - NSMutableArray *foundBlockNumbers = [NSMutableArray array]; - - SearchDataForBytes(data, targetBytes, targetLength, foundOffsets, foundBlockNumbers); - *outFoundOffsets = foundOffsets; - *outFoundBlockNumbers = foundBlockNumbers; -} - -static void SearchDataForBytes(NSData *data, const void *targetBytes, NSUInteger targetLength, - NSMutableArray *foundOffsets, NSMutableArray *foundBlockNumbers) { - __block NSUInteger priorPartialMatchAmount = 0; - __block NSInteger priorPartialMatchStartingBlockNumber = -1; - __block NSInteger blockNumber = -1; - - [data enumerateByteRangesUsingBlock:^(const void *bytes, - NSRange byteRange, - BOOL *stop) { - // Search for the first character in the current range. - const void *ptr = bytes; - NSInteger remainingInCurrentRange = (NSInteger)byteRange.length; - ++blockNumber; - - if (priorPartialMatchAmount > 0) { - NSUInteger amountRemainingToBeMatched = targetLength - priorPartialMatchAmount; - NSUInteger remainingFoundOffset; - NSUInteger amountMatched = FindBytes(targetBytes + priorPartialMatchAmount, - amountRemainingToBeMatched, - ptr, (NSUInteger)remainingInCurrentRange, &remainingFoundOffset); - if (amountMatched == 0 || remainingFoundOffset > 0) { - // No match of the rest of the prior partial match in this range. - } else if (amountMatched < amountRemainingToBeMatched) { - // Another partial match; we're done with this range. - priorPartialMatchAmount = priorPartialMatchAmount + amountMatched; - return; - } else { - // The offset is in an earlier range. - NSUInteger offset = byteRange.location - priorPartialMatchAmount; - [foundOffsets addObject:@(offset)]; - [foundBlockNumbers addObject:@(priorPartialMatchStartingBlockNumber)]; - priorPartialMatchStartingBlockNumber = -1; - } - priorPartialMatchAmount = 0; - } - - while (remainingInCurrentRange > 0) { - NSUInteger offsetFromPtr; - NSUInteger amountMatched = FindBytes(targetBytes, targetLength, ptr, - (NSUInteger)remainingInCurrentRange, &offsetFromPtr); - if (amountMatched == 0) { - // No match in this range. - return; - } - if (amountMatched < targetLength) { - // Found a partial target. If there's another range, we'll check for the rest. - priorPartialMatchAmount = amountMatched; - priorPartialMatchStartingBlockNumber = blockNumber; - return; - } - // Found the full target. - NSUInteger globalOffset = byteRange.location + (NSUInteger)(ptr - bytes) + offsetFromPtr; - - [foundOffsets addObject:@(globalOffset)]; - [foundBlockNumbers addObject:@(blockNumber)]; - - ptr += targetLength + offsetFromPtr; - remainingInCurrentRange -= (targetLength + offsetFromPtr); - } - }]; -} - -// Internal method only for testing; this calls through the static method. -+ (NSUInteger)findBytesWithNeedle:(const unsigned char *)needle - needleLength:(NSUInteger)needleLength - haystack:(const unsigned char *)haystack - haystackLength:(NSUInteger)haystackLength - foundOffset:(NSUInteger *)foundOffset { - return FindBytes(needle, needleLength, haystack, haystackLength, foundOffset); -} - -// Utility method to parse header bytes into an NSDictionary. -+ (NSDictionary *)headersWithData:(NSData *)data { - NSString *headersString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (!headersString) return nil; - - NSMutableDictionary *headers = [NSMutableDictionary dictionary]; - NSScanner *scanner = [NSScanner scannerWithString:headersString]; - // The scanner is skipping leading whitespace and newline characters by default. - NSCharacterSet *newlineCharacters = [NSCharacterSet newlineCharacterSet]; - NSString *key; - NSString *value; - while ([scanner scanUpToString:@":" intoString:&key] - && [scanner scanString:@":" intoString:NULL] - && [scanner scanUpToCharactersFromSet:newlineCharacters intoString:&value]) { - [headers setObject:value forKey:key]; - // Discard the trailing newline. - [scanner scanCharactersFromSet:newlineCharacters intoString:NULL]; - } - return headers; -} - -@end - -// Return how much of the needle was found in the haystack. -// -// If the result is less than needleLen, then the beginning of the needle -// was found at the end of the haystack. -static NSUInteger FindBytes(const unsigned char* needle, NSUInteger needleLen, - const unsigned char* haystack, NSUInteger haystackLen, - NSUInteger *foundOffset) { - const unsigned char *ptr = haystack; - NSInteger remain = (NSInteger)haystackLen; - // Assume memchr is an efficient way to find a match for the first - // byte of the needle, and memcmp is an efficient way to compare a - // range of bytes. - while (remain > 0 && (ptr = memchr(ptr, needle[0], (size_t)remain)) != 0) { - // The first character is present. - NSUInteger offset = (NSUInteger)(ptr - haystack); - remain = (NSInteger)(haystackLen - offset); - - NSUInteger amountToCompare = MIN((NSUInteger)remain, needleLen); - if (memcmp(ptr, needle, amountToCompare) == 0) { - if (foundOffset) *foundOffset = offset; - return amountToCompare; - } - ptr++; - remain--; - } - if (foundOffset) *foundOffset = 0; - return 0; -} diff --git a/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h @@ -1,49 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import <Foundation/Foundation.h> - -#ifndef GTM_NONNULL - #if defined(__has_attribute) - #if __has_attribute(nonnull) - #define GTM_NONNULL(x) __attribute__((nonnull x)) - #else - #define GTM_NONNULL(x) - #endif - #else - #define GTM_NONNULL(x) - #endif -#endif - - -@interface GTMReadMonitorInputStream : NSInputStream <NSStreamDelegate> - -+ (instancetype)inputStreamWithStream:(NSInputStream *)input GTM_NONNULL((1)); - -- (instancetype)initWithStream:(NSInputStream *)input GTM_NONNULL((1)); - -// The read monitor selector is called when bytes have been read. It should have this signature: -// -// - (void)inputStream:(GTMReadMonitorInputStream *)stream -// readIntoBuffer:(uint8_t *)buffer -// length:(int64_t)length; - -@property(atomic, weak) id readDelegate; -@property(atomic, assign) SEL readSelector; - -// Modes for invoking callbacks, when necessary. -@property(atomic, strong) NSArray *runLoopModes; - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m @@ -1,190 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#import "GTMReadMonitorInputStream.h" - -@implementation GTMReadMonitorInputStream { - NSInputStream *_inputStream; // Encapsulated stream that does the work. - - NSThread *_thread; // Thread in which this object was created. - NSArray *_runLoopModes; // Modes for calling callbacks, when necessary. -} - - -@synthesize readDelegate = _readDelegate; -@synthesize readSelector = _readSelector; -@synthesize runLoopModes = _runLoopModes; - -// We'll forward all unhandled messages to the NSInputStream class or to the encapsulated input -// stream. This is needed for all messages sent to NSInputStream which aren't handled by our -// superclass; that includes various private run loop calls. -+ (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - return [NSInputStream methodSignatureForSelector:selector]; -} - -+ (void)forwardInvocation:(NSInvocation*)invocation { - [invocation invokeWithTarget:[NSInputStream class]]; -} - -- (BOOL)respondsToSelector:(SEL)selector { - return [_inputStream respondsToSelector:selector]; -} - -- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { - return [_inputStream methodSignatureForSelector:selector]; -} - -- (void)forwardInvocation:(NSInvocation*)invocation { - [invocation invokeWithTarget:_inputStream]; -} - -#pragma mark - - -+ (instancetype)inputStreamWithStream:(NSInputStream *)input { - return [[self alloc] initWithStream:input]; -} - -- (instancetype)initWithStream:(NSInputStream *)input { - self = [super init]; - if (self) { - _inputStream = input; - _thread = [NSThread currentThread]; - } - return self; -} - -- (instancetype)init { - [self doesNotRecognizeSelector:_cmd]; - return nil; -} - -#pragma mark - - -- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { - // Read from the encapsulated stream. - NSInteger numRead = [_inputStream read:buffer maxLength:len]; - if (numRead > 0) { - if (_readDelegate && _readSelector) { - // Call the read selector with the buffer and number of bytes actually read into it. - BOOL isOnOriginalThread = [_thread isEqual:[NSThread currentThread]]; - if (isOnOriginalThread) { - // Invoke immediately. - NSData *data = [NSData dataWithBytesNoCopy:buffer - length:(NSUInteger)numRead - freeWhenDone:NO]; - [self invokeReadSelectorWithBuffer:data]; - } else { - // Copy the buffer into an NSData to be retained by the performSelector, - // and invoke on the proper thread. - SEL sel = @selector(invokeReadSelectorWithBuffer:); - NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numRead]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - if (_runLoopModes) { - [self performSelector:sel - onThread:_thread - withObject:data - waitUntilDone:NO - modes:_runLoopModes]; - } else { - [self performSelector:sel - onThread:_thread - withObject:data - waitUntilDone:NO]; - } -#pragma clang diagnostic pop - } - } - } - return numRead; -} - -- (void)invokeReadSelectorWithBuffer:(NSData *)data { - const void *buffer = data.bytes; - int64_t length = (int64_t)data.length; - - id argSelf = self; - id readDelegate = _readDelegate; - if (readDelegate) { - NSMethodSignature *signature = [readDelegate methodSignatureForSelector:_readSelector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:_readSelector]; - [invocation setTarget:readDelegate]; - [invocation setArgument:&argSelf atIndex:2]; - [invocation setArgument:&buffer atIndex:3]; - [invocation setArgument:&length atIndex:4]; - [invocation invoke]; - } -} - -- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { - return [_inputStream getBuffer:buffer length:len]; -} - -- (BOOL)hasBytesAvailable { - return [_inputStream hasBytesAvailable]; -} - -#pragma mark Standard messages - -// Pass expected messages to our encapsulated stream. -// -// We want our encapsulated NSInputStream to handle the standard messages; -// we don't want the superclass to handle them. -- (void)open { - [_inputStream open]; -} - -- (void)close { - [_inputStream close]; -} - -- (id)delegate { - return [_inputStream delegate]; -} - -- (void)setDelegate:(id)delegate { - [_inputStream setDelegate:delegate]; -} - -- (id)propertyForKey:(NSString *)key { - return [_inputStream propertyForKey:key]; -} - -- (BOOL)setProperty:(id)property forKey:(NSString *)key { - return [_inputStream setProperty:property forKey:key]; -} - -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { - [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; -} - -- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { - [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; -} - -- (NSStreamStatus)streamStatus { - return [_inputStream streamStatus]; -} - -- (NSError *)streamError { - return [_inputStream streamError]; -} - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h @@ -1,1305 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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. - */ - -// GTMSessionFetcher is a wrapper around NSURLSession for http operations. -// -// What does this offer on top of of NSURLSession? -// -// - Block-style callbacks for useful functionality like progress rather -// than delegate methods. -// - Out-of-process uploads and downloads using NSURLSession, including -// management of fetches after relaunch. -// - Integration with GTMAppAuth for invisible management and refresh of -// authorization tokens. -// - Pretty-printed http logging. -// - Cookies handling that does not interfere with or get interfered with -// by WebKit cookies or on Mac by Safari and other apps. -// - Credentials handling for the http operation. -// - Rate-limiting and cookie grouping when fetchers are created with -// GTMSessionFetcherService. -// -// If the bodyData or bodyFileURL property is set, then a POST request is assumed. -// -// Each fetcher is assumed to be for a one-shot fetch request; don't reuse the object -// for a second fetch. -// -// The fetcher will be self-retained as long as a connection is pending. -// -// To keep user activity private, URLs must have an https scheme (unless the property -// allowedInsecureSchemes is set to permit the scheme.) -// -// Callbacks will be released when the fetch completes or is stopped, so there is no need -// to use weak self references in the callback blocks. -// -// Sample usage: -// -// _fetcherService = [[GTMSessionFetcherService alloc] init]; -// -// GTMSessionFetcher *myFetcher = [_fetcherService fetcherWithURLString:myURLString]; -// myFetcher.retryEnabled = YES; -// myFetcher.comment = @"First profile image"; -// -// // Optionally specify a file URL or NSData for the request body to upload. -// myFetcher.bodyData = [postString dataUsingEncoding:NSUTF8StringEncoding]; -// -// [myFetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { -// if (error != nil) { -// // Server status code or network error. -// // -// // If the domain is kGTMSessionFetcherStatusDomain then the error code -// // is a failure status from the server. -// } else { -// // Fetch succeeded. -// } -// }]; -// -// There is also a beginFetch call that takes a pointer and selector for the completion handler; -// a pointer and selector is a better style when the callback is a substantial, separate method. -// -// NOTE: Fetches may retrieve data from the server even though the server -// returned an error, so the criteria for success is a non-nil error. -// The completion handler is called when the server status is >= 300 with an NSError -// having domain kGTMSessionFetcherStatusDomain and code set to the server status. -// -// Status codes are at <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html> -// -// -// Background session support: -// -// Out-of-process uploads and downloads may be created by setting the fetcher's -// useBackgroundSession property. Data to be uploaded should be provided via -// the uploadFileURL property; the download destination should be specified with -// the destinationFileURL. NOTE: Background upload files should be in a location -// that will be valid even after the device is restarted, so the file should not -// be uploaded from a system temporary or cache directory. -// -// Background session transfers are slower, and should typically be used only -// for very large downloads or uploads (hundreds of megabytes). -// -// When background sessions are used in iOS apps, the application delegate must -// pass through the parameters from UIApplicationDelegate's -// application:handleEventsForBackgroundURLSession:completionHandler: to the -// fetcher class. -// -// When the application has been relaunched, it may also create a new fetcher -// instance to handle completion of the transfers. -// -// - (void)application:(UIApplication *)application -// handleEventsForBackgroundURLSession:(NSString *)identifier -// completionHandler:(void (^)())completionHandler { -// // Application was re-launched on completing an out-of-process download. -// -// // Pass the URLSession info related to this re-launch to the fetcher class. -// [GTMSessionFetcher application:application -// handleEventsForBackgroundURLSession:identifier -// completionHandler:completionHandler]; -// -// // Get a fetcher related to this re-launch and re-hook up a completionHandler to it. -// GTMSessionFetcher *fetcher = [GTMSessionFetcher fetcherWithSessionIdentifier:identifier]; -// NSURL *destinationFileURL = fetcher.destinationFileURL; -// fetcher.completionHandler = ^(NSData *data, NSError *error) { -// [self downloadCompletedToFile:destinationFileURL error:error]; -// }; -// } -// -// -// Threading and queue support: -// -// Networking always happens on a background thread; there is no advantage to -// changing thread or queue to create or start a fetcher. -// -// Callbacks are run on the main thread; alternatively, the app may set the -// fetcher's callbackQueue to a dispatch queue. -// -// Once the fetcher's beginFetch method has been called, the fetcher's methods and -// properties may be accessed from any thread. -// -// Downloading to disk: -// -// To have downloaded data saved directly to disk, specify a file URL for the -// destinationFileURL property. -// -// HTTP methods and headers: -// -// Alternative HTTP methods, like PUT, and custom headers can be specified by -// creating the fetcher with an appropriate NSMutableURLRequest. -// -// -// Caching: -// -// The fetcher avoids caching. That is best for API requests, but may hurt -// repeat fetches of static data. Apps may enable a persistent disk cache by -// customizing the config: -// -// fetcher.configurationBlock = ^(GTMSessionFetcher *configFetcher, -// NSURLSessionConfiguration *config) { -// config.URLCache = [NSURLCache sharedURLCache]; -// }; -// -// Or use the standard system config to share cookie storage with web views -// and to enable disk caching: -// -// fetcher.configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; -// -// -// Cookies: -// -// There are three supported mechanisms for remembering cookies between fetches. -// -// By default, a standalone GTMSessionFetcher uses a mutable array held -// statically to track cookies for all instantiated fetchers. This avoids -// cookies being set by servers for the application from interfering with -// Safari and WebKit cookie settings, and vice versa. -// The fetcher cookies are lost when the application quits. -// -// To rely instead on WebKit's global NSHTTPCookieStorage, set the fetcher's -// cookieStorage property: -// myFetcher.cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; -// -// To share cookies with other apps, use the method introduced in iOS 9/OS X 10.11: -// myFetcher.cookieStorage = -// [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:kMyCompanyContainedID]; -// -// To ignore existing cookies and only have cookies related to the single fetch -// be applied, make a temporary cookie storage object: -// myFetcher.cookieStorage = [[GTMSessionCookieStorage alloc] init]; -// -// Note: cookies set while following redirects will be sent to the server, as -// the redirects are followed by the fetcher. -// -// To completely disable cookies, similar to setting cookieStorageMethod to -// kGTMHTTPFetcherCookieStorageMethodNone, adjust the session configuration -// appropriately in the fetcher or fetcher service: -// fetcher.configurationBlock = ^(GTMSessionFetcher *configFetcher, -// NSURLSessionConfiguration *config) { -// config.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever; -// config.HTTPShouldSetCookies = NO; -// }; -// -// If the fetcher is created from a GTMSessionFetcherService object -// then the cookie storage mechanism is set to use the cookie storage in the -// service object rather than the static storage. Disabling cookies in the -// session configuration set on a service object will disable cookies for all -// fetchers created from that GTMSessionFetcherService object, since the session -// configuration is propagated to the fetcher. -// -// -// Monitoring data transfers. -// -// The fetcher supports a variety of properties for progress monitoring -// progress with callback blocks. -// GTMSessionFetcherSendProgressBlock sendProgressBlock -// GTMSessionFetcherReceivedProgressBlock receivedProgressBlock -// GTMSessionFetcherDownloadProgressBlock downloadProgressBlock -// -// 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; 502, gateway failure; -// 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.retryEnabled = YES; -// -// With retries enabled, the completion 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.maxRetryInterval = 60.0; // in seconds; default is 60 seconds -// // for downloads, 600 for uploads -// -// Servers should never send a 400 or 500 status for errors that are retryable -// by clients, as those values indicate permanent failures. In nearly all -// cases, the default standard retry behavior is correct for clients, and no -// custom client retry behavior is needed or appropriate. Servers that send -// non-retryable status codes and expect the client to retry the request are -// faulty. -// -// Still, the client may provide a block to determine if a status code or other -// error should be retried. The block returns 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 kGTMSessionFetcherStatusDomain. The -// user's method may look something like this: -// -// myFetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *error, -// GTMSessionFetcherRetryResponse response) { -// // Perhaps examine error.domain and error.code, or fetcher.retryCount -// // -// // Respond with 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. -// response(suggestedWillRetry); -// }; - - -#import <Foundation/Foundation.h> - -#if TARGET_OS_IPHONE -#import <UIKit/UIKit.h> -#endif -#if TARGET_OS_WATCH -#import <WatchKit/WatchKit.h> -#endif - -// By default it is stripped from non DEBUG builds. Developers can override -// this in their project settings. -#ifndef STRIP_GTM_FETCH_LOGGING - #if !DEBUG - #define STRIP_GTM_FETCH_LOGGING 1 - #else - #define STRIP_GTM_FETCH_LOGGING 0 - #endif -#endif - -// Logs in debug builds. -#ifndef GTMSESSION_LOG_DEBUG - #if DEBUG - #define GTMSESSION_LOG_DEBUG(...) NSLog(__VA_ARGS__) - #else - #define GTMSESSION_LOG_DEBUG(...) do { } while (0) - #endif -#endif - -// Asserts in debug builds (or logs in debug builds if GTMSESSION_ASSERT_AS_LOG -// or NS_BLOCK_ASSERTIONS are defined.) -#ifndef GTMSESSION_ASSERT_DEBUG - #if DEBUG && !defined(NS_BLOCK_ASSERTIONS) && !GTMSESSION_ASSERT_AS_LOG - #undef GTMSESSION_ASSERT_AS_LOG - #define GTMSESSION_ASSERT_AS_LOG 1 - #endif - - #if DEBUG && !GTMSESSION_ASSERT_AS_LOG - #define GTMSESSION_ASSERT_DEBUG(...) NSAssert(__VA_ARGS__) - #elif DEBUG - #define GTMSESSION_ASSERT_DEBUG(pred, ...) if (!(pred)) { NSLog(__VA_ARGS__); } - #else - #define GTMSESSION_ASSERT_DEBUG(pred, ...) do { } while (0) - #endif -#endif - -// Asserts in debug builds, logs in release builds (or logs in debug builds if -// GTMSESSION_ASSERT_AS_LOG is defined.) -#ifndef GTMSESSION_ASSERT_DEBUG_OR_LOG - #if DEBUG && !GTMSESSION_ASSERT_AS_LOG - #define GTMSESSION_ASSERT_DEBUG_OR_LOG(...) NSAssert(__VA_ARGS__) - #else - #define GTMSESSION_ASSERT_DEBUG_OR_LOG(pred, ...) if (!(pred)) { NSLog(__VA_ARGS__); } - #endif -#endif - -// Macro useful for examining messages from NSURLSession during debugging. -#if 0 -#define GTM_LOG_SESSION_DELEGATE(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__) -#else -#define GTM_LOG_SESSION_DELEGATE(...) -#endif - -#ifndef GTM_NULLABLE - #if __has_feature(nullability) // Available starting in Xcode 6.3 - #define GTM_NULLABLE_TYPE __nullable - #define GTM_NONNULL_TYPE __nonnull - #define GTM_NULLABLE nullable - #define GTM_NONNULL_DECL nonnull // GTM_NONNULL is used by GTMDefines.h - #define GTM_NULL_RESETTABLE null_resettable - - #define GTM_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN - #define GTM_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END - #else - #define GTM_NULLABLE_TYPE - #define GTM_NONNULL_TYPE - #define GTM_NULLABLE - #define GTM_NONNULL_DECL - #define GTM_NULL_RESETTABLE - #define GTM_ASSUME_NONNULL_BEGIN - #define GTM_ASSUME_NONNULL_END - #endif // __has_feature(nullability) -#endif // GTM_NULLABLE - -#if (TARGET_OS_TV \ - || TARGET_OS_WATCH \ - || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0)) -#define GTMSESSION_DEPRECATE_ON_2016_SDKS(_MSG) __attribute__((deprecated("" _MSG))) -#else -#define GTMSESSION_DEPRECATE_ON_2016_SDKS(_MSG) -#endif - -#ifndef GTM_DECLARE_GENERICS - #if __has_feature(objc_generics) - #define GTM_DECLARE_GENERICS 1 - #else - #define GTM_DECLARE_GENERICS 0 - #endif -#endif - -#ifndef GTM_NSArrayOf - #if GTM_DECLARE_GENERICS - #define GTM_NSArrayOf(value) NSArray<value> - #define GTM_NSDictionaryOf(key, value) NSDictionary<key, value> - #else - #define GTM_NSArrayOf(value) NSArray - #define GTM_NSDictionaryOf(key, value) NSDictionary - #endif // __has_feature(objc_generics) -#endif // GTM_NSArrayOf - -// For iOS, the fetcher can declare itself a background task to allow fetches -// to finish when the app leaves the foreground. -// -// (This is unrelated to providing a background configuration, which allows -// out-of-process uploads and downloads.) -// -// To disallow use of background tasks during fetches, the target should define -// GTM_BACKGROUND_TASK_FETCHING to 0, or alternatively may set the -// skipBackgroundTask property to YES. -#if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !defined(GTM_BACKGROUND_TASK_FETCHING) - #define GTM_BACKGROUND_TASK_FETCHING 1 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if (TARGET_OS_TV \ - || TARGET_OS_WATCH \ - || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) - #ifndef GTM_USE_SESSION_FETCHER - #define GTM_USE_SESSION_FETCHER 1 - #endif -#endif - -#if !defined(GTMBridgeFetcher) - // These bridge macros should be identical in GTMHTTPFetcher.h and GTMSessionFetcher.h - #if GTM_USE_SESSION_FETCHER - // Macros to new fetcher class. - #define GTMBridgeFetcher GTMSessionFetcher - #define GTMBridgeFetcherService GTMSessionFetcherService - #define GTMBridgeFetcherServiceProtocol GTMSessionFetcherServiceProtocol - #define GTMBridgeAssertValidSelector GTMSessionFetcherAssertValidSelector - #define GTMBridgeCookieStorage GTMSessionCookieStorage - #define GTMBridgeCleanedUserAgentString GTMFetcherCleanedUserAgentString - #define GTMBridgeSystemVersionString GTMFetcherSystemVersionString - #define GTMBridgeApplicationIdentifier GTMFetcherApplicationIdentifier - #define kGTMBridgeFetcherStatusDomain kGTMSessionFetcherStatusDomain - #define kGTMBridgeFetcherStatusBadRequest GTMSessionFetcherStatusBadRequest - #else - // Macros to old fetcher class. - #define GTMBridgeFetcher GTMHTTPFetcher - #define GTMBridgeFetcherService GTMHTTPFetcherService - #define GTMBridgeFetcherServiceProtocol GTMHTTPFetcherServiceProtocol - #define GTMBridgeAssertValidSelector GTMAssertSelectorNilOrImplementedWithArgs - #define GTMBridgeCookieStorage GTMCookieStorage - #define GTMBridgeCleanedUserAgentString GTMCleanedUserAgentString - #define GTMBridgeSystemVersionString GTMSystemVersionString - #define GTMBridgeApplicationIdentifier GTMApplicationIdentifier - #define kGTMBridgeFetcherStatusDomain kGTMHTTPFetcherStatusDomain - #define kGTMBridgeFetcherStatusBadRequest kGTMHTTPFetcherStatusBadRequest - #endif // GTM_USE_SESSION_FETCHER -#endif - -GTM_ASSUME_NONNULL_BEGIN - -// Notifications -// -// Fetch started and stopped, and fetch retry delay started and stopped. -extern NSString *const kGTMSessionFetcherStartedNotification; -extern NSString *const kGTMSessionFetcherStoppedNotification; -extern NSString *const kGTMSessionFetcherRetryDelayStartedNotification; -extern NSString *const kGTMSessionFetcherRetryDelayStoppedNotification; - -// Completion handler notification. This is intended for use by code capturing -// and replaying fetch requests and results for testing. For fetches where -// destinationFileURL or accumulateDataBlock is set for the fetcher, the data -// will be nil for successful fetches. -// -// This notification is posted on the main thread. -extern NSString *const kGTMSessionFetcherCompletionInvokedNotification; -extern NSString *const kGTMSessionFetcherCompletionDataKey; -extern NSString *const kGTMSessionFetcherCompletionErrorKey; - -// Constants for NSErrors created by the fetcher (excluding server status errors, -// and error objects originating in the OS.) -extern NSString *const kGTMSessionFetcherErrorDomain; - -// The fetcher turns server error status values (3XX, 4XX, 5XX) into NSErrors -// with domain kGTMSessionFetcherStatusDomain. -// -// Any server response body data accompanying the status error is added to the -// userInfo dictionary with key kGTMSessionFetcherStatusDataKey. -extern NSString *const kGTMSessionFetcherStatusDomain; -extern NSString *const kGTMSessionFetcherStatusDataKey; -extern NSString *const kGTMSessionFetcherStatusDataContentTypeKey; - -// When a fetch fails with an error, these keys are included in the error userInfo -// dictionary if retries were attempted. -extern NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey; -extern NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey; - -// Background session support requires access to NSUserDefaults. -// If [NSUserDefaults standardUserDefaults] doesn't yield the correct NSUserDefaults for your usage, -// ie for an App Extension, then implement this class/method to return the correct NSUserDefaults. -// https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW6 -@interface GTMSessionFetcherUserDefaultsFactory : NSObject - -+ (NSUserDefaults *)fetcherUserDefaults; - -@end - -#ifdef __cplusplus -} -#endif - -typedef NS_ENUM(NSInteger, GTMSessionFetcherError) { - GTMSessionFetcherErrorDownloadFailed = -1, - GTMSessionFetcherErrorUploadChunkUnavailable = -2, - GTMSessionFetcherErrorBackgroundExpiration = -3, - GTMSessionFetcherErrorBackgroundFetchFailed = -4, - GTMSessionFetcherErrorInsecureRequest = -5, - GTMSessionFetcherErrorTaskCreationFailed = -6, -}; - -typedef NS_ENUM(NSInteger, GTMSessionFetcherStatus) { - // Standard http status codes. - GTMSessionFetcherStatusNotModified = 304, - GTMSessionFetcherStatusBadRequest = 400, - GTMSessionFetcherStatusUnauthorized = 401, - GTMSessionFetcherStatusForbidden = 403, - GTMSessionFetcherStatusPreconditionFailed = 412 -}; - -#ifdef __cplusplus -extern "C" { -#endif - -@class GTMSessionCookieStorage; -@class GTMSessionFetcher; - -// The configuration block is for modifying the NSURLSessionConfiguration only. -// DO NOT change any fetcher properties in the configuration block. -typedef void (^GTMSessionFetcherConfigurationBlock)(GTMSessionFetcher *fetcher, - NSURLSessionConfiguration *configuration); -typedef void (^GTMSessionFetcherSystemCompletionHandler)(void); -typedef void (^GTMSessionFetcherCompletionHandler)(NSData * GTM_NULLABLE_TYPE data, - NSError * GTM_NULLABLE_TYPE error); -typedef void (^GTMSessionFetcherBodyStreamProviderResponse)(NSInputStream *bodyStream); -typedef void (^GTMSessionFetcherBodyStreamProvider)(GTMSessionFetcherBodyStreamProviderResponse response); -typedef void (^GTMSessionFetcherDidReceiveResponseDispositionBlock)(NSURLSessionResponseDisposition disposition); -typedef void (^GTMSessionFetcherDidReceiveResponseBlock)(NSURLResponse *response, - GTMSessionFetcherDidReceiveResponseDispositionBlock dispositionBlock); -typedef void (^GTMSessionFetcherChallengeDispositionBlock)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * GTM_NULLABLE_TYPE credential); -typedef void (^GTMSessionFetcherChallengeBlock)(GTMSessionFetcher *fetcher, - NSURLAuthenticationChallenge *challenge, - GTMSessionFetcherChallengeDispositionBlock dispositionBlock); -typedef void (^GTMSessionFetcherWillRedirectResponse)(NSURLRequest * GTM_NULLABLE_TYPE redirectedRequest); -typedef void (^GTMSessionFetcherWillRedirectBlock)(NSHTTPURLResponse *redirectResponse, - NSURLRequest *redirectRequest, - GTMSessionFetcherWillRedirectResponse response); -typedef void (^GTMSessionFetcherAccumulateDataBlock)(NSData * GTM_NULLABLE_TYPE buffer); -typedef void (^GTMSessionFetcherSimulateByteTransferBlock)(NSData * GTM_NULLABLE_TYPE buffer, - int64_t bytesWritten, - int64_t totalBytesWritten, - int64_t totalBytesExpectedToWrite); -typedef void (^GTMSessionFetcherReceivedProgressBlock)(int64_t bytesWritten, - int64_t totalBytesWritten); -typedef void (^GTMSessionFetcherDownloadProgressBlock)(int64_t bytesWritten, - int64_t totalBytesWritten, - int64_t totalBytesExpectedToWrite); -typedef void (^GTMSessionFetcherSendProgressBlock)(int64_t bytesSent, - int64_t totalBytesSent, - int64_t totalBytesExpectedToSend); -typedef void (^GTMSessionFetcherWillCacheURLResponseResponse)(NSCachedURLResponse * GTM_NULLABLE_TYPE cachedResponse); -typedef void (^GTMSessionFetcherWillCacheURLResponseBlock)(NSCachedURLResponse *proposedResponse, - GTMSessionFetcherWillCacheURLResponseResponse responseBlock); -typedef void (^GTMSessionFetcherRetryResponse)(BOOL shouldRetry); -typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry, - NSError * GTM_NULLABLE_TYPE error, - GTMSessionFetcherRetryResponse response); - -typedef void (^GTMSessionFetcherTestResponse)(NSHTTPURLResponse * GTM_NULLABLE_TYPE response, - NSData * GTM_NULLABLE_TYPE data, - NSError * GTM_NULLABLE_TYPE error); -typedef void (^GTMSessionFetcherTestBlock)(GTMSessionFetcher *fetcherToTest, - GTMSessionFetcherTestResponse testResponse); - -void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE sel, ...); - -// Utility functions for applications self-identifying to servers via a -// user-agent header - -// The "standard" user agent includes the application identifier, taken from the bundle, -// followed by a space and the system version string. Pass nil to use +mainBundle as the source -// of the bundle identifier. -// -// Applications may use this as a starting point for their own user agent strings, perhaps -// with additional sections appended. Use GTMFetcherCleanedUserAgentString() below to -// clean up any string being added to the user agent. -NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle); - -// 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. -// -// The bundle ID may be overridden as the base identifier string by -// adding to the bundle's Info.plist a "GTMUserAgentID" key. -// -// If no bundle ID or override is available, the process name preceded -// by "proc_" is used. -NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle); - -// Make an identifier like "MacOSX/10.7.1" or "iPod_Touch/4.1 hw/iPod1_1" -NSString *GTMFetcherSystemVersionString(void); - -// Make a parseable user-agent identifier from the given string, replacing whitespace -// and commas with underscores, and removing other characters that may interfere -// with parsing of the full user-agent string. -// -// For example, @"[My App]" would become @"My_App" -NSString *GTMFetcherCleanedUserAgentString(NSString *str); - -// Grab the data from an input stream. Since streams cannot be assumed to be rewindable, -// this may be destructive; the caller can try to rewind the stream (by setting the -// NSStreamFileCurrentOffsetKey property) or can just use the NSData to make a new -// NSInputStream. This function is intended to facilitate testing rather than be used in -// production. -// -// This function operates synchronously on the current thread. Depending on how the -// input stream is implemented, it may be appropriate to dispatch to a different -// queue before calling this function. -// -// Failure is indicated by a returned data value of nil. -NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError); - -#ifdef __cplusplus -} // extern "C" -#endif - - -#if !GTM_USE_SESSION_FETCHER -@protocol GTMHTTPFetcherServiceProtocol; -#endif - -// This protocol allows abstract references to the fetcher service, primarily for -// fetchers (which may be compiled without the fetcher service class present.) -// -// Apps should not need to use this protocol. -@protocol GTMSessionFetcherServiceProtocol <NSObject> -// This protocol allows us to call into the service without requiring -// GTMSessionFetcherService sources in this project - -@property(atomic, strong) dispatch_queue_t callbackQueue; - -- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher; -- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher; -- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher; -- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher; - -- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request; -- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher; - -@property(atomic, assign) BOOL reuseSession; -- (GTM_NULLABLE NSURLSession *)session; -- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation; -- (GTM_NULLABLE id<NSURLSessionDelegate>)sessionDelegate; -- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate; - -// Methods for compatibility with the old GTMHTTPFetcher. -@property(readonly, strong, GTM_NULLABLE) NSOperationQueue *delegateQueue; - -@end // @protocol GTMSessionFetcherServiceProtocol - -#ifndef GTM_FETCHER_AUTHORIZATION_PROTOCOL -#define GTM_FETCHER_AUTHORIZATION_PROTOCOL 1 -@protocol GTMFetcherAuthorizationProtocol <NSObject> -@required -// This protocol allows us to call the authorizer without requiring its sources -// in this project. -- (void)authorizeRequest:(GTM_NULLABLE NSMutableURLRequest *)request - delegate:(id)delegate - didFinishSelector:(SEL)sel; - -- (void)stopAuthorization; - -- (void)stopAuthorizationForRequest:(NSURLRequest *)request; - -- (BOOL)isAuthorizingRequest:(NSURLRequest *)request; - -- (BOOL)isAuthorizedRequest:(NSURLRequest *)request; - -@property(strong, readonly, GTM_NULLABLE) NSString *userEmail; - -@optional - -// Indicate if authorization may be attempted. Even if this succeeds, -// authorization may fail if the user's permissions have been revoked. -@property(readonly) BOOL canAuthorize; - -// For development only, allow authorization of non-SSL requests, allowing -// transmission of the bearer token unencrypted. -@property(assign) BOOL shouldAuthorizeAllRequests; - -- (void)authorizeRequest:(GTM_NULLABLE NSMutableURLRequest *)request - completionHandler:(void (^)(NSError * GTM_NULLABLE_TYPE error))handler; - -#if GTM_USE_SESSION_FETCHER -@property (weak, GTM_NULLABLE) id<GTMSessionFetcherServiceProtocol> fetcherService; -#else -@property (weak, GTM_NULLABLE) id<GTMHTTPFetcherServiceProtocol> fetcherService; -#endif - -- (BOOL)primeForRefresh; - -@end -#endif // GTM_FETCHER_AUTHORIZATION_PROTOCOL - -#if GTM_BACKGROUND_TASK_FETCHING -// A protocol for an alternative target for messages from GTMSessionFetcher to UIApplication. -// Set the target using +[GTMSessionFetcher setSubstituteUIApplication:] -@protocol GTMUIApplicationProtocol <NSObject> -- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:(nullable NSString *)taskName - expirationHandler:(void(^ __nullable)(void))handler; -- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier; -@end -#endif - -#pragma mark - - -// GTMSessionFetcher objects are used for async retrieval of an http get or post -// -// See additional comments at the beginning of this file -@interface GTMSessionFetcher : NSObject <NSURLSessionDelegate> - -// 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. -+ (instancetype)fetcherWithRequest:(GTM_NULLABLE NSURLRequest *)request; - -// Convenience methods that make a request, like +fetcherWithRequest -+ (instancetype)fetcherWithURL:(NSURL *)requestURL; -+ (instancetype)fetcherWithURLString:(NSString *)requestURLString; - -// Methods for creating fetchers to continue previous fetches. -+ (instancetype)fetcherWithDownloadResumeData:(NSData *)resumeData; -+ (GTM_NULLABLE instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier; - -// Returns an array of currently active fetchers for background sessions, -// both restarted and newly created ones. -+ (GTM_NSArrayOf(GTMSessionFetcher *) *)fetchersForBackgroundSessions; - -// Designated initializer. -// -// Applications should create fetchers with a "fetcherWith..." method on a fetcher -// service or a class method, not with this initializer. -// -// The configuration should typically be nil. Applications needing to customize -// the configuration may do so by setting the configurationBlock property. -- (instancetype)initWithRequest:(GTM_NULLABLE NSURLRequest *)request - configuration:(GTM_NULLABLE NSURLSessionConfiguration *)configuration; - -// The fetcher's request. This may not be set after beginFetch has been invoked. The request -// may change due to redirects. -@property(strong, GTM_NULLABLE) NSURLRequest *request; - -// Set a header field value on the request. Header field value changes will not -// affect a fetch after the fetch has begun. -- (void)setRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field; - -// Data used for resuming a download task. -@property(atomic, readonly, GTM_NULLABLE) NSData *downloadResumeData; - -// The configuration; this must be set before the fetch begins. If no configuration is -// set or inherited from the fetcher service, then the fetcher uses an ephemeral config. -// -// NOTE: This property should typically be nil. Applications needing to customize -// the configuration should do so by setting the configurationBlock property. -// That allows the fetcher to pick an appropriate base configuration, with the -// application setting only the configuration properties it needs to customize. -@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration; - -// A block the client may use to customize the configuration used to create the session. -// -// This is called synchronously, either on the thread that begins the fetch or, during a retry, -// on the main thread. The configuration block may be called repeatedly if multiple fetchers are -// created. -// -// The configuration block is for modifying the NSURLSessionConfiguration only. -// DO NOT change any fetcher properties in the configuration block. Fetcher properties -// may be set in the fetcher service prior to fetcher creation, or on the fetcher prior -// to invoking beginFetch. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock; - -// A session is created as needed by the fetcher. A fetcher service object -// may maintain sessions for multiple fetches to the same host. -@property(atomic, strong, GTM_NULLABLE) NSURLSession *session; - -// The task in flight. -@property(atomic, readonly, GTM_NULLABLE) NSURLSessionTask *sessionTask; - -// The background session identifier. -@property(atomic, readonly, GTM_NULLABLE) NSString *sessionIdentifier; - -// Indicates a fetcher created to finish a background session task. -@property(atomic, readonly) BOOL wasCreatedFromBackgroundSession; - -// Additional user-supplied data to encode into the session identifier. Since session identifier -// length limits are unspecified, this should be kept small. Key names beginning with an underscore -// are reserved for use by the fetcher. -@property(atomic, strong, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSString *) *sessionUserInfo; - -// The human-readable description to be assigned to the task. -@property(atomic, copy, GTM_NULLABLE) NSString *taskDescription; - -// The priority assigned to the task, if any. Use NSURLSessionTaskPriorityLow, -// NSURLSessionTaskPriorityDefault, or NSURLSessionTaskPriorityHigh. -@property(atomic, assign) float taskPriority; - -// The fetcher encodes information used to resume a session in the session identifier. -// This method, intended for internal use returns the encoded information. The sessionUserInfo -// dictionary is stored as identifier metadata. -- (GTM_NULLABLE GTM_NSDictionaryOf(NSString *, NSString *) *)sessionIdentifierMetadata; - -#if TARGET_OS_IPHONE && !TARGET_OS_WATCH -// The app should pass to this method the completion handler passed in the app delegate method -// application:handleEventsForBackgroundURLSession:completionHandler: -+ (void)application:(UIApplication *)application - handleEventsForBackgroundURLSession:(NSString *)identifier - completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler; -#endif - -// Indicate that a newly created session should be a background session. -// A new session identifier will be created by the fetcher. -// -// Warning: The only thing background sessions are for is rare download -// of huge, batched files of data. And even just for those, there's a lot -// of pain and hackery needed to get transfers to actually happen reliably -// with background sessions. -// -// Don't try to upload or download in many background sessions, since the system -// will impose an exponentially increasing time penalty to prevent the app from -// getting too much background execution time. -// -// References: -// -// "Moving to Fewer, Larger Transfers" -// https://forums.developer.apple.com/thread/14853 -// -// "NSURLSession’s Resume Rate Limiter" -// https://forums.developer.apple.com/thread/14854 -// -// "Background Session Task state persistence" -// https://forums.developer.apple.com/thread/11554 -// -@property(assign) BOOL useBackgroundSession; - -// Indicates if the fetcher was started using a background session. -@property(atomic, readonly, getter=isUsingBackgroundSession) BOOL usingBackgroundSession; - -// Indicates if uploads should use an upload task. This is always set for file or stream-provider -// bodies, but may be set explicitly for NSData bodies. -@property(atomic, assign) BOOL useUploadTask; - -// Indicates that the fetcher is using a session that may be shared with other fetchers. -@property(atomic, readonly) BOOL canShareSession; - -// By default, the fetcher allows only secure (https) schemes unless this -// property is set, or the GTM_ALLOW_INSECURE_REQUESTS build flag is set. -// -// For example, during debugging when fetching from a development server that lacks SSL support, -// this may be set to @[ @"http" ], or when the fetcher is used to retrieve local files, -// this may be set to @[ @"file" ]. -// -// This should be left as nil for release builds to avoid creating the opportunity for -// leaking private user behavior and data. If a server is providing insecure URLs -// for fetching by the client app, report the problem as server security & privacy bug. -// -// For builds with the iOS 9/OS X 10.11 and later SDKs, this property is required only when -// the app specifies NSAppTransportSecurity/NSAllowsArbitraryLoads in the main bundle's Info.plist. -@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes; - -// By default, the fetcher prohibits localhost requests unless this property is set, -// or the GTM_ALLOW_INSECURE_REQUESTS build flag is set. -// -// For localhost requests, the URL scheme is not checked when this property is set. -// -// For builds with the iOS 9/OS X 10.11 and later SDKs, this property is required only when -// the app specifies NSAppTransportSecurity/NSAllowsArbitraryLoads in the main bundle's Info.plist. -@property(atomic, assign) BOOL allowLocalhostRequest; - -// By default, the fetcher requires valid server certs. This may be bypassed -// temporarily for development against a test server with an invalid cert. -@property(atomic, assign) BOOL allowInvalidServerCertificates; - -// Cookie storage object for this fetcher. If nil, the fetcher will use a static cookie -// storage instance shared among fetchers. If this fetcher was created by a fetcher service -// object, it will be set to use the service object's cookie storage. See Cookies section above for -// the full discussion. -// -// Because as of Jan 2014 standalone instances of NSHTTPCookieStorage do not actually -// store any cookies (Radar 15735276) we use our own subclass, GTMSessionCookieStorage, -// to hold cookies in memory. -@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage; - -// Setting the credential is optional; it is used if the connection receives -// an authentication challenge. -@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential; - -// Setting the proxy credential is optional; it is used if the connection -// receives an authentication challenge from a proxy. -@property(atomic, strong, GTM_NULLABLE) NSURLCredential *proxyCredential; - -// If body data, body file URL, or body stream provider is not set, then a GET request -// method is assumed. -@property(atomic, strong, GTM_NULLABLE) NSData *bodyData; - -// File to use as the request body. This forces use of an upload task. -@property(atomic, strong, GTM_NULLABLE) NSURL *bodyFileURL; - -// Length of body to send, expected or actual. -@property(atomic, readonly) int64_t bodyLength; - -// The body stream provider may be called repeatedly to provide a body. -// Setting a body stream provider forces use of an upload task. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherBodyStreamProvider bodyStreamProvider; - -// Object to add authorization to the request, if needed. -// -// This may not be changed once beginFetch has been invoked. -@property(atomic, strong, GTM_NULLABLE) id<GTMFetcherAuthorizationProtocol> authorizer; - -// The service object that created and monitors this fetcher, if any. -@property(atomic, strong) id<GTMSessionFetcherServiceProtocol> service; - -// The host, if any, used to classify this fetcher in the fetcher service. -@property(atomic, copy, GTM_NULLABLE) 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 when the running fetchers -// exceeds the service's maxRunningFetchersPerHost. A priority of NSIntegerMin will -// exempt this fetcher from delay. -@property(atomic, assign) NSInteger servicePriority; - -// The delegate's optional didReceiveResponse block may be used to inspect or alter -// the session task response. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock; - -// The delegate's optional challenge block may be used to inspect or alter -// the session task challenge. -// -// If this block is not set, the fetcher's default behavior for the NSURLSessionTask -// didReceiveChallenge: delegate method is to use the fetcher's respondToChallenge: method -// which relies on the fetcher's credential and proxyCredential properties. -// -// Warning: This may be called repeatedly if the challenge fails. Check -// challenge.previousFailureCount to identify repeated invocations. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock; - -// The delegate's optional willRedirect block may be used to inspect or alter -// the redirection. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherWillRedirectBlock willRedirectBlock; - -// The optional send progress block reports body bytes uploaded. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherSendProgressBlock sendProgressBlock; - -// The optional accumulate block may be set by clients wishing to accumulate data -// themselves rather than let the fetcher append each buffer to an NSData. -// -// When this is called with nil data (such as on redirect) the client -// should empty its accumulation buffer. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherAccumulateDataBlock accumulateDataBlock; - -// The optional received progress block may be used to monitor data -// received from a data task. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherReceivedProgressBlock receivedProgressBlock; - -// The delegate's optional downloadProgress block may be used to monitor download -// progress in writing to disk. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherDownloadProgressBlock downloadProgressBlock; - -// The delegate's optional willCacheURLResponse block may be used to alter the cached -// NSURLResponse. The user may prevent caching by passing nil to the block's response. -// -// This is called on the callback queue. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock; - -// Enable retrying; see comments at the top of this file. Setting -// retryEnabled=YES resets the min and max retry intervals. -@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled; - -// Retry block is optional for retries. -// -// If present, this block should call the response block with YES to cause a retry or NO to end the -// fetch. -// See comments at the top of this file. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock; - -// 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, 60 seconds for downloads and 600 seconds for uploads. -@property(atomic, 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 -// set this except for unit testing. -@property(atomic, assign) NSTimeInterval minRetryInterval; - -// Multiplier used to increase the interval between retries, typically 2.0. -// Clients should not need to set this. -@property(atomic, assign) double retryFactor; - -// Number of retries attempted. -@property(atomic, readonly) NSUInteger retryCount; - -// Interval delay to precede next retry. -@property(atomic, readonly) NSTimeInterval nextRetryInterval; - -#if GTM_BACKGROUND_TASK_FETCHING -// Skip use of a UIBackgroundTask, thus requiring fetches to complete when the app is in the -// foreground. -// -// Targets should define GTM_BACKGROUND_TASK_FETCHING to 0 to avoid use of a UIBackgroundTask -// on iOS to allow fetches to complete in the background. This property is available when -// it's not practical to set the preprocessor define. -@property(atomic, assign) BOOL skipBackgroundTask; -#endif // GTM_BACKGROUND_TASK_FETCHING - -// Begin fetching the request -// -// The delegate may optionally implement the callback or pass nil for the selector or handler. -// -// The delegate and all callback blocks are retained between the beginFetch call until after the -// finish callback, or until the fetch is stopped. -// -// 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:(GTMSessionFetcher *)fetcher -// finishedWithData:(NSData *)data -// error:(NSError *)error; -// -// If the application has specified a destinationFileURL or an accumulateDataBlock -// for the fetcher, the data parameter passed to the callback will be nil. - -- (void)beginFetchWithDelegate:(GTM_NULLABLE id)delegate - didFinishSelector:(GTM_NULLABLE SEL)finishedSEL; - -- (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler; - -// Returns YES if this fetcher is in the process of fetching a URL. -@property(atomic, readonly, getter=isFetching) BOOL fetching; - -// Cancel the fetch of the request that's currently in progress. The completion handler -// will not be called. -- (void)stopFetching; - -// A block to be called when the fetch completes. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherCompletionHandler completionHandler; - -// A block to be called if download resume data becomes available. -@property(atomic, strong, GTM_NULLABLE) void (^resumeDataBlock)(NSData *); - -// Return the status code from the server response. -@property(atomic, readonly) NSInteger statusCode; - -// Return the http headers from the response. -@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSString *) *responseHeaders; - -// The response, once it's been received. -@property(atomic, strong, readonly, GTM_NULLABLE) NSURLResponse *response; - -// Bytes downloaded so far. -@property(atomic, readonly) int64_t downloadedLength; - -// Buffer of currently-downloaded data, if available. -@property(atomic, readonly, strong, GTM_NULLABLE) NSData *downloadedData; - -// Local path to which the downloaded file will be moved. -// -// If a file already exists at the path, it will be overwritten. -// Will create the enclosing folders if they are not present. -@property(atomic, strong, GTM_NULLABLE) NSURL *destinationFileURL; - -// The time this fetcher originally began fetching. This is useful as a time -// barrier for ignoring irrelevant fetch notifications or callbacks. -@property(atomic, strong, readonly, GTM_NULLABLE) NSDate *initialBeginFetchDate; - -// userData is retained solely for the convenience of the client. -@property(atomic, strong, GTM_NULLABLE) id userData; - -// Stored property values are retained solely for the convenience of the client. -@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties; - -- (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key; // Pass nil for obj to remove the property. -- (GTM_NULLABLE id)propertyForKey:(NSString *)key; - -- (void)addPropertiesFromDictionary:(GTM_NSDictionaryOf(NSString *, id) *)dict; - -// Comments are useful for logging, so are strongly recommended for each fetcher. -@property(atomic, copy, GTM_NULLABLE) NSString *comment; - -- (void)setCommentWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); - -// Log of request and response, if logging is enabled -@property(atomic, copy, GTM_NULLABLE) NSString *log; - -// Callbacks are run on this queue. If none is supplied, the main queue is used. -@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue; - -// The queue used internally by the session to invoke its delegate methods in the fetcher. -// -// Application callbacks are always called by the fetcher on the callbackQueue above, -// not on this queue. Apps should generally not change this queue. -// -// The default delegate queue is the main queue. -// -// This value is ignored after the session has been created, so this -// property should be set in the fetcher service rather in the fetcher as it applies -// to a shared session. -@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue; - -// Spin the run loop or sleep the thread, discarding events, until the fetch has completed. -// -// This is only for use in testing or in tools without a user interface. -// -// Note: Synchronous fetches should never be used by shipping apps; they are -// sufficient reason for rejection from the app store. -// -// Returns NO if timed out. -- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds; - -// Test block is optional for testing. -// -// If present, this block will cause the fetcher to skip starting the session, and instead -// use the test block response values when calling the completion handler and delegate code. -// -// Test code can set this on the fetcher or on the fetcher service. For testing libraries -// that use a fetcher without exposing either the fetcher or the fetcher service, the global -// method setGlobalTestBlock: will set the block for all fetchers that do not have a test -// block set. -// -// The test code can pass nil for all response parameters to indicate that the fetch -// should proceed. -// -// Applications can exclude test block support by setting GTM_DISABLE_FETCHER_TEST_BLOCK. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock; - -+ (void)setGlobalTestBlock:(GTM_NULLABLE GTMSessionFetcherTestBlock)block; - -// When using the testBlock, |testBlockAccumulateDataChunkCount| is the desired number of chunks to -// divide the response data into if the client has streaming enabled. The data will be divided up to -// |testBlockAccumulateDataChunkCount| chunks; however, the exact amount may vary depending on the -// size of the response data (e.g. a 1-byte response can only be divided into one chunk). -@property(atomic, readwrite) NSUInteger testBlockAccumulateDataChunkCount; - -#if GTM_BACKGROUND_TASK_FETCHING -// For testing or to override UIApplication invocations, apps may specify an alternative -// target for messages to UIApplication. -+ (void)setSubstituteUIApplication:(nullable id<GTMUIApplicationProtocol>)substituteUIApplication; -+ (nullable id<GTMUIApplicationProtocol>)substituteUIApplication; -#endif // GTM_BACKGROUND_TASK_FETCHING - -// Exposed for testing. -+ (GTMSessionCookieStorage *)staticCookieStorage; -+ (BOOL)appAllowsInsecureRequests; - -#if STRIP_GTM_FETCH_LOGGING -// If logging is stripped, provide a stub for the main method -// for controlling logging. -+ (void)setLoggingEnabled:(BOOL)flag; -+ (BOOL)isLoggingEnabled; - -#else - -// These methods let an application log specific body text, such as the text description of a binary -// request or response. The application should set the fetcher to defer response body logging until -// the response has been received and the log response body has been set by the app. For example: -// -// fetcher.logRequestBody = [binaryObject stringDescription]; -// fetcher.deferResponseBodyLogging = YES; -// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { -// if (error == nil) { -// fetcher.logResponseBody = [[[MyThing alloc] initWithData:data] stringDescription]; -// } -// fetcher.deferResponseBodyLogging = NO; -// }]; - -@property(atomic, copy, GTM_NULLABLE) NSString *logRequestBody; -@property(atomic, assign) BOOL deferResponseBodyLogging; -@property(atomic, copy, GTM_NULLABLE) NSString *logResponseBody; - -// Internal logging support. -@property(atomic, readonly) NSData *loggedStreamData; -@property(atomic, assign) BOOL hasLoggedError; -@property(atomic, strong, GTM_NULLABLE) NSURL *redirectedFromURL; -- (void)appendLoggedStreamData:(NSData *)dataToAdd; -- (void)clearLoggedStreamData; - -#endif // STRIP_GTM_FETCH_LOGGING - -@end - -@interface GTMSessionFetcher (BackwardsCompatibilityOnly) -// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. -// This method is just for compatibility with the old GTMHTTPFetcher class. -- (void)setCookieStorageMethod:(NSInteger)method; -@end - -// Until we can just instantiate NSHTTPCookieStorage for local use, we'll -// implement all the public methods ourselves. This stores cookies only in -// memory. Additional methods are provided for testing. -// -// iOS 9/OS X 10.11 added +[NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:] -// which may also be used to create cookie storage. -@interface GTMSessionCookieStorage : NSHTTPCookieStorage - -// Add the array off cookies to the storage, replacing duplicates. -// Also removes expired cookies from the storage. -- (void)setCookies:(GTM_NULLABLE GTM_NSArrayOf(NSHTTPCookie *) *)cookies; - -- (void)removeAllCookies; - -@end - -// Macros to monitor synchronization blocks in debug builds. -// These report problems using GTMSessionCheckDebug. -// -// GTMSessionMonitorSynchronized Start monitoring a top-level-only -// @sync scope. -// GTMSessionMonitorRecursiveSynchronized Start monitoring a top-level or -// recursive @sync scope. -// GTMSessionCheckSynchronized Verify that the current execution -// is inside a @sync scope. -// GTMSessionCheckNotSynchronized Verify that the current execution -// is not inside a @sync scope. -// -// Example usage: -// -// - (void)myExternalMethod { -// @synchronized(self) { -// GTMSessionMonitorSynchronized(self) -// -// - (void)myInternalMethod { -// GTMSessionCheckSynchronized(self); -// -// - (void)callMyCallbacks { -// GTMSessionCheckNotSynchronized(self); -// -// GTMSessionCheckNotSynchronized is available for verifying the code isn't -// in a deadlockable @sync state when posting notifications and invoking -// callbacks. Don't use GTMSessionCheckNotSynchronized immediately before a -// @sync scope; the normal recursiveness check of GTMSessionMonitorSynchronized -// can catch those. - -#ifdef __OBJC__ -// If asserts are entirely no-ops, the synchronization monitor is just a bunch -// of counting code that doesn't report exceptional circumstances in any way. -// Only build the synchronization monitor code if NS_BLOCK_ASSERTIONS is not -// defined or asserts are being logged instead. -#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) - #define __GTMSessionMonitorSynchronizedVariableInner(varname, counter) \ - varname ## counter - #define __GTMSessionMonitorSynchronizedVariable(varname, counter) \ - __GTMSessionMonitorSynchronizedVariableInner(varname, counter) - - #define GTMSessionMonitorSynchronized(obj) \ - NS_VALID_UNTIL_END_OF_SCOPE id \ - __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ - [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ - allowRecursive:NO \ - functionName:__func__] - - #define GTMSessionMonitorRecursiveSynchronized(obj) \ - NS_VALID_UNTIL_END_OF_SCOPE id \ - __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ - [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ - allowRecursive:YES \ - functionName:__func__] - - #define GTMSessionCheckSynchronized(obj) { \ - GTMSESSION_ASSERT_DEBUG( \ - [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ - @"GTMSessionCheckSynchronized(" #obj ") failed: not sync'd" \ - @" on " #obj " in %s. Call stack:\n%@", \ - __func__, [NSThread callStackSymbols]); \ - } - - #define GTMSessionCheckNotSynchronized(obj) { \ - GTMSESSION_ASSERT_DEBUG( \ - ![GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ - @"GTMSessionCheckNotSynchronized(" #obj ") failed: was sync'd" \ - @" on " #obj " in %s by %@. Call stack:\n%@", __func__, \ - [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ - [NSThread callStackSymbols]); \ - } - -// GTMSessionSyncMonitorInternal is a private class that keeps track of the -// beginning and end of synchronized scopes. -// -// This class should not be used directly, but only via the -// GTMSessionMonitorSynchronized macro. -@interface GTMSessionSyncMonitorInternal : NSObject -- (instancetype)initWithSynchronizationObject:(id)object - allowRecursive:(BOOL)allowRecursive - functionName:(const char *)functionName; -// Return the names of the functions that hold sync on the object, or nil if none. -+ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object; -@end - -#else - #define GTMSessionMonitorSynchronized(obj) do { } while (0) - #define GTMSessionMonitorRecursiveSynchronized(obj) do { } while (0) - #define GTMSessionCheckSynchronized(obj) do { } while (0) - #define GTMSessionCheckNotSynchronized(obj) do { } while (0) -#endif // !DEBUG -#endif // __OBJC__ - - -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m @@ -1,4579 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#import "GTMSessionFetcher.h" - -#import <sys/utsname.h> - -#ifndef STRIP_GTM_FETCH_LOGGING - #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. -#endif - -GTM_ASSUME_NONNULL_BEGIN - -NSString *const kGTMSessionFetcherStartedNotification = @"kGTMSessionFetcherStartedNotification"; -NSString *const kGTMSessionFetcherStoppedNotification = @"kGTMSessionFetcherStoppedNotification"; -NSString *const kGTMSessionFetcherRetryDelayStartedNotification = @"kGTMSessionFetcherRetryDelayStartedNotification"; -NSString *const kGTMSessionFetcherRetryDelayStoppedNotification = @"kGTMSessionFetcherRetryDelayStoppedNotification"; - -NSString *const kGTMSessionFetcherCompletionInvokedNotification = @"kGTMSessionFetcherCompletionInvokedNotification"; -NSString *const kGTMSessionFetcherCompletionDataKey = @"data"; -NSString *const kGTMSessionFetcherCompletionErrorKey = @"error"; - -NSString *const kGTMSessionFetcherErrorDomain = @"com.google.GTMSessionFetcher"; -NSString *const kGTMSessionFetcherStatusDomain = @"com.google.HTTPStatus"; -NSString *const kGTMSessionFetcherStatusDataKey = @"data"; // data returned with a kGTMSessionFetcherStatusDomain error -NSString *const kGTMSessionFetcherStatusDataContentTypeKey = @"data_content_type"; - -NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey = @"kGTMSessionFetcherNumberOfRetriesDoneKey"; -NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey = @"kGTMSessionFetcherElapsedIntervalWithRetriesKey"; - -static NSString *const kGTMSessionIdentifierPrefix = @"com.google.GTMSessionFetcher"; -static NSString *const kGTMSessionIdentifierDestinationFileURLMetadataKey = @"_destURL"; -static NSString *const kGTMSessionIdentifierBodyFileURLMetadataKey = @"_bodyURL"; - -// The default max retry interview is 10 minutes for uploads (POST/PUT/PATCH), -// 1 minute for downloads. -static const NSTimeInterval kUnsetMaxRetryInterval = -1.0; -static const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0; -static const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.; - -// The maximum data length that can be loaded to the error userInfo -static const int64_t kMaximumDownloadErrorDataLength = 20000; - -#ifdef GTMSESSION_PERSISTED_DESTINATION_KEY -// Projects using unique class names should also define a unique persisted destination key. -static NSString * const kGTMSessionFetcherPersistedDestinationKey = - GTMSESSION_PERSISTED_DESTINATION_KEY; -#else -static NSString * const kGTMSessionFetcherPersistedDestinationKey = - @"com.google.GTMSessionFetcher.downloads"; -#endif - -GTM_ASSUME_NONNULL_END - -// -// GTMSessionFetcher -// - -#if 0 -#define GTM_LOG_BACKGROUND_SESSION(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__) -#else -#define GTM_LOG_BACKGROUND_SESSION(...) -#endif - -#ifndef GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY - #if (TARGET_OS_TV \ - || TARGET_OS_WATCH \ - || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) - #define GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY 1 - #endif -#endif - -@interface GTMSessionFetcher () - -@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadedData; -@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadResumeData; - -#if GTM_BACKGROUND_TASK_FETCHING -// Should always be accessed within an @synchronized(self). -@property(assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier; -#endif - -@property(atomic, readwrite, getter=isUsingBackgroundSession) BOOL usingBackgroundSession; - -@end - -#if !GTMSESSION_BUILD_COMBINED_SOURCES -@interface GTMSessionFetcher (GTMSessionFetcherLoggingInternal) -- (void)logFetchWithError:(NSError *)error; -- (void)logNowWithError:(GTM_NULLABLE NSError *)error; -- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; -- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: - (GTMSessionFetcherBodyStreamProvider)streamProvider; -@end -#endif // !GTMSESSION_BUILD_COMBINED_SOURCES - -GTM_ASSUME_NONNULL_BEGIN - -static NSTimeInterval InitialMinRetryInterval(void) { - return 1.0 + ((double)(arc4random_uniform(0x0FFFF)) / (double) 0x0FFFF); -} - -static BOOL IsLocalhost(NSString * GTM_NULLABLE_TYPE host) { - // We check if there's host, and then make the comparisons. - if (host == nil) return NO; - return ([host caseInsensitiveCompare:@"localhost"] == NSOrderedSame - || [host isEqual:@"::1"] - || [host isEqual:@"127.0.0.1"]); -} - -static NSDictionary *GTM_NULLABLE_TYPE GTMErrorUserInfoForData( - NSData *GTM_NULLABLE_TYPE data, NSDictionary *GTM_NULLABLE_TYPE responseHeaders) { - NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - - if (data.length > 0) { - userInfo[kGTMSessionFetcherStatusDataKey] = data; - - NSString *contentType = responseHeaders[@"Content-Type"]; - if (contentType) { - userInfo[kGTMSessionFetcherStatusDataContentTypeKey] = contentType; - } - } - - return userInfo.count > 0 ? userInfo : nil; -} - -static GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE gGlobalTestBlock; - -@implementation GTMSessionFetcher { - NSMutableURLRequest *_request; // after beginFetch, changed only in delegate callbacks - BOOL _useUploadTask; // immutable after beginFetch - NSURL *_bodyFileURL; // immutable after beginFetch - GTMSessionFetcherBodyStreamProvider _bodyStreamProvider; // immutable after beginFetch - NSURLSession *_session; - BOOL _shouldInvalidateSession; // immutable after beginFetch - NSURLSession *_sessionNeedingInvalidation; - NSURLSessionConfiguration *_configuration; - NSURLSessionTask *_sessionTask; - NSString *_taskDescription; - float _taskPriority; - NSURLResponse *_response; - NSString *_sessionIdentifier; - BOOL _wasCreatedFromBackgroundSession; - BOOL _didCreateSessionIdentifier; - NSString *_sessionIdentifierUUID; - BOOL _userRequestedBackgroundSession; - BOOL _usingBackgroundSession; - NSMutableData * GTM_NULLABLE_TYPE _downloadedData; - NSError *_downloadFinishedError; - NSData *_downloadResumeData; // immutable after construction - NSData * GTM_NULLABLE_TYPE _downloadTaskErrorData; // Data for when download task fails - NSURL *_destinationFileURL; - int64_t _downloadedLength; - NSURLCredential *_credential; // username & password - NSURLCredential *_proxyCredential; // credential supplied to proxy servers - BOOL _isStopNotificationNeeded; // set when start notification has been sent - BOOL _isUsingTestBlock; // set when a test block was provided (remains set when the block is released) - id _userData; // retained, if set by caller - NSMutableDictionary *_properties; // more data retained for caller - dispatch_queue_t _callbackQueue; - dispatch_group_t _callbackGroup; // read-only after creation - NSOperationQueue *_delegateQueue; // immutable after beginFetch - - id<GTMFetcherAuthorizationProtocol> _authorizer; // immutable after beginFetch - - // The service object that created and monitors this fetcher, if any. - id<GTMSessionFetcherServiceProtocol> _service; // immutable; set by the fetcher service upon creation - NSString *_serviceHost; - NSInteger _servicePriority; // immutable after beginFetch - BOOL _hasStoppedFetching; // counterpart to _initialBeginFetchDate - BOOL _userStoppedFetching; - - BOOL _isRetryEnabled; // user wants auto-retry - NSTimer *_retryTimer; - NSUInteger _retryCount; - NSTimeInterval _maxRetryInterval; // default 60 (download) or 600 (upload) seconds - NSTimeInterval _minRetryInterval; // random between 1 and 2 seconds - NSTimeInterval _retryFactor; // default interval multiplier is 2 - NSTimeInterval _lastRetryInterval; - NSDate *_initialBeginFetchDate; // date that beginFetch was first invoked; immutable after initial beginFetch - NSDate *_initialRequestDate; // date of first request to the target server (ignoring auth) - BOOL _hasAttemptedAuthRefresh; // accessed only in shouldRetryNowForStatus: - - NSString *_comment; // comment for log - NSString *_log; -#if !STRIP_GTM_FETCH_LOGGING - NSMutableData *_loggedStreamData; - NSURL *_redirectedFromURL; - NSString *_logRequestBody; - NSString *_logResponseBody; - BOOL _hasLoggedError; - BOOL _deferResponseBodyLogging; -#endif -} - -#if !GTMSESSION_UNIT_TESTING -+ (void)load { - [self fetchersForBackgroundSessions]; -} -#endif - -+ (instancetype)fetcherWithRequest:(GTM_NULLABLE NSURLRequest *)request { - return [[self alloc] initWithRequest:request configuration:nil]; -} - -+ (instancetype)fetcherWithURL:(NSURL *)requestURL { - return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; -} - -+ (instancetype)fetcherWithURLString:(NSString *)requestURLString { - return [self fetcherWithURL:(NSURL *)[NSURL URLWithString:requestURLString]]; -} - -+ (instancetype)fetcherWithDownloadResumeData:(NSData *)resumeData { - GTMSessionFetcher *fetcher = [self fetcherWithRequest:nil]; - fetcher.comment = @"Resuming download"; - fetcher.downloadResumeData = resumeData; - return fetcher; -} - -+ (GTM_NULLABLE instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier { - GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); - NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; - GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; - if (!fetcher && [sessionIdentifier hasPrefix:kGTMSessionIdentifierPrefix]) { - fetcher = [self fetcherWithRequest:nil]; - [fetcher setSessionIdentifier:sessionIdentifier]; - [sessionIdentifierToFetcherMap setObject:fetcher forKey:sessionIdentifier]; - fetcher->_wasCreatedFromBackgroundSession = YES; - [fetcher setCommentWithFormat:@"Resuming %@", - fetcher && fetcher->_sessionIdentifierUUID ? fetcher->_sessionIdentifierUUID : @"?"]; - } - return fetcher; -} - -+ (NSMapTable *)sessionIdentifierToFetcherMap { - // TODO: What if a service is involved in creating the fetcher? Currently, when re-creating - // fetchers, if a service was involved, it is not re-created. Should the service maintain a map? - static NSMapTable *gSessionIdentifierToFetcherMap = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - gSessionIdentifierToFetcherMap = [NSMapTable strongToWeakObjectsMapTable]; - }); - return gSessionIdentifierToFetcherMap; -} - -#if !GTM_ALLOW_INSECURE_REQUESTS -+ (BOOL)appAllowsInsecureRequests { - // If the main bundle Info.plist key NSAppTransportSecurity is present, and it specifies - // NSAllowsArbitraryLoads, then we need to explicitly enforce secure schemes. -#if GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY - static BOOL allowsInsecureRequests; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSBundle *mainBundle = [NSBundle mainBundle]; - NSDictionary *appTransportSecurity = - [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"]; - allowsInsecureRequests = - [[appTransportSecurity objectForKey:@"NSAllowsArbitraryLoads"] boolValue]; - }); - return allowsInsecureRequests; -#else - // For builds targeting iOS 8 or 10.10 and earlier, we want to require fetcher - // security checks. - return YES; -#endif // GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY -} -#else // GTM_ALLOW_INSECURE_REQUESTS -+ (BOOL)appAllowsInsecureRequests { - return YES; -} -#endif // !GTM_ALLOW_INSECURE_REQUESTS - - -- (instancetype)init { - return [self initWithRequest:nil configuration:nil]; -} - -- (instancetype)initWithRequest:(NSURLRequest *)request { - return [self initWithRequest:request configuration:nil]; -} - -- (instancetype)initWithRequest:(GTM_NULLABLE NSURLRequest *)request - configuration:(GTM_NULLABLE NSURLSessionConfiguration *)configuration { - self = [super init]; - if (self) { -#if GTM_BACKGROUND_TASK_FETCHING - _backgroundTaskIdentifier = UIBackgroundTaskInvalid; -#endif - _request = [request mutableCopy]; - _configuration = configuration; - - NSData *bodyData = request.HTTPBody; - if (bodyData) { - _bodyLength = (int64_t)bodyData.length; - } else { - _bodyLength = NSURLSessionTransferSizeUnknown; - } - - _callbackQueue = dispatch_get_main_queue(); - _callbackGroup = dispatch_group_create(); - _delegateQueue = [NSOperationQueue mainQueue]; - - _minRetryInterval = InitialMinRetryInterval(); - _maxRetryInterval = kUnsetMaxRetryInterval; - - _taskPriority = -1.0f; // Valid values if set are 0.0...1.0. - - _testBlockAccumulateDataChunkCount = 1; - -#if !STRIP_GTM_FETCH_LOGGING - // Encourage developers to set the comment property or use - // setCommentWithFormat: by providing a default string. - _comment = @"(No fetcher comment set)"; -#endif - } - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - // disallow use of fetchers in a copy property - [self doesNotRecognizeSelector:_cmd]; - return nil; -} - -- (NSString *)description { - NSString *requestStr = self.request.URL.description; - if (requestStr.length == 0) { - if (self.downloadResumeData.length > 0) { - requestStr = @"<download resume data>"; - } else if (_wasCreatedFromBackgroundSession) { - requestStr = @"<from bg session>"; - } else { - requestStr = @"<no request>"; - } - } - return [NSString stringWithFormat:@"%@ %p (%@)", [self class], self, requestStr]; -} - -- (void)dealloc { - GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, - @"unbalanced fetcher notification for %@", _request.URL); - [self forgetSessionIdentifierForFetcherWithoutSyncCheck]; - - // Note: if a session task 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 -} - -#pragma mark - - -// Begin fetching the URL (or begin a retry fetch). The delegate is retained -// for the duration of the fetch connection. - -- (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler { - GTMSessionCheckNotSynchronized(self); - - _completionHandler = [handler copy]; - - // 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. - [self beginFetchMayDelay:YES mayAuthorize:YES]; -} - -// Begin fetching the URL for a retry fetch. The delegate and completion handler -// are already provided, and do not need to be copied. -- (void)beginFetchForRetry { - GTMSessionCheckNotSynchronized(self); - - [self beginFetchMayDelay:YES mayAuthorize:YES]; -} - -- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(GTM_NULLABLE_TYPE id)target - didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { - GTMSessionFetcherAssertValidSelector(target, finishedSelector, @encode(GTMSessionFetcher *), - @encode(NSData *), @encode(NSError *), 0); - GTMSessionFetcherCompletionHandler completionHandler = ^(NSData *data, NSError *error) { - if (target && finishedSelector) { - id selfArg = self; // Placate ARC. - NSMethodSignature *sig = [target methodSignatureForSelector:finishedSelector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; - [invocation setSelector:(SEL)finishedSelector]; - [invocation setTarget:target]; - [invocation setArgument:&selfArg atIndex:2]; - [invocation setArgument:&data atIndex:3]; - [invocation setArgument:&error atIndex:4]; - [invocation invoke]; - } - }; - return completionHandler; -} - -- (void)beginFetchWithDelegate:(GTM_NULLABLE_TYPE id)target - didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { - GTMSessionCheckNotSynchronized(self); - - GTMSessionFetcherCompletionHandler handler = [self completionHandlerWithTarget:target - didFinishSelector:finishedSelector]; - [self beginFetchWithCompletionHandler:handler]; -} - -- (void)beginFetchMayDelay:(BOOL)mayDelay - mayAuthorize:(BOOL)mayAuthorize { - // This is the internal entry point for re-starting fetches. - GTMSessionCheckNotSynchronized(self); - - NSMutableURLRequest *fetchRequest = _request; // The request property is now externally immutable. - NSURL *fetchRequestURL = fetchRequest.URL; - NSString *priorSessionIdentifier = self.sessionIdentifier; - - // A utility block for creating error objects when we fail to start the fetch. - NSError *(^beginFailureError)(NSInteger) = ^(NSInteger code){ - NSString *urlString = fetchRequestURL.absoluteString; - NSDictionary *userInfo = @{ - NSURLErrorFailingURLStringErrorKey : (urlString ? urlString : @"(missing URL)") - }; - return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain - code:code - userInfo:userInfo]; - }; - - // Catch delegate queue maxConcurrentOperationCount values other than 1, particularly - // NSOperationQueueDefaultMaxConcurrentOperationCount (-1), to avoid the additional complexity - // of simultaneous or out-of-order delegate callbacks. - GTMSESSION_ASSERT_DEBUG(_delegateQueue.maxConcurrentOperationCount == 1, - @"delegate queue %@ should support one concurrent operation, not %ld", - _delegateQueue.name, - (long)_delegateQueue.maxConcurrentOperationCount); - - if (!_initialBeginFetchDate) { - // This ivar is set only here on the initial beginFetch so need not be synchronized. - _initialBeginFetchDate = [[NSDate alloc] init]; - } - - if (self.sessionTask != nil) { - // If cached fetcher returned through fetcherWithSessionIdentifier:, then it's - // already begun, but don't consider this a failure, since the user need not know this. - if (self.sessionIdentifier != nil) { - return; - } - GTMSESSION_ASSERT_DEBUG(NO, @"Fetch object %@ being reused; this should never happen", self); - [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; - return; - } - - if (fetchRequestURL == nil && !_downloadResumeData && !priorSessionIdentifier) { - GTMSESSION_ASSERT_DEBUG(NO, @"Beginning a fetch requires a request with a URL"); - [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; - return; - } - - // We'll respect the user's request for a background session (unless this is - // an upload fetcher, which does its initial request foreground.) - self.usingBackgroundSession = self.useBackgroundSession && [self canFetchWithBackgroundSession]; - - NSURL *bodyFileURL = self.bodyFileURL; - if (bodyFileURL) { - NSError *fileCheckError; - if (![bodyFileURL checkResourceIsReachableAndReturnError:&fileCheckError]) { - // This assert fires when the file being uploaded no longer exists once - // the fetcher is ready to start the upload. - GTMSESSION_ASSERT_DEBUG_OR_LOG(0, @"Body file is unreachable: %@\n %@", - bodyFileURL.path, fileCheckError); - [self failToBeginFetchWithError:fileCheckError]; - return; - } - } - - NSString *requestScheme = fetchRequestURL.scheme; - BOOL isDataRequest = [requestScheme isEqual:@"data"]; - if (isDataRequest) { - // NSURLSession does not support data URLs in background sessions. -#if DEBUG - if (priorSessionIdentifier || self.sessionIdentifier) { - GTMSESSION_LOG_DEBUG(@"Converting background to foreground session for %@", - fetchRequest); - } -#endif - [self setSessionIdentifierInternal:nil]; - self.useBackgroundSession = NO; - } - -#if GTM_ALLOW_INSECURE_REQUESTS - BOOL shouldCheckSecurity = NO; -#else - BOOL shouldCheckSecurity = (fetchRequestURL != nil - && !isDataRequest - && [[self class] appAllowsInsecureRequests]); -#endif - - if (shouldCheckSecurity) { - // Allow https only for requests, unless overridden by the client. - // - // Non-https requests may too easily be snooped, so we disallow them by default. - // - // file: and data: schemes are usually safe if they are hardcoded in the client or provided - // by a trusted source, but since it's fairly rare to need them, it's safest to make clients - // explicitly whitelist them. - BOOL isSecure = - requestScheme != nil && [requestScheme caseInsensitiveCompare:@"https"] == NSOrderedSame; - if (!isSecure) { - BOOL allowRequest = NO; - NSString *host = fetchRequestURL.host; - - // Check schemes first. A file scheme request may be allowed here, or as a localhost request. - for (NSString *allowedScheme in _allowedInsecureSchemes) { - if (requestScheme != nil && - [requestScheme caseInsensitiveCompare:allowedScheme] == NSOrderedSame) { - allowRequest = YES; - break; - } - } - if (!allowRequest) { - // Check for localhost requests. Security checks only occur for non-https requests, so - // this check won't happen for an https request to localhost. - BOOL isLocalhostRequest = (host.length == 0 && [fetchRequestURL isFileURL]) || IsLocalhost(host); - if (isLocalhostRequest) { - if (self.allowLocalhostRequest) { - allowRequest = YES; - } else { - GTMSESSION_ASSERT_DEBUG(NO, @"Fetch request for localhost but fetcher" - @" allowLocalhostRequest is not set: %@", fetchRequestURL); - } - } else { - GTMSESSION_ASSERT_DEBUG(NO, @"Insecure fetch request has a scheme (%@)" - @" not found in fetcher allowedInsecureSchemes (%@): %@", - requestScheme, _allowedInsecureSchemes ?: @" @[] ", fetchRequestURL); - } - } - - if (!allowRequest) { -#if !DEBUG - NSLog(@"Insecure fetch disallowed for %@", fetchRequestURL.description ?: @"nil request URL"); -#endif - [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorInsecureRequest)]; - return; - } - } // !isSecure - } // (requestURL != nil) && !isDataRequest - - if (self.cookieStorage == nil) { - self.cookieStorage = [[self class] staticCookieStorage]; - } - - BOOL isRecreatingSession = (self.sessionIdentifier != nil) && (fetchRequest == nil); - - self.canShareSession = !isRecreatingSession && !self.usingBackgroundSession; - - if (!self.session && self.canShareSession) { - self.session = [_service sessionForFetcherCreation]; - // If _session is nil, then the service's session creation semaphore will block - // until this fetcher invokes fetcherDidCreateSession: below, so this *must* invoke - // that method, even if the session fails to be created. - } - - if (!self.session) { - // Create a session. - if (!_configuration) { - if (priorSessionIdentifier || self.usingBackgroundSession) { - NSString *sessionIdentifier = priorSessionIdentifier; - if (!sessionIdentifier) { - sessionIdentifier = [self createSessionIdentifierWithMetadata:nil]; - } - NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap]; - [sessionIdentifierToFetcherMap setObject:self forKey:self.sessionIdentifier]; - -#if (TARGET_OS_TV \ - || TARGET_OS_WATCH \ - || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)) - // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name. - _configuration = - [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier]; -#elif (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0) - // Do a runtime check to avoid a deprecation warning about using - // +backgroundSessionConfiguration: on iOS 8. - if ([NSURLSessionConfiguration respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { - // Running on iOS 8+/OS X 10.10+. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" -// Disable unguarded availability warning as we can't use the @availability macro until we require -// all clients to build with Xcode 9 or above. - _configuration = - [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier]; -#pragma clang diagnostic pop - } else { - // Running on iOS 7/OS X 10.9. - _configuration = - [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; - } -#else - // Building with an SDK earlier than iOS 8/OS X 10.10. - _configuration = - [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; -#endif - self.usingBackgroundSession = YES; - self.canShareSession = NO; - } else { - _configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; - } -#if !GTM_ALLOW_INSECURE_REQUESTS - _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; -#endif - } // !_configuration - _configuration.HTTPCookieStorage = self.cookieStorage; - - if (_configurationBlock) { - _configurationBlock(self, _configuration); - } - - id<NSURLSessionDelegate> delegate = [_service sessionDelegate]; - if (!delegate || !self.canShareSession) { - delegate = self; - } - self.session = [NSURLSession sessionWithConfiguration:_configuration - delegate:delegate - delegateQueue:self.sessionDelegateQueue]; - GTMSESSION_ASSERT_DEBUG(self.session, @"Couldn't create session"); - - // Tell the service about the session created by this fetcher. This also signals the - // service's semaphore to allow other fetchers to request this session. - [_service fetcherDidCreateSession:self]; - - // If this assertion fires, the client probably tried to use a session identifier that was - // already used. The solution is to make the client use a unique identifier (or better yet let - // the session fetcher assign the identifier). - GTMSESSION_ASSERT_DEBUG(self.session.delegate == delegate, @"Couldn't assign delegate."); - - if (self.session) { - BOOL isUsingSharedDelegate = (delegate != self); - if (!isUsingSharedDelegate) { - _shouldInvalidateSession = YES; - } - } - } - - if (isRecreatingSession) { - _shouldInvalidateSession = YES; - - // Let's make sure there are tasks still running or if not that we get a callback from a - // completed one; otherwise, we assume the tasks failed. - // This is the observed behavior perhaps 25% of the time within the Simulator running 7.0.3 on - // exiting the app after starting an upload and relaunching the app if we manage to relaunch - // after the task has completed, but before the system relaunches us in the background. - [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, - NSArray *downloadTasks) { - if (dataTasks.count == 0 && uploadTasks.count == 0 && downloadTasks.count == 0) { - double const kDelayInSeconds = 1.0; // We should get progress indication or completion soon - dispatch_time_t checkForFeedbackDelay = - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDelayInSeconds * NSEC_PER_SEC)); - dispatch_after(checkForFeedbackDelay, dispatch_get_main_queue(), ^{ - if (!self.sessionTask && !fetchRequest) { - // If our task and/or request haven't been restored, then we assume task feedback lost. - [self removePersistedBackgroundSessionFromDefaults]; - NSError *sessionError = - [NSError errorWithDomain:kGTMSessionFetcherErrorDomain - code:GTMSessionFetcherErrorBackgroundFetchFailed - userInfo:nil]; - [self failToBeginFetchWithError:sessionError]; - } - }); - } - }]; - return; - } - - self.downloadedData = nil; - self.downloadedLength = 0; - - if (_servicePriority == NSIntegerMin) { - mayDelay = NO; - } - if (mayDelay && _service) { - BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self]; - if (!shouldFetchNow) { - // The fetch is deferred, but will happen later. - // - // If this session is held by the fetcher service, clear the session now so that we don't - // assume it's still valid after the fetcher is restarted. - if (self.canShareSession) { - self.session = nil; - } - return; - } - } - - NSString *effectiveHTTPMethod = [fetchRequest valueForHTTPHeaderField:@"X-HTTP-Method-Override"]; - if (effectiveHTTPMethod == nil) { - effectiveHTTPMethod = fetchRequest.HTTPMethod; - } - BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil - || [effectiveHTTPMethod isEqual:@"GET"]); - - BOOL needsUploadTask = (self.useUploadTask || self.bodyFileURL || self.bodyStreamProvider); - if (_bodyData || self.bodyStreamProvider || fetchRequest.HTTPBodyStream) { - if (isEffectiveHTTPGet) { - fetchRequest.HTTPMethod = @"POST"; - isEffectiveHTTPGet = NO; - } - - if (_bodyData) { - if (!needsUploadTask) { - fetchRequest.HTTPBody = _bodyData; - } -#if !STRIP_GTM_FETCH_LOGGING - } else if (fetchRequest.HTTPBodyStream) { - if ([self respondsToSelector:@selector(loggedInputStreamForInputStream:)]) { - fetchRequest.HTTPBodyStream = - [self performSelector:@selector(loggedInputStreamForInputStream:) - withObject:fetchRequest.HTTPBodyStream]; - } -#endif - } - } - - // 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 && !isDataRequest) { - BOOL isAuthorized = [_authorizer isAuthorizedRequest:fetchRequest]; - if (!isAuthorized) { - // Authorization needed. - // - // If this session is held by the fetcher service, clear the session now so that we don't - // assume it's still valid after authorization completes. - if (self.canShareSession) { - self.session = nil; - } - - // Authorizing the request will recursively call this beginFetch:mayDelay: - // or failToBeginFetchWithError:. - [self authorizeRequest]; - return; - } - } - - // set the default upload or download retry interval, if necessary - if ([self isRetryEnabled] && self.maxRetryInterval <= 0) { - if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) { - [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval]; - } else { - [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval]; - } - } - - // finally, start the connection - NSURLSessionTask *newSessionTask; - BOOL needsDataAccumulator = NO; - if (_downloadResumeData) { - newSessionTask = [_session downloadTaskWithResumeData:_downloadResumeData]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed downloadTaskWithResumeData for %@, resume data %lu bytes", - _session, (unsigned long)_downloadResumeData.length); - } else if (_destinationFileURL && !isDataRequest) { - newSessionTask = [_session downloadTaskWithRequest:fetchRequest]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed downloadTaskWithRequest for %@, %@", - _session, fetchRequest); - } else if (needsUploadTask) { - if (bodyFileURL) { - newSessionTask = [_session uploadTaskWithRequest:fetchRequest - fromFile:bodyFileURL]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed uploadTaskWithRequest for %@, %@, file %@", - _session, fetchRequest, bodyFileURL.path); - } else if (self.bodyStreamProvider) { - newSessionTask = [_session uploadTaskWithStreamedRequest:fetchRequest]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed uploadTaskWithStreamedRequest for %@, %@", - _session, fetchRequest); - } else { - GTMSESSION_ASSERT_DEBUG_OR_LOG(_bodyData != nil, - @"Upload task needs body data, %@", fetchRequest); - newSessionTask = [_session uploadTaskWithRequest:fetchRequest - fromData:(NSData * GTM_NONNULL_TYPE)_bodyData]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed uploadTaskWithRequest for %@, %@, body data %lu bytes", - _session, fetchRequest, (unsigned long)_bodyData.length); - } - needsDataAccumulator = YES; - } else { - newSessionTask = [_session dataTaskWithRequest:fetchRequest]; - needsDataAccumulator = YES; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed dataTaskWithRequest for %@, %@", - _session, fetchRequest); - } - self.sessionTask = newSessionTask; - - if (!newSessionTask) { - // We shouldn't get here; if we're here, an earlier assertion should have fired to explain - // which session task creation failed. - [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorTaskCreationFailed)]; - return; - } - - if (needsDataAccumulator && _accumulateDataBlock == nil) { - self.downloadedData = [NSMutableData data]; - } - if (_taskDescription) { - newSessionTask.taskDescription = _taskDescription; - } - if (_taskPriority >= 0) { -#if TARGET_OS_TV || TARGET_OS_WATCH - BOOL hasTaskPriority = YES; -#elif (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) - BOOL hasTaskPriority = YES; -#else - BOOL hasTaskPriority = [newSessionTask respondsToSelector:@selector(setPriority:)]; -#endif - if (hasTaskPriority) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" -// Disable unguarded availability warning as we can't use the @availability macro until we require -// all clients to build with Xcode 9 or above. - newSessionTask.priority = _taskPriority; -#pragma clang diagnostic pop - } - } - -#if GTM_DISABLE_FETCHER_TEST_BLOCK - GTMSESSION_ASSERT_DEBUG(_testBlock == nil && gGlobalTestBlock == nil, @"test blocks disabled"); - _testBlock = nil; -#else - if (!_testBlock) { - if (gGlobalTestBlock) { - // Note that the test block may pass nil for all of its response parameters, - // indicating that the fetch should actually proceed. This is useful when the - // global test block has been set, and the app is only testing a specific - // fetcher. The block simulation code will then resume the task. - _testBlock = gGlobalTestBlock; - } - } - _isUsingTestBlock = (_testBlock != nil); -#endif // GTM_DISABLE_FETCHER_TEST_BLOCK - -#if GTM_BACKGROUND_TASK_FETCHING - id<GTMUIApplicationProtocol> app = [[self class] fetcherUIApplication]; - // Background tasks seem to interfere with out-of-process uploads and downloads. - if (app && !self.skipBackgroundTask && !self.useBackgroundSession) { - // Tell UIApplication that we want to continue even when the app is in the - // background. -#if DEBUG - NSString *bgTaskName = [NSString stringWithFormat:@"%@-%@", - [self class], fetchRequest.URL.host]; -#else - NSString *bgTaskName = @"GTMSessionFetcher"; -#endif - __block UIBackgroundTaskIdentifier bgTaskID = [app beginBackgroundTaskWithName:bgTaskName - expirationHandler:^{ - // Background task expiration callback - this block is always invoked by - // UIApplication on the main thread. - if (bgTaskID != UIBackgroundTaskInvalid) { - @synchronized(self) { - if (bgTaskID == self.backgroundTaskIdentifier) { - self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; - } - } - [app endBackgroundTask:bgTaskID]; - } - }]; - @synchronized(self) { - self.backgroundTaskIdentifier = bgTaskID; - } - } -#endif - - if (!_initialRequestDate) { - _initialRequestDate = [[NSDate alloc] init]; - } - - // We don't expect to reach here even on retry or auth until a stop notification has been sent - // for the previous task, but we should ensure that we don't unbalance that. - GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, @"Start notification without a prior stop"); - [self sendStopNotificationIfNeeded]; - - [self addPersistedBackgroundSessionToDefaults]; - - [self setStopNotificationNeeded:YES]; - - [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification - userInfo:nil - requireAsync:NO]; - - // The service needs to know our task if it is serving as NSURLSession delegate. - [_service fetcherDidBeginFetching:self]; - - if (_testBlock) { -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - [self simulateFetchForTestBlock]; -#endif - } else { - // We resume the session task after posting the notification since the - // delegate callbacks may happen immediately if the fetch is started off - // the main thread or the session delegate queue is on a background thread, - // and we don't want to post a start notification after a premature finish - // of the session task. - [newSessionTask resume]; - } -} - -NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError) { - NSMutableData *data = [NSMutableData data]; - - [inputStream open]; - NSInteger numberOfBytesRead = 0; - while ([inputStream hasBytesAvailable]) { - uint8_t buffer[512]; - numberOfBytesRead = [inputStream read:buffer maxLength:sizeof(buffer)]; - if (numberOfBytesRead > 0) { - [data appendBytes:buffer length:(NSUInteger)numberOfBytesRead]; - } else { - break; - } - } - [inputStream close]; - NSError *streamError = inputStream.streamError; - - if (streamError) { - data = nil; - } - if (outError) { - *outError = streamError; - } - return data; -} - -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - -- (void)simulateFetchForTestBlock { - // This is invoked on the same thread as the beginFetch method was. - // - // Callbacks will all occur on the callback queue. - _testBlock(self, ^(NSURLResponse *response, NSData *responseData, NSError *error) { - // Callback from test block. - if (response == nil && responseData == nil && error == nil) { - // Assume the fetcher should execute rather than be tested. - self->_testBlock = nil; - self->_isUsingTestBlock = NO; - [self->_sessionTask resume]; - return; - } - - GTMSessionFetcherBodyStreamProvider bodyStreamProvider = self.bodyStreamProvider; - if (bodyStreamProvider) { - bodyStreamProvider(^(NSInputStream *bodyStream){ - // Read from the input stream into an NSData buffer. We'll drain the stream - // explicitly on a background queue. - [self invokeOnCallbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) - afterUserStopped:NO - block:^{ - NSError *streamError; - NSData *streamedData = GTMDataFromInputStream(bodyStream, &streamError); - - dispatch_async(dispatch_get_main_queue(), ^{ - // Continue callbacks on the main thread, since serial behavior - // is more reliable for tests. - [self simulateDataCallbacksForTestBlockWithBodyData:streamedData - response:response - responseData:responseData - error:(error ?: streamError)]; - }); - }]; - }); - } else { - // No input stream; use the supplied data or file URL. - NSURL *bodyFileURL = self.bodyFileURL; - if (bodyFileURL) { - NSError *readError; - self->_bodyData = [NSData dataWithContentsOfURL:bodyFileURL - options:NSDataReadingMappedIfSafe - error:&readError]; - error = readError; - } - - // No stream provider. - - // In real fetches, nothing happens until the run loop spins, so apps have leeway to - // set callbacks after they call beginFetch. We'll mirror that fetcher behavior by - // delaying callbacks here at least to the next spin of the run loop. That keeps - // immediate, synchronous setting of callback blocks after beginFetch working in tests. - dispatch_async(dispatch_get_main_queue(), ^{ - [self simulateDataCallbacksForTestBlockWithBodyData:self->_bodyData - response:response - responseData:responseData - error:error]; - }); - } - }); -} - -- (void)simulateByteTransferReportWithDataLength:(int64_t)totalDataLength - block:(GTMSessionFetcherSendProgressBlock)block { - // This utility method simulates transfer progress with up to three callbacks. - // It is used to call back to any of the progress blocks. - int64_t sendReportSize = totalDataLength / 3 + 1; - int64_t totalSent = 0; - while (totalSent < totalDataLength) { - int64_t bytesRemaining = totalDataLength - totalSent; - sendReportSize = MIN(sendReportSize, bytesRemaining); - totalSent += sendReportSize; - [self invokeOnCallbackQueueUnlessStopped:^{ - block(sendReportSize, totalSent, totalDataLength); - }]; - } -} - -- (void)simulateDataCallbacksForTestBlockWithBodyData:(NSData * GTM_NULLABLE_TYPE)bodyData - response:(NSURLResponse *)response - responseData:(NSData *)suppliedData - error:(NSError *)suppliedError { - __block NSData *responseData = suppliedData; - __block NSError *responseError = suppliedError; - - // This method does the test simulation of callbacks once the upload - // and download data are known. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Get copies of ivars we'll access in async invocations. This simulation assumes - // they won't change during fetcher execution. - NSURL *destinationFileURL = _destinationFileURL; - GTMSessionFetcherWillRedirectBlock willRedirectBlock = _willRedirectBlock; - GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock = _didReceiveResponseBlock; - GTMSessionFetcherSendProgressBlock sendProgressBlock = _sendProgressBlock; - GTMSessionFetcherDownloadProgressBlock downloadProgressBlock = _downloadProgressBlock; - GTMSessionFetcherAccumulateDataBlock accumulateDataBlock = _accumulateDataBlock; - GTMSessionFetcherReceivedProgressBlock receivedProgressBlock = _receivedProgressBlock; - GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock = - _willCacheURLResponseBlock; - - // Simulate receipt of redirection. - if (willRedirectBlock) { - [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES - block:^{ - willRedirectBlock((NSHTTPURLResponse *)response, self->_request, - ^(NSURLRequest *redirectRequest) { - // For simulation, we'll assume the app will just continue. - }); - }]; - } - - // If the fetcher has a challenge block, simulate a challenge. - // - // It might be nice to eventually let the user determine which testBlock - // fetches get challenged rather than always executing the supplied - // challenge block. - if (_challengeBlock) { - [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES - block:^{ - if (self->_challengeBlock) { - NSURL *requestURL = self->_request.URL; - NSString *host = requestURL.host; - NSURLProtectionSpace *pspace = - [[NSURLProtectionSpace alloc] initWithHost:host - port:requestURL.port.integerValue - protocol:requestURL.scheme - realm:nil - authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; - id<NSURLAuthenticationChallengeSender> unusedSender = - (id<NSURLAuthenticationChallengeSender>)[NSNull null]; - NSURLAuthenticationChallenge *challenge = - [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:pspace - proposedCredential:nil - previousFailureCount:0 - failureResponse:nil - error:nil - sender:unusedSender]; - self->_challengeBlock(self, challenge, ^(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * GTM_NULLABLE_TYPE credential){ - // We could change the responseData and responseError based on the disposition, - // but it's easier for apps to just supply the expected data and error - // directly to the test block. So this simulation ignores the disposition. - }); - } - }]; - } - - // Simulate receipt of an initial response. - if (response && didReceiveResponseBlock) { - [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES - block:^{ - didReceiveResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) { - // For simulation, we'll assume the disposition is to continue. - }); - }]; - } - - // Simulate reporting send progress. - if (sendProgressBlock) { - [self simulateByteTransferReportWithDataLength:(int64_t)bodyData.length - block:^(int64_t bytesSent, - int64_t totalBytesSent, - int64_t totalBytesExpectedToSend) { - // This is invoked on the callback queue unless stopped. - sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); - }]; - } - - if (destinationFileURL) { - // Simulate download to file progress. - if (downloadProgressBlock) { - [self simulateByteTransferReportWithDataLength:(int64_t)responseData.length - block:^(int64_t bytesDownloaded, - int64_t totalBytesDownloaded, - int64_t totalBytesExpectedToDownload) { - // This is invoked on the callback queue unless stopped. - downloadProgressBlock(bytesDownloaded, totalBytesDownloaded, - totalBytesExpectedToDownload); - }]; - } - - NSError *writeError; - [responseData writeToURL:destinationFileURL - options:NSDataWritingAtomic - error:&writeError]; - if (writeError) { - // Tell the test code that writing failed. - responseError = writeError; - } - } else { - // Simulate download to NSData progress. - if ((accumulateDataBlock || receivedProgressBlock) && responseData) { - [self simulateByteTransferWithData:responseData - block:^(NSData *data, - int64_t bytesReceived, - int64_t totalBytesReceived, - int64_t totalBytesExpectedToReceive) { - // This is invoked on the callback queue unless stopped. - if (accumulateDataBlock) { - accumulateDataBlock(data); - } - - if (receivedProgressBlock) { - receivedProgressBlock(bytesReceived, totalBytesReceived); - } - }]; - } - - if (!accumulateDataBlock) { - _downloadedData = [responseData mutableCopy]; - } - - if (willCacheURLResponseBlock) { - // Simulate letting the client inspect and alter the cached response. - NSData *cachedData = responseData ?: [[NSData alloc] init]; // Always have non-nil data. - NSCachedURLResponse *cachedResponse = - [[NSCachedURLResponse alloc] initWithResponse:response - data:cachedData]; - [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES - block:^{ - willCacheURLResponseBlock(cachedResponse, ^(NSCachedURLResponse *responseToCache){ - // The app may provide an alternative response, or nil to defeat caching. - }); - }]; - } - } - _response = response; - } // @synchronized(self) - - NSOperationQueue *queue = self.sessionDelegateQueue; - [queue addOperationWithBlock:^{ - // Rather than invoke failToBeginFetchWithError: we want to simulate completion of - // a connection that started and ended, so we'll call down to finishWithError: - NSInteger status = responseError ? responseError.code : 200; - if (status >= 200 && status <= 399) { - [self finishWithError:nil shouldRetry:NO]; - } else { - [self shouldRetryNowForStatus:status - error:responseError - forceAssumeRetry:NO - response:^(BOOL shouldRetry) { - [self finishWithError:responseError shouldRetry:shouldRetry]; - }]; - } - }]; -} - -- (void)simulateByteTransferWithData:(NSData *)responseData - block:(GTMSessionFetcherSimulateByteTransferBlock)transferBlock { - // This utility method simulates transfering data to the client. It divides the data into at most - // "chunkCount" chunks and then passes each chunk along with a progress update to transferBlock. - // This function can be used with accumulateDataBlock or receivedProgressBlock. - - NSUInteger chunkCount = MAX(self.testBlockAccumulateDataChunkCount, (NSUInteger) 1); - NSUInteger totalDataLength = responseData.length; - NSUInteger sendDataSize = totalDataLength / chunkCount + 1; - NSUInteger totalSent = 0; - while (totalSent < totalDataLength) { - NSUInteger bytesRemaining = totalDataLength - totalSent; - sendDataSize = MIN(sendDataSize, bytesRemaining); - NSData *chunkData = [responseData subdataWithRange:NSMakeRange(totalSent, sendDataSize)]; - totalSent += sendDataSize; - [self invokeOnCallbackQueueUnlessStopped:^{ - transferBlock(chunkData, - (int64_t)sendDataSize, - (int64_t)totalSent, - (int64_t)totalDataLength); - }]; - } -} - -#endif // !GTM_DISABLE_FETCHER_TEST_BLOCK - -- (void)setSessionTask:(NSURLSessionTask *)sessionTask { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_sessionTask != sessionTask) { - _sessionTask = sessionTask; - if (_sessionTask) { - // Request could be nil on restoring this fetcher from a background session. - if (!_request) { - _request = [_sessionTask.originalRequest mutableCopy]; - } - } - } - } // @synchronized(self) -} - -- (NSURLSessionTask * GTM_NULLABLE_TYPE)sessionTask { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _sessionTask; - } // @synchronized(self) -} - -+ (NSUserDefaults *)fetcherUserDefaults { - static NSUserDefaults *gFetcherUserDefaults = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - Class fetcherUserDefaultsClass = NSClassFromString(@"GTMSessionFetcherUserDefaultsFactory"); - if (fetcherUserDefaultsClass) { - gFetcherUserDefaults = [fetcherUserDefaultsClass fetcherUserDefaults]; - } else { - gFetcherUserDefaults = [NSUserDefaults standardUserDefaults]; - } - }); - return gFetcherUserDefaults; -} - -- (void)addPersistedBackgroundSessionToDefaults { - NSString *sessionIdentifier = self.sessionIdentifier; - if (!sessionIdentifier) { - return; - } - NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions]; - if ([oldBackgroundSessions containsObject:_sessionIdentifier]) { - return; - } - NSMutableArray *newBackgroundSessions = - [NSMutableArray arrayWithArray:oldBackgroundSessions]; - [newBackgroundSessions addObject:sessionIdentifier]; - GTM_LOG_BACKGROUND_SESSION(@"Add to background sessions: %@", newBackgroundSessions); - - NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; - [userDefaults setObject:newBackgroundSessions - forKey:kGTMSessionFetcherPersistedDestinationKey]; - [userDefaults synchronize]; -} - -- (void)removePersistedBackgroundSessionFromDefaults { - NSString *sessionIdentifier = self.sessionIdentifier; - if (!sessionIdentifier) return; - - NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions]; - if (!oldBackgroundSessions) { - return; - } - NSMutableArray *newBackgroundSessions = - [NSMutableArray arrayWithArray:oldBackgroundSessions]; - NSUInteger sessionIndex = [newBackgroundSessions indexOfObject:sessionIdentifier]; - if (sessionIndex == NSNotFound) { - return; - } - [newBackgroundSessions removeObjectAtIndex:sessionIndex]; - GTM_LOG_BACKGROUND_SESSION(@"Remove from background sessions: %@", newBackgroundSessions); - - NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; - if (newBackgroundSessions.count == 0) { - [userDefaults removeObjectForKey:kGTMSessionFetcherPersistedDestinationKey]; - } else { - [userDefaults setObject:newBackgroundSessions - forKey:kGTMSessionFetcherPersistedDestinationKey]; - } - [userDefaults synchronize]; -} - -+ (GTM_NULLABLE NSArray *)activePersistedBackgroundSessions { - NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; - NSArray *oldBackgroundSessions = - [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey]; - if (oldBackgroundSessions.count == 0) { - return nil; - } - NSMutableArray *activeBackgroundSessions = nil; - NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; - for (NSString *sessionIdentifier in oldBackgroundSessions) { - GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; - if (fetcher) { - if (!activeBackgroundSessions) { - activeBackgroundSessions = [[NSMutableArray alloc] init]; - } - [activeBackgroundSessions addObject:sessionIdentifier]; - } - } - return activeBackgroundSessions; -} - -+ (NSArray *)fetchersForBackgroundSessions { - NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; - NSArray *backgroundSessions = - [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey]; - NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; - NSMutableArray *fetchers = [NSMutableArray array]; - for (NSString *sessionIdentifier in backgroundSessions) { - GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; - if (!fetcher) { - fetcher = [self fetcherWithSessionIdentifier:sessionIdentifier]; - GTMSESSION_ASSERT_DEBUG(fetcher != nil, - @"Unexpected invalid session identifier: %@", sessionIdentifier); - [fetcher beginFetchWithCompletionHandler:nil]; - } - GTM_LOG_BACKGROUND_SESSION(@"%@ restoring session %@ by creating fetcher %@ %p", - [self class], sessionIdentifier, fetcher, fetcher); - if (fetcher != nil) { - [fetchers addObject:fetcher]; - } - } - return fetchers; -} - -#if TARGET_OS_IPHONE && !TARGET_OS_WATCH -+ (void)application:(UIApplication *)application - handleEventsForBackgroundURLSession:(NSString *)identifier - completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler { - GTMSessionFetcher *fetcher = [self fetcherWithSessionIdentifier:identifier]; - if (fetcher != nil) { - fetcher.systemCompletionHandler = completionHandler; - } else { - GTM_LOG_BACKGROUND_SESSION(@"%@ did not create background session identifier: %@", - [self class], identifier); - } -} -#endif - -- (NSString * GTM_NULLABLE_TYPE)sessionIdentifier { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _sessionIdentifier; - } // @synchronized(self) -} - -- (void)setSessionIdentifier:(NSString *)sessionIdentifier { - GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSESSION_ASSERT_DEBUG(!_session, @"Unable to set session identifier after session created"); - _sessionIdentifier = [sessionIdentifier copy]; - _usingBackgroundSession = YES; - _canShareSession = NO; - [self restoreDefaultStateForSessionIdentifierMetadata]; - } // @synchronized(self) -} - -- (void)setSessionIdentifierInternal:(GTM_NULLABLE NSString *)sessionIdentifier { - // This internal method only does a synchronized set of the session identifier. - // It does not have side effects on the background session, shared session, or - // session identifier metadata. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _sessionIdentifier = [sessionIdentifier copy]; - } // @synchronized(self) -} - -- (NSDictionary * GTM_NULLABLE_TYPE)sessionUserInfo { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_sessionUserInfo == nil) { - // We'll return the metadata dictionary with internal keys removed. This avoids the user - // re-using the userInfo dictionary later and accidentally including the internal keys. - NSMutableDictionary *metadata = [[self sessionIdentifierMetadataUnsynchronized] mutableCopy]; - NSSet *keysToRemove = [metadata keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { - return [key hasPrefix:@"_"]; - }]; - [metadata removeObjectsForKeys:[keysToRemove allObjects]]; - if (metadata.count > 0) { - _sessionUserInfo = metadata; - } - } - return _sessionUserInfo; - } // @synchronized(self) -} - -- (void)setSessionUserInfo:(NSDictionary * GTM_NULLABLE_TYPE)dictionary { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSESSION_ASSERT_DEBUG(_sessionIdentifier == nil, @"Too late to assign userInfo"); - _sessionUserInfo = dictionary; - } // @synchronized(self) -} - -- (GTM_NULLABLE NSDictionary *)sessionIdentifierDefaultMetadata { - GTMSessionCheckSynchronized(self); - - NSMutableDictionary *defaultUserInfo = [[NSMutableDictionary alloc] init]; - if (_destinationFileURL) { - defaultUserInfo[kGTMSessionIdentifierDestinationFileURLMetadataKey] = - [_destinationFileURL absoluteString]; - } - if (_bodyFileURL) { - defaultUserInfo[kGTMSessionIdentifierBodyFileURLMetadataKey] = [_bodyFileURL absoluteString]; - } - return (defaultUserInfo.count > 0) ? defaultUserInfo : nil; -} - -- (void)restoreDefaultStateForSessionIdentifierMetadata { - GTMSessionCheckSynchronized(self); - - NSDictionary *metadata = [self sessionIdentifierMetadataUnsynchronized]; - NSString *destinationFileURLString = metadata[kGTMSessionIdentifierDestinationFileURLMetadataKey]; - if (destinationFileURLString) { - _destinationFileURL = [NSURL URLWithString:destinationFileURLString]; - GTM_LOG_BACKGROUND_SESSION(@"Restoring destination file URL: %@", _destinationFileURL); - } - NSString *bodyFileURLString = metadata[kGTMSessionIdentifierBodyFileURLMetadataKey]; - if (bodyFileURLString) { - _bodyFileURL = [NSURL URLWithString:bodyFileURLString]; - GTM_LOG_BACKGROUND_SESSION(@"Restoring body file URL: %@", _bodyFileURL); - } -} - -- (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadata { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [self sessionIdentifierMetadataUnsynchronized]; - } -} - -- (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadataUnsynchronized { - GTMSessionCheckSynchronized(self); - - // Session Identifier format: "com.google.<ClassName>_<UUID>_<Metadata in JSON format> - if (!_sessionIdentifier) { - return nil; - } - NSScanner *metadataScanner = [NSScanner scannerWithString:_sessionIdentifier]; - [metadataScanner setCharactersToBeSkipped:nil]; - NSString *metadataString; - NSString *uuid; - if ([metadataScanner scanUpToString:@"_" intoString:NULL] && - [metadataScanner scanString:@"_" intoString:NULL] && - [metadataScanner scanUpToString:@"_" intoString:&uuid] && - [metadataScanner scanString:@"_" intoString:NULL] && - [metadataScanner scanUpToString:@"\n" intoString:&metadataString]) { - _sessionIdentifierUUID = uuid; - NSData *metadataData = [metadataString dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; - NSDictionary *metadataDict = - [NSJSONSerialization JSONObjectWithData:metadataData - options:0 - error:&error]; - GTM_LOG_BACKGROUND_SESSION(@"User Info from session identifier: %@ %@", - metadataDict, error ? error : @""); - return metadataDict; - } - return nil; -} - -- (NSString *)createSessionIdentifierWithMetadata:(NSDictionary * GTM_NULLABLE_TYPE)metadataToInclude { - NSString *result; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Session Identifier format: "com.google.<ClassName>_<UUID>_<Metadata in JSON format> - GTMSESSION_ASSERT_DEBUG(!_sessionIdentifier, @"Session identifier already created"); - _sessionIdentifierUUID = [[NSUUID UUID] UUIDString]; - _sessionIdentifier = - [NSString stringWithFormat:@"%@_%@", kGTMSessionIdentifierPrefix, _sessionIdentifierUUID]; - // Start with user-supplied keys so they cannot accidentally override the fetcher's keys. - NSMutableDictionary *metadataDict = - [NSMutableDictionary dictionaryWithDictionary:(NSDictionary * GTM_NONNULL_TYPE)_sessionUserInfo]; - - if (metadataToInclude) { - [metadataDict addEntriesFromDictionary:(NSDictionary *)metadataToInclude]; - } - NSDictionary *defaultMetadataDict = [self sessionIdentifierDefaultMetadata]; - if (defaultMetadataDict) { - [metadataDict addEntriesFromDictionary:defaultMetadataDict]; - } - if (metadataDict.count > 0) { - NSData *metadataData = [NSJSONSerialization dataWithJSONObject:metadataDict - options:0 - error:NULL]; - GTMSESSION_ASSERT_DEBUG(metadataData != nil, - @"Session identifier user info failed to convert to JSON"); - if (metadataData.length > 0) { - NSString *metadataString = [[NSString alloc] initWithData:metadataData - encoding:NSUTF8StringEncoding]; - _sessionIdentifier = - [_sessionIdentifier stringByAppendingFormat:@"_%@", metadataString]; - } - } - _didCreateSessionIdentifier = YES; - result = _sessionIdentifier; - } // @synchronized(self) - return result; -} - -- (void)failToBeginFetchWithError:(NSError *)error { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _hasStoppedFetching = YES; - } - - if (error == nil) { - error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain - code:GTMSessionFetcherErrorDownloadFailed - userInfo:nil]; - } - - [self invokeFetchCallbacksOnCallbackQueueWithData:nil - error:error]; - [self releaseCallbacks]; - - [_service fetcherDidStop:self]; - - self.authorizer = nil; -} - -+ (GTMSessionCookieStorage *)staticCookieStorage { - static GTMSessionCookieStorage *gCookieStorage = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - gCookieStorage = [[GTMSessionCookieStorage alloc] init]; - }); - return gCookieStorage; -} - -#if GTM_BACKGROUND_TASK_FETCHING - -- (void)endBackgroundTask { - // Whenever the connection stops or background execution expires, - // we need to tell UIApplication we're done. - UIBackgroundTaskIdentifier bgTaskID; - @synchronized(self) { - bgTaskID = self.backgroundTaskIdentifier; - if (bgTaskID != UIBackgroundTaskInvalid) { - self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; - } - } - - if (bgTaskID != UIBackgroundTaskInvalid) { - id<GTMUIApplicationProtocol> app = [[self class] fetcherUIApplication]; - [app endBackgroundTask:bgTaskID]; - } -} - -#endif // GTM_BACKGROUND_TASK_FETCHING - -- (void)authorizeRequest { - GTMSessionCheckNotSynchronized(self); - - id authorizer = self.authorizer; - SEL asyncAuthSel = @selector(authorizeRequest:delegate:didFinishSelector:); - if ([authorizer respondsToSelector:asyncAuthSel]) { - SEL callbackSel = @selector(authorizer:request:finishedWithError:); - NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; - [authorizer authorizeRequest:mutableRequest - delegate:self - didFinishSelector:callbackSel]; - } else { - GTMSESSION_ASSERT_DEBUG(authorizer == nil, @"invalid authorizer for fetch"); - - // No authorizing possible, and authorizing happens only after any delay; - // just begin fetching - [self beginFetchMayDelay:NO - mayAuthorize:NO]; - } -} - -- (void)authorizer:(id<GTMFetcherAuthorizationProtocol>)auth - request:(NSMutableURLRequest *)authorizedRequest - finishedWithError:(NSError *)error { - GTMSessionCheckNotSynchronized(self); - - if (error != nil) { - // We can't fetch without authorization - [self failToBeginFetchWithError:error]; - } else { - @synchronized(self) { - _request = authorizedRequest; - } - [self beginFetchMayDelay:NO - mayAuthorize:NO]; - } -} - - -- (BOOL)canFetchWithBackgroundSession { - // Subclasses may override. - return YES; -} - -// Returns YES if the fetcher has been started and has not yet stopped. -// -// Fetching includes waiting for authorization or for retry, waiting to be allowed by the -// service object to start the request, and actually fetching the request. -- (BOOL)isFetching { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [self isFetchingUnsynchronized]; - } -} - -- (BOOL)isFetchingUnsynchronized { - GTMSessionCheckSynchronized(self); - - BOOL hasBegun = (_initialBeginFetchDate != nil); - return hasBegun && !_hasStoppedFetching; -} - -- (NSURLResponse * GTM_NULLABLE_TYPE)response { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSURLResponse *response = [self responseUnsynchronized]; - return response; - } // @synchronized(self) -} - -- (NSURLResponse * GTM_NULLABLE_TYPE)responseUnsynchronized { - GTMSessionCheckSynchronized(self); - - NSURLResponse *response = _sessionTask.response; - if (!response) response = _response; - return response; -} - -- (NSInteger)statusCode { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSInteger statusCode = [self statusCodeUnsynchronized]; - return statusCode; - } // @synchronized(self) -} - -- (NSInteger)statusCodeUnsynchronized { - GTMSessionCheckSynchronized(self); - - NSURLResponse *response = [self responseUnsynchronized]; - NSInteger statusCode; - - if ([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 * GTM_NULLABLE_TYPE)responseHeaders { - GTMSessionCheckNotSynchronized(self); - - NSURLResponse *response = self.response; - if ([response respondsToSelector:@selector(allHeaderFields)]) { - NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; - return headers; - } - return nil; -} - -- (NSDictionary * GTM_NULLABLE_TYPE)responseHeadersUnsynchronized { - GTMSessionCheckSynchronized(self); - - NSURLResponse *response = [self responseUnsynchronized]; - if ([response respondsToSelector:@selector(allHeaderFields)]) { - NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; - return headers; - } - return nil; -} - -- (void)releaseCallbacks { - // Avoid releasing blocks in the sync section since objects dealloc'd by - // the blocks being released may call back into the fetcher or fetcher - // service. - dispatch_queue_t NS_VALID_UNTIL_END_OF_SCOPE holdCallbackQueue; - GTMSessionFetcherCompletionHandler NS_VALID_UNTIL_END_OF_SCOPE holdCompletionHandler; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - holdCallbackQueue = _callbackQueue; - holdCompletionHandler = _completionHandler; - - _callbackQueue = nil; - _completionHandler = nil; // Setter overridden in upload. Setter assumed to be used externally. - } - - // Set local callback pointers to nil here rather than let them release at the end of the scope - // to make any problems due to the blocks being released be a bit more obvious in a stack trace. - holdCallbackQueue = nil; - holdCompletionHandler = nil; - - self.configurationBlock = nil; - self.didReceiveResponseBlock = nil; - self.challengeBlock = nil; - self.willRedirectBlock = nil; - self.sendProgressBlock = nil; - self.receivedProgressBlock = nil; - self.downloadProgressBlock = nil; - self.accumulateDataBlock = nil; - self.willCacheURLResponseBlock = nil; - self.retryBlock = nil; - self.testBlock = nil; - self.resumeDataBlock = nil; -} - -- (void)forgetSessionIdentifierForFetcher { - GTMSessionCheckSynchronized(self); - [self forgetSessionIdentifierForFetcherWithoutSyncCheck]; -} - -- (void)forgetSessionIdentifierForFetcherWithoutSyncCheck { - // This should be called inside a @synchronized block (except during dealloc.) - if (_sessionIdentifier) { - NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap]; - [sessionIdentifierToFetcherMap removeObjectForKey:_sessionIdentifier]; - _sessionIdentifier = nil; - _didCreateSessionIdentifier = NO; - } -} - -// External stop method -- (void)stopFetching { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Prevent enqueued callbacks from executing. - _userStoppedFetching = YES; - } // @synchronized(self) - [self stopFetchReleasingCallbacks:YES]; -} - -// Cancel the fetch of the URL that's currently in progress. -// -// If shouldReleaseCallbacks is NO then the fetch will be retried so the callbacks -// need to still be retained. -- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks { - [self removePersistedBackgroundSessionFromDefaults]; - - id<GTMSessionFetcherServiceProtocol> service; - NSMutableURLRequest *request; - - // If the task 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. - __autoreleasing GTMSessionFetcher *holdSelf = self; - - BOOL hasCanceledTask = NO; - - [holdSelf destroyRetryTimer]; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _hasStoppedFetching = YES; - - service = _service; - request = _request; - - if (_sessionTask) { - // In case cancelling the task or session calls this recursively, we want - // to ensure that we'll only release the task and delegate once, - // so first set _sessionTask to nil - // - // This may be called in a callback from the task, so use autorelease to avoid - // releasing the task in its own callback. - __autoreleasing NSURLSessionTask *oldTask = _sessionTask; - if (!_isUsingTestBlock) { - _response = _sessionTask.response; - } - _sessionTask = nil; - - if ([oldTask state] != NSURLSessionTaskStateCompleted) { - // For download tasks, when the fetch is stopped, we may provide resume data that can - // be used to create a new session. - BOOL mayResume = (_resumeDataBlock - && [oldTask respondsToSelector:@selector(cancelByProducingResumeData:)]); - if (!mayResume) { - [oldTask cancel]; - // A side effect of stopping the task is that URLSession:task:didCompleteWithError: - // will be invoked asynchronously on the delegate queue. - } else { - void (^resumeBlock)(NSData *) = _resumeDataBlock; - _resumeDataBlock = nil; - - // Save callbackQueue since releaseCallbacks clears it. - dispatch_queue_t callbackQueue = _callbackQueue; - dispatch_group_enter(_callbackGroup); - [(NSURLSessionDownloadTask *)oldTask cancelByProducingResumeData:^(NSData *resumeData) { - [self invokeOnCallbackQueue:callbackQueue - afterUserStopped:YES - block:^{ - resumeBlock(resumeData); - dispatch_group_leave(self->_callbackGroup); - }]; - }]; - } - hasCanceledTask = YES; - } - } - - // If the task was canceled, wait until the URLSession:task:didCompleteWithError: to call - // finishTasksAndInvalidate, since calling it immediately tends to crash, see radar 18471901. - if (_session) { - BOOL shouldInvalidate = _shouldInvalidateSession; -#if TARGET_OS_IPHONE - // Don't invalidate if we've got a systemCompletionHandler, since - // URLSessionDidFinishEventsForBackgroundURLSession: won't be called if invalidated. - shouldInvalidate = shouldInvalidate && !self.systemCompletionHandler; -#endif - if (shouldInvalidate) { - __autoreleasing NSURLSession *oldSession = _session; - _session = nil; - - if (!hasCanceledTask) { - [oldSession finishTasksAndInvalidate]; - } else { - _sessionNeedingInvalidation = oldSession; - } - } - } - } // @synchronized(self) - - // send the stopped notification - [self sendStopNotificationIfNeeded]; - - [_authorizer stopAuthorizationForRequest:request]; - - if (shouldReleaseCallbacks) { - [self releaseCallbacks]; - - self.authorizer = nil; - } - - [service fetcherDidStop:self]; - -#if GTM_BACKGROUND_TASK_FETCHING - [self endBackgroundTask]; -#endif -} - -- (void)setStopNotificationNeeded:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _isStopNotificationNeeded = flag; - } // @synchronized(self) -} - -- (void)sendStopNotificationIfNeeded { - BOOL sendNow = NO; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_isStopNotificationNeeded) { - _isStopNotificationNeeded = NO; - sendNow = YES; - } - } // @synchronized(self) - - if (sendNow) { - [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification - userInfo:nil - requireAsync:NO]; - } -} - -- (void)retryFetch { - [self stopFetchReleasingCallbacks:NO]; - - // A retry will need a configuration with a fresh session identifier. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_sessionIdentifier && _didCreateSessionIdentifier) { - [self forgetSessionIdentifierForFetcher]; - _configuration = nil; - } - - if (_canShareSession) { - // Force a grab of the current session from the fetcher service in case - // the service's old one has become invalid. - _session = nil; - } - } // @synchronized(self) - - [self beginFetchForRetry]; -} - -- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { - // Uncovered in upload fetcher testing, because the chunk fetcher is being waited on, and gets - // released by the upload code. The uploader just holds onto it with an ivar, and that gets - // nilled in the chunk fetcher callback. - // Used once in while loop just to avoid unused variable compiler warning. - __autoreleasing GTMSessionFetcher *holdSelf = self; - - NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; - - BOOL shouldSpinRunLoop = ([NSThread isMainThread] && - (!self.callbackQueue - || self.callbackQueue == dispatch_get_main_queue())); - BOOL expired = NO; - - // Loop until the callbacks have been called and released, and until - // the connection is no longer pending, until there are no callback dispatches - // in flight, or until the timeout has expired. - int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms - while (1) { - BOOL isTaskInProgress = (holdSelf->_sessionTask - && [_sessionTask state] != NSURLSessionTaskStateCompleted); - BOOL needsToCallCompletion = (_completionHandler != nil); - BOOL isCallbackInProgress = (_callbackGroup - && dispatch_group_wait(_callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta))); - - if (!isTaskInProgress && !needsToCallCompletion && !isCallbackInProgress) break; - - expired = ([giveUpDate timeIntervalSinceNow] < 0); - if (expired) { - GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher waitForCompletionWithTimeout:%0.1f expired -- " - @"%@%@%@", timeoutInSeconds, - isTaskInProgress ? @"taskInProgress " : @"", - needsToCallCompletion ? @"needsToCallCompletion " : @"", - isCallbackInProgress ? @"isCallbackInProgress" : @""); - break; - } - - // Run the current run loop 1/1000 of a second to give the networking - // code a chance to work - const NSTimeInterval kSpinInterval = 0.001; - if (shouldSpinRunLoop) { - NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; - [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; - } else { - [NSThread sleepForTimeInterval:kSpinInterval]; - } - } - return !expired; -} - -+ (void)setGlobalTestBlock:(GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE)block { -#if GTM_DISABLE_FETCHER_TEST_BLOCK - GTMSESSION_ASSERT_DEBUG(block == nil, @"test blocks disabled"); -#endif - gGlobalTestBlock = [block copy]; -} - -#if GTM_BACKGROUND_TASK_FETCHING - -static GTM_NULLABLE_TYPE id<GTMUIApplicationProtocol> gSubstituteUIApp; - -+ (void)setSubstituteUIApplication:(nullable id<GTMUIApplicationProtocol>)app { - gSubstituteUIApp = app; -} - -+ (nullable id<GTMUIApplicationProtocol>)substituteUIApplication { - return gSubstituteUIApp; -} - -+ (nullable id<GTMUIApplicationProtocol>)fetcherUIApplication { - id<GTMUIApplicationProtocol> app = gSubstituteUIApp; - if (app) return app; - - // iOS App extensions should not call [UIApplication sharedApplication], even - // if UIApplication responds to it. - - static Class applicationClass = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - BOOL isAppExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; - if (!isAppExtension) { - Class cls = NSClassFromString(@"UIApplication"); - if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { - applicationClass = cls; - } - } - }); - - if (applicationClass) { - app = (id<GTMUIApplicationProtocol>)[applicationClass sharedApplication]; - } - return app; -} -#endif // GTM_BACKGROUND_TASK_FETCHING - -#pragma mark NSURLSession Delegate Methods - -// NSURLSession documentation indicates that redirectRequest can be passed to the handler -// but empirically redirectRequest lacks the HTTP body, so passing it will break POSTs. -// Instead, we construct a new request, a copy of the original, with overrides from the -// redirect. - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -willPerformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse - newRequest:(NSURLRequest *)redirectRequest - completionHandler:(void (^)(NSURLRequest * GTM_NULLABLE_TYPE))handler { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ willPerformHTTPRedirection:%@ newRequest:%@", - [self class], self, session, task, redirectResponse, redirectRequest); - - if ([self userStoppedFetching]) { - handler(nil); - return; - } - if (redirectRequest && redirectResponse) { - // Copy the original request, including the body. - NSURLRequest *originalRequest = self.request; - NSMutableURLRequest *newRequest = [originalRequest mutableCopy]; - - // The new requests's URL overrides the original's URL. - [newRequest setURL:[GTMSessionFetcher redirectURLWithOriginalRequestURL:originalRequest.URL - redirectRequestURL:redirectRequest.URL]]; - - // 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]; - } - - redirectRequest = newRequest; - - // Log the response we just received - [self setResponse:redirectResponse]; - [self logNowWithError:nil]; - - GTMSessionFetcherWillRedirectBlock willRedirectBlock = self.willRedirectBlock; - if (willRedirectBlock) { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - willRedirectBlock(redirectResponse, redirectRequest, ^(NSURLRequest *clientRequest) { - - // Update the request for future logging. - [self updateMutableRequest:[clientRequest mutableCopy]]; - - handler(clientRequest); - }); - }]; - } // @synchronized(self) - return; - } - // Continues here if the client did not provide a redirect block. - - // Update the request for future logging. - [self updateMutableRequest:[redirectRequest mutableCopy]]; - } - handler(redirectRequest); -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didReceiveResponse:(NSURLResponse *)response - completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))handler { - [self setSessionTask:dataTask]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveResponse:%@", - [self class], self, session, dataTask, response); - void (^accumulateAndFinish)(NSURLSessionResponseDisposition) = - ^(NSURLSessionResponseDisposition dispositionValue) { - // 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. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - BOOL hadPreviousData = self->_downloadedLength > 0; - - [self->_downloadedData setLength:0]; - self->_downloadedLength = 0; - - if (hadPreviousData && (dispositionValue != NSURLSessionResponseCancel)) { - // Tell the accumulate block to discard prior data. - GTMSessionFetcherAccumulateDataBlock accumulateBlock = self->_accumulateDataBlock; - if (accumulateBlock) { - [self invokeOnCallbackQueueUnlessStopped:^{ - accumulateBlock(nil); - }]; - } - } - } // @synchronized(self) - handler(dispositionValue); - }; - - GTMSessionFetcherDidReceiveResponseBlock receivedResponseBlock; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - receivedResponseBlock = _didReceiveResponseBlock; - if (receivedResponseBlock) { - // We will ultimately need to call back to NSURLSession's handler with the disposition value - // for this delegate method even if the user has stopped the fetcher. - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - receivedResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) { - accumulateAndFinish(desiredDisposition); - }); - }]; - } - } // @synchronized(self) - - if (receivedResponseBlock == nil) { - accumulateAndFinish(NSURLSessionResponseAllow); - } -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didBecomeDownloadTask:%@", - [self class], self, session, dataTask, downloadTask); - [self setSessionTask:downloadTask]; -} - - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * GTM_NULLABLE_TYPE credential))handler { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didReceiveChallenge:%@", - [self class], self, session, task, challenge); - - GTMSessionFetcherChallengeBlock challengeBlock = self.challengeBlock; - if (challengeBlock) { - // The fetcher user has provided custom challenge handling. - // - // We will ultimately need to call back to NSURLSession's handler with the disposition value - // for this delegate method even if the user has stopped the fetcher. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - challengeBlock(self, challenge, handler); - }]; - } - } else { - // No challenge block was provided by the client. - [self respondToChallenge:challenge - completionHandler:handler]; - } -} - -- (void)respondToChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * GTM_NULLABLE_TYPE credential))handler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSInteger previousFailureCount = [challenge previousFailureCount]; - if (previousFailureCount <= 2) { - NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; - NSString *authenticationMethod = [protectionSpace authenticationMethod]; - if ([authenticationMethod isEqual:NSURLAuthenticationMethodServerTrust]) { - // SSL. - // - // Background sessions seem to require an explicit check of the server trust object - // rather than default handling. - SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; - if (serverTrust == NULL) { - // No server trust information is available. - handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - } else { - // Server trust information is available. - void (^callback)(SecTrustRef, BOOL) = ^(SecTrustRef trustRef, BOOL allow){ - if (allow) { - NSURLCredential *trustCredential = [NSURLCredential credentialForTrust:trustRef]; - handler(NSURLSessionAuthChallengeUseCredential, trustCredential); - } else { - GTMSESSION_LOG_DEBUG(@"Cancelling authentication challenge for %@", self->_request.URL); - handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); - } - }; - if (_allowInvalidServerCertificates) { - callback(serverTrust, YES); - } else { - [[self class] evaluateServerTrust:serverTrust - forRequest:_request - completionHandler:callback]; - } - } - return; - } - - NSURLCredential *credential = _credential; - - if ([[challenge protectionSpace] isProxy] && _proxyCredential != nil) { - credential = _proxyCredential; - } - - if (credential) { - handler(NSURLSessionAuthChallengeUseCredential, credential); - } else { - // The credential is still nil; tell the OS to use the default handling. This is needed - // for things that can come out of the keychain (proxies, client certificates, etc.). - // - // Note: Looking up a credential with NSURLCredentialStorage's - // defaultCredentialForProtectionSpace: is *not* the same invoking the handler with - // NSURLSessionAuthChallengePerformDefaultHandling. In the case of - // NSURLAuthenticationMethodClientCertificate, you can get nil back from - // NSURLCredentialStorage, while using this code path instead works. - handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - } - - } else { - // We've failed auth 3 times. The completion handler will be called with code - // NSURLErrorCancelled. - handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); - } - } // @synchronized(self) -} - -// Return redirect URL based on the original request URL and redirect request URL. -// -// Method disallows any scheme changes between the original request URL and redirect request URL -// aside from "http" to "https". If a change in scheme is detected the redirect URL inherits the -// scheme from the original request URL. -+ (GTM_NULLABLE NSURL *)redirectURLWithOriginalRequestURL:(GTM_NULLABLE NSURL *)originalRequestURL - redirectRequestURL:(GTM_NULLABLE NSURL *)redirectRequestURL { - // In the case of an NSURLSession redirect, neither URL should ever be nil; as a sanity check - // if either is nil return the other URL. - if (!redirectRequestURL) return originalRequestURL; - if (!originalRequestURL) return redirectRequestURL; - - NSString *originalScheme = originalRequestURL.scheme; - NSString *redirectScheme = redirectRequestURL.scheme; - BOOL insecureToSecureRedirect = - (originalScheme != nil && [originalScheme caseInsensitiveCompare:@"http"] == NSOrderedSame && - redirectScheme != nil && [redirectScheme caseInsensitiveCompare:@"https"] == NSOrderedSame); - - // Check for changes to the scheme and disallow any changes except for http to https. - if (!insecureToSecureRedirect && - (redirectScheme.length != originalScheme.length || - [redirectScheme caseInsensitiveCompare:originalScheme] != NSOrderedSame)) { - NSURLComponents *components = - [NSURLComponents componentsWithURL:(NSURL * _Nonnull)redirectRequestURL - resolvingAgainstBaseURL:NO]; - components.scheme = originalScheme; - return components.URL; - } - - return redirectRequestURL; -} - -// Validate the certificate chain. -// -// This may become a public method if it appears to be useful to users. -+ (void)evaluateServerTrust:(SecTrustRef)serverTrust - forRequest:(NSURLRequest *)request - completionHandler:(void (^)(SecTrustRef trustRef, BOOL allow))handler { - // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. - CFRetain(serverTrust); - - // Evaluate the certificate chain. - // - // The delegate queue may be the main thread. Trust evaluation could cause some - // blocking network activity, so we must evaluate async, as documented at - // https://developer.apple.com/library/ios/technotes/tn2232/ - // - // We must also avoid multiple uses of the trust object, per docs: - // "It is not safe to call this function concurrently with any other function that uses - // the same trust management object, or to re-enter this function for the same trust - // management object." - // - // SecTrustEvaluateAsync both does sync execution of Evaluate and calls back on the - // queue passed to it, according to at sources in - // http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.9/lib/SecTrust.cpp - // It would require a global serial queue to ensure the evaluate happens only on a - // single thread at a time, so we'll stick with using SecTrustEvaluate on a background - // thread. - dispatch_queue_t evaluateBackgroundQueue = - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(evaluateBackgroundQueue, ^{ - // It looks like the implementation of SecTrustEvaluate() on Mac grabs a global lock, - // so it may be redundant for us to also lock, but it's easy to synchronize here - // anyway. - SecTrustResultType trustEval = kSecTrustResultInvalid; - BOOL shouldAllow; - OSStatus trustError; - @synchronized([GTMSessionFetcher class]) { - GTMSessionMonitorSynchronized([GTMSessionFetcher class]); - - trustError = SecTrustEvaluate(serverTrust, &trustEval); - } - if (trustError != errSecSuccess) { - GTMSESSION_LOG_DEBUG(@"Error %d evaluating trust for %@", - (int)trustError, request); - shouldAllow = NO; - } else { - // Having a trust level "unspecified" by the user is the usual result, described at - // https://developer.apple.com/library/mac/qa/qa1360 - if (trustEval == kSecTrustResultUnspecified - || trustEval == kSecTrustResultProceed) { - shouldAllow = YES; - } else { - shouldAllow = NO; - GTMSESSION_LOG_DEBUG(@"Challenge SecTrustResultType %u for %@, properties: %@", - trustEval, request.URL.host, - CFBridgingRelease(SecTrustCopyProperties(serverTrust))); - } - } - handler(serverTrust, shouldAllow); - - CFRelease(serverTrust); - }); -} - -- (void)invokeOnCallbackQueueUnlessStopped:(void (^)(void))block { - [self invokeOnCallbackQueueAfterUserStopped:NO - block:block]; -} - -- (void)invokeOnCallbackQueueAfterUserStopped:(BOOL)afterStopped - block:(void (^)(void))block { - GTMSessionCheckSynchronized(self); - - [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:afterStopped - block:block]; -} - -- (void)invokeOnCallbackUnsynchronizedQueueAfterUserStopped:(BOOL)afterStopped - block:(void (^)(void))block { - // testBlock simulation code may not be synchronizing when this is invoked. - [self invokeOnCallbackQueue:_callbackQueue - afterUserStopped:afterStopped - block:block]; -} - -- (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue - afterUserStopped:(BOOL)afterStopped - block:(void (^)(void))block { - if (callbackQueue) { - dispatch_group_async(_callbackGroup, callbackQueue, ^{ - if (!afterStopped) { - NSDate *serviceStoppedAllDate = [self->_service stoppedAllFetchersDate]; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Avoid a race between stopFetching and the callback. - if (self->_userStoppedFetching) { - return; - } - - // Also avoid calling back if the service has stopped all fetchers - // since this one was created. The fetcher may have stopped before - // stopAllFetchers was invoked, so _userStoppedFetching wasn't set, - // but the app still won't expect the callback to fire after - // the service's stopAllFetchers was invoked. - if (serviceStoppedAllDate - && [self->_initialBeginFetchDate compare:serviceStoppedAllDate] != NSOrderedDescending) { - // stopAllFetchers was called after this fetcher began. - return; - } - } // @synchronized(self) - } - block(); - }); - } -} - -- (void)invokeFetchCallbacksOnCallbackQueueWithData:(GTM_NULLABLE NSData *)data - error:(GTM_NULLABLE NSError *)error { - // Callbacks will be released in the method stopFetchReleasingCallbacks: - GTMSessionFetcherCompletionHandler handler; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - handler = _completionHandler; - - if (handler) { - [self invokeOnCallbackQueueUnlessStopped:^{ - handler(data, error); - - // Post a notification, primarily to allow code to collect responses for - // testing. - // - // The observing code is not likely on the fetcher's callback - // queue, so this posts explicitly to the main queue. - NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - if (data) { - userInfo[kGTMSessionFetcherCompletionDataKey] = data; - } - if (error) { - userInfo[kGTMSessionFetcherCompletionErrorKey] = error; - } - [self postNotificationOnMainThreadWithName:kGTMSessionFetcherCompletionInvokedNotification - userInfo:userInfo - requireAsync:NO]; - }]; - } - } // @synchronized(self) -} - -- (void)postNotificationOnMainThreadWithName:(NSString *)noteName - userInfo:(GTM_NULLABLE NSDictionary *)userInfo - requireAsync:(BOOL)requireAsync { - dispatch_block_t postBlock = ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:noteName - object:self - userInfo:userInfo]; - }; - - if ([NSThread isMainThread] && !requireAsync) { - // Post synchronously for compatibility with older code using the fetcher. - - // Avoid calling out to other code from inside a sync block to avoid risk - // of a deadlock or of recursive sync. - GTMSessionCheckNotSynchronized(self); - - postBlock(); - } else { - dispatch_async(dispatch_get_main_queue(), postBlock); - } -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)uploadTask - needNewBodyStream:(void (^)(NSInputStream * GTM_NULLABLE_TYPE bodyStream))completionHandler { - [self setSessionTask:uploadTask]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ needNewBodyStream:", - [self class], self, session, uploadTask); - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSessionFetcherBodyStreamProvider provider = _bodyStreamProvider; -#if !STRIP_GTM_FETCH_LOGGING - if ([self respondsToSelector:@selector(loggedStreamProviderForStreamProvider:)]) { - provider = [self performSelector:@selector(loggedStreamProviderForStreamProvider:) - withObject:provider]; - } -#endif - if (provider) { - [self invokeOnCallbackQueueUnlessStopped:^{ - provider(completionHandler); - }]; - } else { - GTMSESSION_ASSERT_DEBUG(NO, @"NSURLSession expects a stream provider"); - - completionHandler(nil); - } - } // @synchronized(self) -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - didSendBodyData:(int64_t)bytesSent - totalBytesSent:(int64_t)totalBytesSent -totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didSendBodyData:%lld" - @" totalBytesSent:%lld totalBytesExpectedToSend:%lld", - [self class], self, session, task, bytesSent, totalBytesSent, - totalBytesExpectedToSend); - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (!_sendProgressBlock) { - return; - } - // We won't hold on to send progress block; it's ok to not send it if the upload finishes. - [self invokeOnCallbackQueueUnlessStopped:^{ - GTMSessionFetcherSendProgressBlock progressBlock; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - progressBlock = self->_sendProgressBlock; - } - if (progressBlock) { - progressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); - } - }]; - } // @synchronized(self) -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - didReceiveData:(NSData *)data { - [self setSessionTask:dataTask]; - NSUInteger bufferLength = data.length; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveData:%p (%llu bytes)", - [self class], self, session, dataTask, data, - (unsigned long long)bufferLength); - if (bufferLength == 0) { - // Observed on completing an out-of-process upload. - return; - } - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSessionFetcherAccumulateDataBlock accumulateBlock = _accumulateDataBlock; - if (accumulateBlock) { - // Let the client accumulate the data. - _downloadedLength += bufferLength; - [self invokeOnCallbackQueueUnlessStopped:^{ - accumulateBlock(data); - }]; - } else if (!_userStoppedFetching) { - // Append to the mutable data buffer unless the fetch has been cancelled. - - // Resumed upload tasks may not yet have a data buffer. - if (_downloadedData == nil) { - // Using NSClassFromString for iOS 6 compatibility. - GTMSESSION_ASSERT_DEBUG( - ![dataTask isKindOfClass:NSClassFromString(@"NSURLSessionDownloadTask")], - @"Resumed download tasks should not receive data bytes"); - _downloadedData = [[NSMutableData alloc] init]; - } - - [_downloadedData appendData:data]; - _downloadedLength = (int64_t)_downloadedData.length; - - // We won't hold on to receivedProgressBlock here; it's ok to not send - // it if the transfer finishes. - if (_receivedProgressBlock) { - [self invokeOnCallbackQueueUnlessStopped:^{ - GTMSessionFetcherReceivedProgressBlock progressBlock; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - progressBlock = self->_receivedProgressBlock; - } - if (progressBlock) { - progressBlock((int64_t)bufferLength, self->_downloadedLength); - } - }]; - } - } - } // @synchronized(self) -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - willCacheResponse:(NSCachedURLResponse *)proposedResponse - completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ willCacheResponse:%@ %@", - [self class], self, session, dataTask, - proposedResponse, proposedResponse.response); - GTMSessionFetcherWillCacheURLResponseBlock callback; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - callback = _willCacheURLResponseBlock; - - if (callback) { - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - callback(proposedResponse, completionHandler); - }]; - } - } // @synchronized(self) - if (!callback) { - completionHandler(proposedResponse); - } -} - - -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask - didWriteData:(int64_t)bytesWritten - totalBytesWritten:(int64_t)totalBytesWritten -totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didWriteData:%lld" - @" bytesWritten:%lld totalBytesExpectedToWrite:%lld", - [self class], self, session, downloadTask, bytesWritten, - totalBytesWritten, totalBytesExpectedToWrite); - [self setSessionTask:downloadTask]; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if ((totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown) && - (totalBytesExpectedToWrite < totalBytesWritten)) { - // Have observed cases were bytesWritten == totalBytesExpectedToWrite, - // but totalBytesWritten > totalBytesExpectedToWrite, so setting to unkown in these cases. - totalBytesExpectedToWrite = NSURLSessionTransferSizeUnknown; - } - // We won't hold on to download progress block during the enqueue; - // it's ok to not send it if the upload finishes. - - [self invokeOnCallbackQueueUnlessStopped:^{ - GTMSessionFetcherDownloadProgressBlock progressBlock; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - progressBlock = self->_downloadProgressBlock; - } - if (progressBlock) { - progressBlock(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); - } - }]; - } // @synchronized(self) -} - -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask - didResumeAtOffset:(int64_t)fileOffset -expectedTotalBytes:(int64_t)expectedTotalBytes { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didResumeAtOffset:%lld" - @" expectedTotalBytes:%lld", - [self class], self, session, downloadTask, fileOffset, - expectedTotalBytes); - [self setSessionTask:downloadTask]; -} - -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask -didFinishDownloadingToURL:(NSURL *)downloadLocationURL { - // Download may have relaunched app, so update _sessionTask. - [self setSessionTask:downloadTask]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didFinishDownloadingToURL:%@", - [self class], self, session, downloadTask, downloadLocationURL); - NSNumber *fileSizeNum; - [downloadLocationURL getResourceValue:&fileSizeNum - forKey:NSURLFileSizeKey - error:NULL]; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSURL *destinationURL = _destinationFileURL; - - _downloadedLength = fileSizeNum.longLongValue; - - // Overwrite any previous file at the destination URL. - NSFileManager *fileMgr = [NSFileManager defaultManager]; - NSError *removeError; - if (![fileMgr removeItemAtURL:destinationURL error:&removeError] - && removeError.code != NSFileNoSuchFileError) { - GTMSESSION_LOG_DEBUG(@"Could not remove previous file at %@ due to %@", - downloadLocationURL.path, removeError); - } - - NSInteger statusCode = [self statusCodeUnsynchronized]; - if (statusCode < 200 || statusCode > 399) { - // In OS X 10.11, the response body is written to a file even on a server - // status error. For convenience of the fetcher client, we'll skip saving the - // downloaded body to the destination URL so that clients do not need to know - // to delete the file following fetch errors. - GTMSESSION_LOG_DEBUG(@"Abandoning download due to status %ld, file %@", - (long)statusCode, downloadLocationURL.path); - - // On error code, add the contents of the temporary file to _downloadTaskErrorData - // This way fetcher clients have access to error details possibly passed by the server. - if (_downloadedLength > 0 && _downloadedLength <= kMaximumDownloadErrorDataLength) { - _downloadTaskErrorData = [NSData dataWithContentsOfURL:downloadLocationURL]; - } else if (_downloadedLength > kMaximumDownloadErrorDataLength) { - GTMSESSION_LOG_DEBUG(@"Download error data for file %@ not passed to userInfo due to size " - @"%lld", downloadLocationURL.path, _downloadedLength); - } - } else { - NSError *moveError; - NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; - BOOL didMoveDownload = NO; - if ([fileMgr createDirectoryAtURL:destinationFolderURL - withIntermediateDirectories:YES - attributes:nil - error:&moveError]) { - didMoveDownload = [fileMgr moveItemAtURL:downloadLocationURL - toURL:destinationURL - error:&moveError]; - } - if (!didMoveDownload) { - _downloadFinishedError = moveError; - } - GTM_LOG_BACKGROUND_SESSION(@"%@ %p Moved download from \"%@\" to \"%@\" %@", - [self class], self, - downloadLocationURL.path, destinationURL.path, - error ? error : @""); - } - } // @synchronized(self) -} - -/* Sent as the last message related to a specific task. Error may be - * nil, which implies that no error occurred and this task is complete. - */ -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didCompleteWithError:(NSError *)error { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didCompleteWithError:%@", - [self class], self, session, task, error); - - NSInteger status = self.statusCode; - BOOL forceAssumeRetry = NO; - BOOL succeeded = NO; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - // The task is never resumed when a testBlock is used. When the session is destroyed, - // we should ignore the callback, since the testBlock support code itself invokes - // shouldRetryNowForStatus: and finishWithError:shouldRetry: - if (_isUsingTestBlock) return; -#endif - - if (error == nil) { - error = _downloadFinishedError; - } - succeeded = (error == nil && status >= 0 && status < 300); - if (succeeded) { - // Succeeded. - _bodyLength = task.countOfBytesSent; - } - } // @synchronized(self) - - if (succeeded) { - [self finishWithError:nil shouldRetry:NO]; - return; - } - // For background redirects, no delegate method is called, so we cannot restore a stripped - // Authorization header, so if a 403 ("Forbidden") was generated due to a missing OAuth 2 header, - // set the current request's URL to the redirected URL, so we in effect restore the Authorization - // header. - if ((status == 403) && self.usingBackgroundSession) { - NSURL *redirectURL = self.response.URL; - NSURLRequest *request = self.request; - if (![request.URL isEqual:redirectURL]) { - NSString *authorizationHeader = [request.allHTTPHeaderFields objectForKey:@"Authorization"]; - if (authorizationHeader != nil) { - NSMutableURLRequest *mutableRequest = [request mutableCopy]; - mutableRequest.URL = redirectURL; - [self updateMutableRequest:mutableRequest]; - // Avoid assuming the session is still valid. - self.session = nil; - forceAssumeRetry = YES; - } - } - } - - // If invalidating the session was deferred in stopFetchReleasingCallbacks: then do it now. - NSURLSession *oldSession = self.sessionNeedingInvalidation; - if (oldSession) { - [self setSessionNeedingInvalidation:NULL]; - [oldSession finishTasksAndInvalidate]; - } - - // Failed. - [self shouldRetryNowForStatus:status - error:error - forceAssumeRetry:forceAssumeRetry - response:^(BOOL shouldRetry) { - [self finishWithError:error shouldRetry:shouldRetry]; - }]; -} - -#if TARGET_OS_IPHONE -- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSessionDidFinishEventsForBackgroundURLSession:%@", - [self class], self, session); - [self removePersistedBackgroundSessionFromDefaults]; - - GTMSessionFetcherSystemCompletionHandler handler; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - handler = self.systemCompletionHandler; - self.systemCompletionHandler = nil; - } // @synchronized(self) - if (handler) { - GTM_LOG_BACKGROUND_SESSION(@"%@ %p Calling system completionHandler", [self class], self); - handler(); - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSURLSession *oldSession = _session; - _session = nil; - if (_shouldInvalidateSession) { - [oldSession finishTasksAndInvalidate]; - } - } // @synchronized(self) - } -} -#endif - -- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(GTM_NULLABLE NSError *)error { - // This may happen repeatedly for retries. On authentication callbacks, the retry - // may begin before the prior session sends the didBecomeInvalid delegate message. - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", - [self class], self, session, error); - if (session == (NSURLSession *)self.session) { - GTM_LOG_SESSION_DELEGATE(@" Unexpected retained invalid session: %@", session); - self.session = nil; - } -} - -- (void)finishWithError:(GTM_NULLABLE NSError *)error shouldRetry:(BOOL)shouldRetry { - [self removePersistedBackgroundSessionFromDefaults]; - - BOOL shouldStopFetching = YES; - NSData *downloadedData = nil; -#if !STRIP_GTM_FETCH_LOGGING - BOOL shouldDeferLogging = NO; -#endif - BOOL shouldBeginRetryTimer = NO; - NSInteger status = [self statusCode]; - NSURL *destinationURL = self.destinationFileURL; - - BOOL fetchSucceeded = (error == nil && status >= 0 && status < 300); - -#if !STRIP_GTM_FETCH_LOGGING - if (!fetchSucceeded) { - if (!shouldDeferLogging && !self.hasLoggedError) { - [self logNowWithError:error]; - self.hasLoggedError = YES; - } - } -#endif // !STRIP_GTM_FETCH_LOGGING - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - -#if !STRIP_GTM_FETCH_LOGGING - shouldDeferLogging = _deferResponseBodyLogging; -#endif - if (fetchSucceeded) { - // Success - if ((_downloadedData.length > 0) && (destinationURL != nil)) { - // Overwrite any previous file at the destination URL. - NSFileManager *fileMgr = [NSFileManager defaultManager]; - [fileMgr removeItemAtURL:destinationURL - error:NULL]; - NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; - BOOL didMoveDownload = NO; - if ([fileMgr createDirectoryAtURL:destinationFolderURL - withIntermediateDirectories:YES - attributes:nil - error:&error]) { - didMoveDownload = [_downloadedData writeToURL:destinationURL - options:NSDataWritingAtomic - error:&error]; - } - if (didMoveDownload) { - _downloadedData = nil; - } else { - _downloadFinishedError = error; - } - } - downloadedData = _downloadedData; - } else { - // Unsuccessful with error or status over 300. Retry or notify the delegate of failure - if (shouldRetry) { - // Retrying. - shouldBeginRetryTimer = YES; - shouldStopFetching = NO; - } else { - if (error == nil) { - // Create an error. - NSDictionary *userInfo = GTMErrorUserInfoForData( - _downloadedData.length > 0 ? _downloadedData : _downloadTaskErrorData, - [self responseHeadersUnsynchronized]); - - error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain - code:status - userInfo:userInfo]; - } else { - // If the error had resume data, and the client supplied a resume block, pass the - // data to the client. - void (^resumeBlock)(NSData *) = _resumeDataBlock; - _resumeDataBlock = nil; - if (resumeBlock) { - NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]; - if (resumeData) { - [self invokeOnCallbackQueueAfterUserStopped:YES block:^{ - resumeBlock(resumeData); - }]; - } - } - } - if (_downloadedData.length > 0) { - downloadedData = _downloadedData; - } - // If the error occurred after retries, report the number and duration of the - // retries. This provides a clue to a developer looking at the error description - // that the fetcher did retry before failing with this error. - if (_retryCount > 0) { - NSMutableDictionary *userInfoWithRetries = - [NSMutableDictionary dictionaryWithDictionary:(NSDictionary *)error.userInfo]; - NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; - [userInfoWithRetries setObject:@(timeSinceInitialRequest) - forKey:kGTMSessionFetcherElapsedIntervalWithRetriesKey]; - [userInfoWithRetries setObject:@(_retryCount) - forKey:kGTMSessionFetcherNumberOfRetriesDoneKey]; - error = [NSError errorWithDomain:(NSString *)error.domain - code:error.code - userInfo:userInfoWithRetries]; - } - } - } - } // @synchronized(self) - - if (shouldBeginRetryTimer) { - [self beginRetryTimer]; - } - - // 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]; - - if (shouldStopFetching) { - [self invokeFetchCallbacksOnCallbackQueueWithData:downloadedData - error:error]; - // The upload subclass doesn't want to release callbacks until upload chunks have completed. - BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion]; - [self stopFetchReleasingCallbacks:shouldRelease]; - } - -#if !STRIP_GTM_FETCH_LOGGING - // _hasLoggedError is only set by this method - if (!shouldDeferLogging && !_hasLoggedError) { - [self logNowWithError:error]; - } -#endif -} - -- (BOOL)shouldReleaseCallbacksUponCompletion { - // A subclass can override this to keep callbacks around after the - // connection has finished successfully - return YES; -} - -- (void)logNowWithError:(GTM_NULLABLE NSError *)error { - GTMSessionCheckNotSynchronized(self); - - // 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 { - __unsafe_unretained NSString *const domain; - NSInteger code; - }; - - struct RetryRecord retries[] = { - { kGTMSessionFetcherStatusDomain, 408 }, // request timeout - { kGTMSessionFetcherStatusDomain, 502 }, // failure gatewaying to another server - { kGTMSessionFetcherStatusDomain, 503 }, // service unavailable - { kGTMSessionFetcherStatusDomain, 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 - NSString *domain = error.domain; - NSInteger code = error.code; - for (int idx = 0; retries[idx].domain != nil; idx++) { - if (code == retries[idx].code && [domain isEqual:retries[idx].domain]) { - return YES; - } - } - return NO; -} - -// shouldRetryNowForStatus:error: responds with 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 retry block is present and returns YES when called, or the -// authorizer may be able to fix. -- (void)shouldRetryNowForStatus:(NSInteger)status - error:(NSError *)error - forceAssumeRetry:(BOOL)forceAssumeRetry - response:(GTMSessionFetcherRetryResponse)response { - // Determine if a refreshed authorizer may avoid an authorization error - BOOL willRetry = NO; - - // We assume _authorizer is immutable after beginFetch, and _hasAttemptedAuthRefresh is modified - // only in this method, and this method is invoked on the serial delegate queue. - // - // We want to avoid calling the authorizer from inside a sync block. - BOOL isFirstAuthError = (_authorizer != nil - && !_hasAttemptedAuthRefresh - && status == GTMSessionFetcherStatusUnauthorized); // 401 - - BOOL hasPrimed = NO; - if (isFirstAuthError) { - if ([_authorizer respondsToSelector:@selector(primeForRefresh)]) { - hasPrimed = [_authorizer primeForRefresh]; - } - } - - BOOL shouldRetryForAuthRefresh = NO; - if (hasPrimed) { - shouldRetryForAuthRefresh = YES; - _hasAttemptedAuthRefresh = YES; - [self updateRequestValue:nil forHTTPHeaderField:@"Authorization"]; - } - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - BOOL shouldDoRetry = [self isRetryEnabledUnsynchronized]; - if (shouldDoRetry && ![self hasRetryAfterInterval]) { - - // Determine if we're doing exponential backoff retries - shouldDoRetry = [self nextRetryIntervalUnsynchronized] < _maxRetryInterval; - - if (shouldDoRetry) { - // If an explicit max retry interval was set, we expect repeated backoffs to take - // up to roughly twice that for repeated fast failures. If the initial attempt is - // already more than 3 times the max retry interval, then failures have taken a long time - // (such as from network timeouts) so don't retry again to avoid the app becoming - // unexpectedly unresponsive. - if (_maxRetryInterval > 0) { - NSTimeInterval maxAllowedIntervalBeforeRetry = _maxRetryInterval * 3; - NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; - if (timeSinceInitialRequest > maxAllowedIntervalBeforeRetry) { - shouldDoRetry = NO; - } - } - } - } - BOOL canRetry = shouldRetryForAuthRefresh || forceAssumeRetry || shouldDoRetry; - if (canRetry) { - NSDictionary *userInfo = - GTMErrorUserInfoForData(_downloadedData, [self responseHeadersUnsynchronized]); - NSError *statusError = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain - code:status - userInfo:userInfo]; - if (error == nil) { - error = statusError; - } - willRetry = shouldRetryForAuthRefresh || - forceAssumeRetry || - [self isRetryError:error] || - ((error != statusError) && [self isRetryError:statusError]); - - // If the user has installed a retry callback, consult that. - GTMSessionFetcherRetryBlock retryBlock = _retryBlock; - if (retryBlock) { - [self invokeOnCallbackQueueUnlessStopped:^{ - retryBlock(willRetry, error, response); - }]; - return; - } - } - } // @synchronized(self) - response(willRetry); -} - -- (BOOL)hasRetryAfterInterval { - GTMSessionCheckSynchronized(self); - - NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; - NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; - return (retryAfterValue != nil); -} - -- (NSTimeInterval)retryAfterInterval { - GTMSessionCheckSynchronized(self); - - NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; - NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; - if (retryAfterValue == nil) { - return 0; - } - // Retry-After formatted as HTTP-date | delta-seconds - // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - NSDateFormatter *rfc1123DateFormatter = [[NSDateFormatter alloc] init]; - rfc1123DateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - rfc1123DateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; - rfc1123DateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z"; - NSDate *retryAfterDate = [rfc1123DateFormatter dateFromString:retryAfterValue]; - NSTimeInterval retryAfterInterval = (retryAfterDate != nil) ? - retryAfterDate.timeIntervalSinceNow : retryAfterValue.intValue; - retryAfterInterval = MAX(0, retryAfterInterval); - return retryAfterInterval; -} - -- (void)beginRetryTimer { - if (![NSThread isMainThread]) { - // Defer creating and starting the timer until we're on the main thread to ensure it has - // a run loop. - dispatch_group_async(_callbackGroup, dispatch_get_main_queue(), ^{ - [self beginRetryTimer]; - }); - return; - } - - [self destroyRetryTimer]; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSTimeInterval nextInterval = [self nextRetryIntervalUnsynchronized]; - NSTimeInterval maxInterval = _maxRetryInterval; - NSTimeInterval newInterval = MIN(nextInterval, (maxInterval > 0 ? maxInterval : DBL_MAX)); - NSTimeInterval newIntervalTolerance = (newInterval / 10) > 1.0 ?: 1.0; - - _lastRetryInterval = newInterval; - - _retryTimer = [NSTimer timerWithTimeInterval:newInterval - target:self - selector:@selector(retryTimerFired:) - userInfo:nil - repeats:NO]; - _retryTimer.tolerance = newIntervalTolerance; - [[NSRunLoop mainRunLoop] addTimer:_retryTimer - forMode:NSDefaultRunLoopMode]; - } // @synchronized(self) - - [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStartedNotification - userInfo:nil - requireAsync:NO]; -} - -- (void)retryTimerFired:(NSTimer *)timer { - [self destroyRetryTimer]; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _retryCount++; - } // @synchronized(self) - - NSOperationQueue *queue = self.sessionDelegateQueue; - [queue addOperationWithBlock:^{ - [self retryFetch]; - }]; -} - -- (void)destroyRetryTimer { - BOOL shouldNotify = NO; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_retryTimer) { - [_retryTimer invalidate]; - _retryTimer = nil; - shouldNotify = YES; - } - } - - if (shouldNotify) { - [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStoppedNotification - userInfo:nil - requireAsync:NO]; - } -} - -- (NSUInteger)retryCount { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _retryCount; - } // @synchronized(self) -} - -- (NSTimeInterval)nextRetryInterval { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSTimeInterval interval = [self nextRetryIntervalUnsynchronized]; - return interval; - } // @synchronized(self) -} - -- (NSTimeInterval)nextRetryIntervalUnsynchronized { - GTMSessionCheckSynchronized(self); - - NSInteger statusCode = [self statusCodeUnsynchronized]; - if ((statusCode == 503) && [self hasRetryAfterInterval]) { - NSTimeInterval secs = [self retryAfterInterval]; - return secs; - } - // The next wait interval is the factor (2.0) times the last interval, - // but never less than the minimum interval. - NSTimeInterval secs = _lastRetryInterval * _retryFactor; - if (_maxRetryInterval > 0) { - secs = MIN(secs, _maxRetryInterval); - } - secs = MAX(secs, _minRetryInterval); - - return secs; -} - -- (NSTimer *)retryTimer { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _retryTimer; - } // @synchronized(self) -} - -- (BOOL)isRetryEnabled { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _isRetryEnabled; - } // @synchronized(self) -} - -- (BOOL)isRetryEnabledUnsynchronized { - GTMSessionCheckSynchronized(self); - - return _isRetryEnabled; -} - -- (void)setRetryEnabled:(BOOL)flag { - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - 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 - _minRetryInterval = InitialMinRetryInterval(); - _maxRetryInterval = kUnsetMaxRetryInterval; - _retryFactor = 2.0; - _lastRetryInterval = 0.0; - } - _isRetryEnabled = flag; - } // @synchronized(self) -}; - -- (NSTimeInterval)maxRetryInterval { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _maxRetryInterval; - } // @synchronized(self) -} - -- (void)setMaxRetryInterval:(NSTimeInterval)secs { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (secs > 0) { - _maxRetryInterval = secs; - } else { - _maxRetryInterval = kUnsetMaxRetryInterval; - } - } // @synchronized(self) -} - -- (double)minRetryInterval { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _minRetryInterval; - } // @synchronized(self) -} - -- (void)setMinRetryInterval:(NSTimeInterval)secs { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - 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 = InitialMinRetryInterval(); - } - } // @synchronized(self) - -} - -#pragma mark iOS System Completion Handlers - -#if TARGET_OS_IPHONE -static NSMutableDictionary *gSystemCompletionHandlers = nil; - -- (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler { - return [[self class] systemCompletionHandlerForSessionIdentifier:_sessionIdentifier]; -} - -- (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler { - [[self class] setSystemCompletionHandler:systemCompletionHandler - forSessionIdentifier:_sessionIdentifier]; -} - -+ (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler - forSessionIdentifier:(NSString *)sessionIdentifier { - if (!sessionIdentifier) { - NSLog(@"%s with nil identifier", __PRETTY_FUNCTION__); - return; - } - - @synchronized([GTMSessionFetcher class]) { - if (gSystemCompletionHandlers == nil && systemCompletionHandler != nil) { - gSystemCompletionHandlers = [[NSMutableDictionary alloc] init]; - } - // Use setValue: to remove the object if completionHandler is nil. - [gSystemCompletionHandlers setValue:systemCompletionHandler - forKey:sessionIdentifier]; - } -} - -+ (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandlerForSessionIdentifier:(NSString *)sessionIdentifier { - if (!sessionIdentifier) { - return nil; - } - @synchronized([GTMSessionFetcher class]) { - return [gSystemCompletionHandlers objectForKey:sessionIdentifier]; - } -} -#endif // TARGET_OS_IPHONE - -#pragma mark Getters and Setters - -@synthesize downloadResumeData = _downloadResumeData, - configuration = _configuration, - configurationBlock = _configurationBlock, - sessionTask = _sessionTask, - wasCreatedFromBackgroundSession = _wasCreatedFromBackgroundSession, - sessionUserInfo = _sessionUserInfo, - taskDescription = _taskDescription, - taskPriority = _taskPriority, - usingBackgroundSession = _usingBackgroundSession, - canShareSession = _canShareSession, - completionHandler = _completionHandler, - credential = _credential, - proxyCredential = _proxyCredential, - bodyData = _bodyData, - bodyLength = _bodyLength, - service = _service, - serviceHost = _serviceHost, - accumulateDataBlock = _accumulateDataBlock, - receivedProgressBlock = _receivedProgressBlock, - downloadProgressBlock = _downloadProgressBlock, - resumeDataBlock = _resumeDataBlock, - didReceiveResponseBlock = _didReceiveResponseBlock, - challengeBlock = _challengeBlock, - willRedirectBlock = _willRedirectBlock, - sendProgressBlock = _sendProgressBlock, - willCacheURLResponseBlock = _willCacheURLResponseBlock, - retryBlock = _retryBlock, - retryFactor = _retryFactor, - allowedInsecureSchemes = _allowedInsecureSchemes, - allowLocalhostRequest = _allowLocalhostRequest, - allowInvalidServerCertificates = _allowInvalidServerCertificates, - cookieStorage = _cookieStorage, - callbackQueue = _callbackQueue, - initialBeginFetchDate = _initialBeginFetchDate, - testBlock = _testBlock, - testBlockAccumulateDataChunkCount = _testBlockAccumulateDataChunkCount, - comment = _comment, - log = _log; - -#if !STRIP_GTM_FETCH_LOGGING -@synthesize redirectedFromURL = _redirectedFromURL, - logRequestBody = _logRequestBody, - logResponseBody = _logResponseBody, - hasLoggedError = _hasLoggedError; -#endif - -#if GTM_BACKGROUND_TASK_FETCHING -@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier, - skipBackgroundTask = _skipBackgroundTask; -#endif - -- (GTM_NULLABLE NSURLRequest *)request { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_request copy]; - } // @synchronized(self) -} - -- (void)setRequest:(GTM_NULLABLE NSURLRequest *)request { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (![self isFetchingUnsynchronized]) { - _request = [request mutableCopy]; - } else { - GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked"); - } - } // @synchronized(self) -} - -- (GTM_NULLABLE NSMutableURLRequest *)mutableRequestForTesting { - // Allow tests only to modify the request, useful during retries. - return _request; -} - -// Internal method for updating the request property such as on redirects. -- (void)updateMutableRequest:(GTM_NULLABLE NSMutableURLRequest *)request { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _request = request; - } // @synchronized(self) -} - -// Set a header field value on the request. Header field value changes will not -// affect a fetch after the fetch has begun. -- (void)setRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field { - if (![self isFetching]) { - [self updateRequestValue:value forHTTPHeaderField:field]; - } else { - GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked"); - } -} - -// Internal method for updating request headers. -- (void)updateRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [_request setValue:value forHTTPHeaderField:field]; - } // @synchronized(self) -} - -- (void)setResponse:(GTM_NULLABLE NSURLResponse *)response { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _response = response; - } // @synchronized(self) -} - -- (int64_t)bodyLength { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_bodyLength == NSURLSessionTransferSizeUnknown) { - if (_bodyData) { - _bodyLength = (int64_t)_bodyData.length; - } else if (_bodyFileURL) { - NSNumber *fileSizeNum = nil; - NSError *fileSizeError = nil; - if ([_bodyFileURL getResourceValue:&fileSizeNum - forKey:NSURLFileSizeKey - error:&fileSizeError]) { - _bodyLength = [fileSizeNum longLongValue]; - } - } - } - return _bodyLength; - } // @synchronized(self) -} - -- (BOOL)useUploadTask { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _useUploadTask; - } // @synchronized(self) -} - -- (void)setUseUploadTask:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (flag != _useUploadTask) { - GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], - @"useUploadTask should not change after beginFetch has been invoked"); - _useUploadTask = flag; - } - } // @synchronized(self) -} - -- (GTM_NULLABLE NSURL *)bodyFileURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _bodyFileURL; - } // @synchronized(self) -} - -- (void)setBodyFileURL:(GTM_NULLABLE NSURL *)fileURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // The comparison here is a trivial optimization and forgiveness for any client that - // repeatedly sets the property, so it just uses pointer comparison rather than isEqual:. - if (fileURL != _bodyFileURL) { - GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], - @"fileURL should not change after beginFetch has been invoked"); - - _bodyFileURL = fileURL; - } - } // @synchronized(self) -} - -- (GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)bodyStreamProvider { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _bodyStreamProvider; - } // @synchronized(self) -} - -- (void)setBodyStreamProvider:(GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)block { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], - @"stream provider should not change after beginFetch has been invoked"); - - _bodyStreamProvider = [block copy]; - } // @synchronized(self) -} - -- (GTM_NULLABLE id<GTMFetcherAuthorizationProtocol>)authorizer { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _authorizer; - } // @synchronized(self) -} - -- (void)setAuthorizer:(GTM_NULLABLE id<GTMFetcherAuthorizationProtocol>)authorizer { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (authorizer != _authorizer) { - if ([self isFetchingUnsynchronized]) { - GTMSESSION_ASSERT_DEBUG(0, @"authorizer should not change after beginFetch has been invoked"); - } else { - _authorizer = authorizer; - } - } - } // @synchronized(self) -} - -- (GTM_NULLABLE NSData *)downloadedData { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _downloadedData; - } // @synchronized(self) -} - -- (void)setDownloadedData:(GTM_NULLABLE NSData *)data { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _downloadedData = [data mutableCopy]; - } // @synchronized(self) -} - -- (int64_t)downloadedLength { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _downloadedLength; - } // @synchronized(self) -} - -- (void)setDownloadedLength:(int64_t)length { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _downloadedLength = length; - } // @synchronized(self) -} - -- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _callbackQueue; - } // @synchronized(self) -} - -- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _callbackQueue = queue ?: dispatch_get_main_queue(); - } // @synchronized(self) -} - -- (GTM_NULLABLE NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _session; - } // @synchronized(self) -} - -- (NSInteger)servicePriority { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _servicePriority; - } // @synchronized(self) -} - -- (void)setServicePriority:(NSInteger)value { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (value != _servicePriority) { - GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], - @"servicePriority should not change after beginFetch has been invoked"); - - _servicePriority = value; - } - } // @synchronized(self) -} - - -- (void)setSession:(GTM_NULLABLE NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _session = session; - } // @synchronized(self) -} - -- (BOOL)canShareSession { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _canShareSession; - } // @synchronized(self) -} - -- (void)setCanShareSession:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _canShareSession = flag; - } // @synchronized(self) -} - -- (BOOL)useBackgroundSession { - // This reflects if the user requested a background session, not necessarily - // if one was created. That is tracked with _usingBackgroundSession. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _userRequestedBackgroundSession; - } // @synchronized(self) -} - -- (void)setUseBackgroundSession:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (flag != _userRequestedBackgroundSession) { - GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], - @"useBackgroundSession should not change after beginFetch has been invoked"); - - _userRequestedBackgroundSession = flag; - } - } // @synchronized(self) -} - -- (BOOL)isUsingBackgroundSession { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _usingBackgroundSession; - } // @synchronized(self) -} - -- (void)setUsingBackgroundSession:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _usingBackgroundSession = flag; - } // @synchronized(self) -} - -- (GTM_NULLABLE NSURLSession *)sessionNeedingInvalidation { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _sessionNeedingInvalidation; - } // @synchronized(self) -} - -- (void)setSessionNeedingInvalidation:(GTM_NULLABLE NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _sessionNeedingInvalidation = session; - } // @synchronized(self) -} - -- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateQueue; - } // @synchronized(self) -} - -- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (queue != _delegateQueue) { - if ([self isFetchingUnsynchronized]) { - GTMSESSION_ASSERT_DEBUG(0, @"sessionDelegateQueue should not change after fetch begins"); - } else { - _delegateQueue = queue ?: [NSOperationQueue mainQueue]; - } - } - } // @synchronized(self) -} - -- (BOOL)userStoppedFetching { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _userStoppedFetching; - } // @synchronized(self) -} - -- (GTM_NULLABLE id)userData { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _userData; - } // @synchronized(self) -} - -- (void)setUserData:(GTM_NULLABLE id)theObj { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _userData = theObj; - } // @synchronized(self) -} - -- (GTM_NULLABLE NSURL *)destinationFileURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _destinationFileURL; - } // @synchronized(self) -} - -- (void)setDestinationFileURL:(GTM_NULLABLE NSURL *)destinationFileURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (((_destinationFileURL == nil) && (destinationFileURL == nil)) || - [_destinationFileURL isEqual:destinationFileURL]) { - return; - } - if (_sessionIdentifier) { - // This is something we don't expect to happen in production. - // However if it ever happen, leave a system log. - NSLog(@"%@: Destination File URL changed from (%@) to (%@) after session identifier has " - @"been created.", - [self class], _destinationFileURL, destinationFileURL); -#if DEBUG - // On both the simulator and devices, the path can change to the download file, but the name - // shouldn't change. Technically, this isn't supported in the fetcher, but the change of - // URL is expected to happen only across development runs through Xcode. - NSString *oldFilename = [_destinationFileURL lastPathComponent]; - NSString *newFilename = [destinationFileURL lastPathComponent]; - #pragma unused(oldFilename) - #pragma unused(newFilename) - GTMSESSION_ASSERT_DEBUG([oldFilename isEqualToString:newFilename], - @"Destination File URL cannot be changed after session identifier has been created"); -#endif - } - _destinationFileURL = destinationFileURL; - } // @synchronized(self) -} - -- (void)setProperties:(GTM_NULLABLE NSDictionary *)dict { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _properties = [dict mutableCopy]; - } // @synchronized(self) -} - -- (GTM_NULLABLE NSDictionary *)properties { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _properties; - } // @synchronized(self) -} - -- (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_properties == nil && obj != nil) { - _properties = [[NSMutableDictionary alloc] init]; - } - [_properties setValue:obj forKey:key]; - } // @synchronized(self) -} - -- (GTM_NULLABLE id)propertyForKey:(NSString *)key { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_properties objectForKey:key]; - } // @synchronized(self) -} - -- (void)addPropertiesFromDictionary:(NSDictionary *)dict { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_properties == nil && dict != nil) { - [self setProperties:[dict mutableCopy]]; - } else { - [_properties addEntriesFromDictionary:dict]; - } - } // @synchronized(self) -} - -- (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]; - va_end(argList); - } - [self setComment:result]; -#endif -} - -#if !STRIP_GTM_FETCH_LOGGING -- (NSData *)loggedStreamData { - return _loggedStreamData; -} - -- (void)appendLoggedStreamData:dataToAdd { - if (!_loggedStreamData) { - _loggedStreamData = [NSMutableData data]; - } - [_loggedStreamData appendData:dataToAdd]; -} - -- (void)clearLoggedStreamData { - _loggedStreamData = nil; -} - -- (void)setDeferResponseBodyLogging:(BOOL)deferResponseBodyLogging { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (deferResponseBodyLogging != _deferResponseBodyLogging) { - _deferResponseBodyLogging = deferResponseBodyLogging; - if (!deferResponseBodyLogging && !self.hasLoggedError) { - [_delegateQueue addOperationWithBlock:^{ - [self logNowWithError:nil]; - }]; - } - } - } // @synchronized(self) -} - -- (BOOL)deferResponseBodyLogging { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _deferResponseBodyLogging; - } // @synchronized(self) -} - -#else -+ (void)setLoggingEnabled:(BOOL)flag { -} - -+ (BOOL)isLoggingEnabled { - return NO; -} -#endif // STRIP_GTM_FETCH_LOGGING - -@end - -@implementation GTMSessionFetcher (BackwardsCompatibilityOnly) - -- (void)setCookieStorageMethod:(NSInteger)method { - // For backwards compatibility with the old fetcher, we'll support the old constants. - // - // Clients using the GTMSessionFetcher class should set the cookie storage explicitly - // themselves. - NSHTTPCookieStorage *storage = nil; - switch(method) { - case 0: // kGTMHTTPFetcherCookieStorageMethodStatic - // nil storage will use [[self class] staticCookieStorage] when the fetch begins. - break; - case 1: // kGTMHTTPFetcherCookieStorageMethodFetchHistory - // Do nothing; use whatever was set by the fetcher service. - return; - case 2: // kGTMHTTPFetcherCookieStorageMethodSystemDefault - storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - break; - case 3: // kGTMHTTPFetcherCookieStorageMethodNone - // Create temporary storage for this fetcher only. - storage = [[GTMSessionCookieStorage alloc] init]; - break; - default: - GTMSESSION_ASSERT_DEBUG(0, @"Invalid cookie storage method: %d", (int)method); - } - self.cookieStorage = storage; -} - -@end - -@implementation GTMSessionCookieStorage { - NSMutableArray *_cookies; - NSHTTPCookieAcceptPolicy _policy; -} - -- (id)init { - self = [super init]; - if (self != nil) { - _cookies = [[NSMutableArray alloc] init]; - } - return self; -} - -- (GTM_NULLABLE NSArray *)cookies { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_cookies copy]; - } // @synchronized(self) -} - -- (void)setCookie:(NSHTTPCookie *)cookie { - if (!cookie) return; - if (_policy == NSHTTPCookieAcceptPolicyNever) return; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self internalSetCookie:cookie]; - } // @synchronized(self) -} - -// Note: this should only be called from inside a @synchronized(self) block. -- (void)internalSetCookie:(NSHTTPCookie *)newCookie { - GTMSessionCheckSynchronized(self); - - if (_policy == NSHTTPCookieAcceptPolicyNever) return; - - BOOL isValidCookie = (newCookie.name.length > 0 - && newCookie.domain.length > 0 - && newCookie.path.length > 0); - GTMSESSION_ASSERT_DEBUG(isValidCookie, @"invalid cookie: %@", newCookie); - - if (isValidCookie) { - // Remove the cookie if it's currently in the array. - NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie]; - if (oldCookie) { - [_cookies removeObjectIdenticalTo:oldCookie]; - } - - if (![[self class] hasCookieExpired:newCookie]) { - [_cookies addObject:newCookie]; - } - } -} - -// 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:(GTM_NULLABLE NSArray *)newCookies { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self removeExpiredCookies]; - - for (NSHTTPCookie *newCookie in newCookies) { - [self internalSetCookie:newCookie]; - } - } // @synchronized(self) -} - -- (void)setCookies:(NSArray *)cookies forURL:(GTM_NULLABLE NSURL *)URL mainDocumentURL:(GTM_NULLABLE NSURL *)mainDocumentURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_policy == NSHTTPCookieAcceptPolicyNever) { - return; - } - - if (_policy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain) { - NSString *mainHost = mainDocumentURL.host; - NSString *associatedHost = URL.host; - if (!mainHost || ![associatedHost hasSuffix:mainHost]) { - return; - } - } - } // @synchronized(self) - [self setCookies:cookies]; -} - -- (void)deleteCookie:(NSHTTPCookie *)cookie { - if (!cookie) return; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie]; - if (foundCookie) { - [_cookies removeObjectIdenticalTo:foundCookie]; - } - } // @synchronized(self) -} - -// 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. -- (GTM_NULLABLE NSArray *)cookiesForURL:(NSURL *)theURL { - NSMutableArray *foundCookies = nil; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [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 *requestingDomain = nil; - BOOL isLocalhostRetrieval = NO; - - if (IsLocalhost(host)) { - isLocalhostRetrieval = YES; - } else { - if (host.length > 0) { - requestingDomain = [@"." stringByAppendingString:host]; - } - } - - for (NSHTTPCookie *storedCookie in _cookies) { - 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 = (IsLocalhost(cookieDomain) - || [cookieDomain isEqual:@"localhost.local"]); - } else { - // Ensure we're matching exact domain names. We prepended a dot to the - // requesting domain, so we can also prepend one here if needed before - // checking if the request contains the cookie domain. - if (![cookieDomain hasPrefix:@"."]) { - cookieDomain = [@"." stringByAppendingString:cookieDomain]; - } - isDomainOK = [requestingDomain hasSuffix:cookieDomain]; - } - - BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath]; - BOOL isSecureOK = (!cookieIsSecure - || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame); - - if (isDomainOK && isPathOK && isSecureOK) { - if (foundCookies == nil) { - foundCookies = [NSMutableArray array]; - } - [foundCookies addObject:storedCookie]; - } - } - } // @synchronized(self) - return foundCookies; -} - -// Override methods from the NSHTTPCookieStorage (NSURLSessionTaskAdditions) category. -- (void)storeCookies:(NSArray *)cookies forTask:(NSURLSessionTask *)task { - NSURLRequest *currentRequest = task.currentRequest; - [self setCookies:cookies forURL:currentRequest.URL mainDocumentURL:nil]; -} - -- (void)getCookiesForTask:(NSURLSessionTask *)task - completionHandler:(void (^)(GTM_NSArrayOf(NSHTTPCookie *) *))completionHandler { - if (completionHandler) { - NSURLRequest *currentRequest = task.currentRequest; - NSURL *currentRequestURL = currentRequest.URL; - NSArray *cookies = [self cookiesForURL:currentRequestURL]; - completionHandler(cookies); - } -} - -// Return a cookie from the array with the same name, domain, and path as the -// given cookie, or else return nil if none found. -// -// Both the cookie being tested and all cookies in the storage array should -// be valid (non-nil name, domains, paths). -// -// Note: this should only be called from inside a @synchronized(self) block -- (GTM_NULLABLE NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie { - GTMSessionCheckSynchronized(self); - - NSString *name = cookie.name; - NSString *domain = cookie.domain; - NSString *path = cookie.path; - - GTMSESSION_ASSERT_DEBUG(name && domain && path, - @"Invalid stored cookie (name:%@ domain:%@ path:%@)", name, domain, path); - - for (NSHTTPCookie *storedCookie in _cookies) { - 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(self) block -- (void)removeExpiredCookies { - GTMSessionCheckSynchronized(self); - - // Count backwards since we're deleting items from the array - for (NSInteger idx = (NSInteger)_cookies.count - 1; idx >= 0; idx--) { - NSHTTPCookie *storedCookie = [_cookies objectAtIndex:(NSUInteger)idx]; - if ([[self class] hasCookieExpired:storedCookie]) { - [_cookies removeObjectAtIndex:(NSUInteger)idx]; - } - } -} - -+ (BOOL)hasCookieExpired:(NSHTTPCookie *)cookie { - NSDate *expiresDate = [cookie expiresDate]; - if (expiresDate == nil) { - // Cookies seem to have a Expires property even when the expiresDate method returns nil. - id expiresVal = [[cookie properties] objectForKey:NSHTTPCookieExpires]; - if ([expiresVal isKindOfClass:[NSDate class]]) { - expiresDate = expiresVal; - } - } - BOOL hasExpired = (expiresDate != nil && [expiresDate timeIntervalSinceNow] < 0); - return hasExpired; -} - -- (void)removeAllCookies { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [_cookies removeAllObjects]; - } // @synchronized(self) -} - -- (NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _policy; - } // @synchronized(self) -} - -- (void)setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _policy = cookieAcceptPolicy; - } // @synchronized(self) -} - -@end - -void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE 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([(id)obj class]), - NSStringFromSelector((SEL)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([(id)obj class]), - NSStringFromSelector((SEL)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([(id)obj class]), - NSStringFromSelector((SEL)sel), (argCount - 2)); - NSCAssert(0, @"callback selector arguments incorrect"); - } - } - } - - va_end(argList); -#endif -} - -NSString *GTMFetcherCleanedUserAgentString(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 @""; - - NSMutableString *result = [NSMutableString stringWithString:str]; - - // Replace spaces and commas with underscores - [result replaceOccurrencesOfString:@" " - withString:@"_" - options:0 - range:NSMakeRange(0, result.length)]; - [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 = - [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy]; - [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 *GTMFetcherSystemVersionString(void) { - static NSString *sSavedSystemString; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // The Xcode 8 SDKs finally cleaned up this mess by providing TARGET_OS_OSX - // and TARGET_OS_IOS, but to build with older SDKs, those don't exist and - // instead one has to rely on TARGET_OS_MAC (which is true for iOS, watchOS, - // and tvOS) and TARGET_OS_IPHONE (which is true for iOS, watchOS, tvOS). So - // one has to order these carefully so you pick off the specific things - // first. - // If the code can ever assume Xcode 8 or higher (even when building for - // older OSes), then - // TARGET_OS_MAC -> TARGET_OS_OSX - // TARGET_OS_IPHONE -> TARGET_OS_IOS - // TARGET_IPHONE_SIMULATOR -> TARGET_OS_SIMULATOR -#if TARGET_OS_WATCH - // watchOS - WKInterfaceDevice - - WKInterfaceDevice *currentDevice = [WKInterfaceDevice currentDevice]; - - NSString *rawModel = [currentDevice model]; - NSString *model = GTMFetcherCleanedUserAgentString(rawModel); - - NSString *systemVersion = [currentDevice systemVersion]; - -#if TARGET_OS_SIMULATOR - NSString *hardwareModel = @"sim"; -#else - NSString *hardwareModel; - struct utsname unameRecord; - if (uname(&unameRecord) == 0) { - NSString *machineName = @(unameRecord.machine); - hardwareModel = GTMFetcherCleanedUserAgentString(machineName); - } - if (hardwareModel.length == 0) { - hardwareModel = @"unk"; - } -#endif - - sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", - model, systemVersion, hardwareModel]; - // Example: Apple_Watch/3.0 hw/Watch1_2 -#elif TARGET_OS_TV || TARGET_OS_IPHONE - // iOS and tvOS have UIDevice, use that. - UIDevice *currentDevice = [UIDevice currentDevice]; - - NSString *rawModel = [currentDevice model]; - NSString *model = GTMFetcherCleanedUserAgentString(rawModel); - - NSString *systemVersion = [currentDevice systemVersion]; - -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR - NSString *hardwareModel = @"sim"; -#else - NSString *hardwareModel; - struct utsname unameRecord; - if (uname(&unameRecord) == 0) { - NSString *machineName = @(unameRecord.machine); - hardwareModel = GTMFetcherCleanedUserAgentString(machineName); - } - if (hardwareModel.length == 0) { - hardwareModel = @"unk"; - } -#endif - - sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", - model, systemVersion, hardwareModel]; - // Example: iPod_Touch/2.2 hw/iPod1_1 - // Example: Apple_TV/9.2 hw/AppleTV5,3 -#elif TARGET_OS_MAC - // Mac build - NSProcessInfo *procInfo = [NSProcessInfo processInfo]; -#if !defined(MAC_OS_X_VERSION_10_10) - BOOL hasOperatingSystemVersion = NO; -#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10 - BOOL hasOperatingSystemVersion = - [procInfo respondsToSelector:@selector(operatingSystemVersion)]; -#else - BOOL hasOperatingSystemVersion = YES; -#endif - NSString *versString; - if (hasOperatingSystemVersion) { -#if defined(MAC_OS_X_VERSION_10_10) - // A reference to NSOperatingSystemVersion requires the 10.10 SDK. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" -// Disable unguarded availability warning as we can't use the @availability macro until we require -// all clients to build with Xcode 9 or above. - NSOperatingSystemVersion version = procInfo.operatingSystemVersion; -#pragma clang diagnostic pop - versString = [NSString stringWithFormat:@"%ld.%ld.%ld", - (long)version.majorVersion, (long)version.minorVersion, - (long)version.patchVersion]; -#else -#pragma unused(procInfo) -#endif - } else { - // With Gestalt inexplicably deprecated in 10.8, we're reduced to reading - // the system plist file. - NSString *const kPath = @"/System/Library/CoreServices/SystemVersion.plist"; - NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:kPath]; - versString = [plist objectForKey:@"ProductVersion"]; - if (versString.length == 0) { - versString = @"10.?.?"; - } - } - - sSavedSystemString = [[NSString alloc] initWithFormat:@"MacOSX/%@", versString]; -#elif defined(_SYS_UTSNAME_H) - // Foundation-only build - struct utsname unameRecord; - uname(&unameRecord); - - sSavedSystemString = [NSString stringWithFormat:@"%s/%s", - unameRecord.sysname, unameRecord.release]; // "Darwin/8.11.1" -#else -#error No branch taken for a default user agent -#endif - }); - return sSavedSystemString; -} - -NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle) { - NSString *result = [NSString stringWithFormat:@"%@ %@", - GTMFetcherApplicationIdentifier(bundle), - GTMFetcherSystemVersionString()]; - return result; -} - -NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle) { - @synchronized([GTMSessionFetcher class]) { - static NSMutableDictionary *sAppIDMap = nil; - - // If there's a bundle ID, use that; otherwise, use the process name - if (bundle == nil) { - bundle = [NSBundle mainBundle]; - } - NSString *bundleID = [bundle bundleIdentifier]; - if (bundleID == nil) { - bundleID = @""; - } - - NSString *identifier = [sAppIDMap objectForKey:bundleID]; - if (identifier) return identifier; - - // Apps may add a string to the info.plist to uniquely identify different builds. - identifier = [bundle objectForInfoDictionaryKey:@"GTMUserAgentID"]; - if (identifier.length == 0) { - 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 = GTMFetcherCleanedUserAgentString(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 = GTMFetcherCleanedUserAgentString(version); - - // Glue the two together (cleanup done above or else cleanup would strip the - // slash) - if (version.length > 0) { - identifier = [identifier stringByAppendingFormat:@"/%@", version]; - } - - if (sAppIDMap == nil) { - sAppIDMap = [[NSMutableDictionary alloc] init]; - } - [sAppIDMap setObject:identifier forKey:bundleID]; - return identifier; - } -} - -#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) -@implementation GTMSessionSyncMonitorInternal { - NSValue *_objectKey; // The synchronize target object. - const char *_functionName; // The function containing the monitored sync block. -} - -- (instancetype)initWithSynchronizationObject:(id)object - allowRecursive:(BOOL)allowRecursive - functionName:(const char *)functionName { - self = [super init]; - if (self) { - Class threadKey = [GTMSessionSyncMonitorInternal class]; - _objectKey = [NSValue valueWithNonretainedObject:object]; - _functionName = functionName; - - NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; - NSMutableDictionary *counters = threadDict[threadKey]; - if (counters == nil) { - counters = [NSMutableDictionary dictionary]; - threadDict[(id)threadKey] = counters; - } - NSCountedSet *functionNamesCounter = counters[_objectKey]; - NSUInteger numberOfSyncingFunctions = functionNamesCounter.count; - - if (!allowRecursive) { - BOOL isTopLevelSyncScope = (numberOfSyncingFunctions == 0); - NSArray *stack = [NSThread callStackSymbols]; - GTMSESSION_ASSERT_DEBUG(isTopLevelSyncScope, - @"*** Recursive sync on %@ at %s; previous sync at %@\n%@", - [object class], functionName, functionNamesCounter.allObjects, - [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); - } - - if (!functionNamesCounter) { - functionNamesCounter = [NSCountedSet set]; - counters[_objectKey] = functionNamesCounter; - } - [functionNamesCounter addObject:(id _Nonnull)@(functionName)]; - } - return self; -} - -- (void)dealloc { - Class threadKey = [GTMSessionSyncMonitorInternal class]; - - NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; - NSMutableDictionary *counters = threadDict[threadKey]; - NSCountedSet *functionNamesCounter = counters[_objectKey]; - NSString *functionNameStr = @(_functionName); - NSUInteger numberOfSyncsByThisFunction = [functionNamesCounter countForObject:functionNameStr]; - NSArray *stack = [NSThread callStackSymbols]; - GTMSESSION_ASSERT_DEBUG(numberOfSyncsByThisFunction > 0, @"Sync not found on %@ at %s\n%@", - [_objectKey.nonretainedObjectValue class], _functionName, - [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); - [functionNamesCounter removeObject:functionNameStr]; - if (functionNamesCounter.count == 0) { - [counters removeObjectForKey:_objectKey]; - } -} - -+ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object { - Class threadKey = [GTMSessionSyncMonitorInternal class]; - NSValue *localObjectKey = [NSValue valueWithNonretainedObject:object]; - - NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; - NSMutableDictionary *counters = threadDict[threadKey]; - NSCountedSet *functionNamesCounter = counters[localObjectKey]; - return functionNamesCounter.count > 0 ? functionNamesCounter.allObjects : nil; -} -@end -#endif // DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h @@ -1,112 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 "GTMSessionFetcher.h" - -// GTM HTTP Logging -// -// All traffic using GTMSessionFetcher can be easily logged. Call -// -// [GTMSessionFetcher setLoggingEnabled:YES]; -// -// to begin generating log files. -// -// Unless explicitly set by the application using +setLoggingDirectory:, -// logs are put into a default directory, located at: -// * macOS: ~/Desktop/GTMHTTPDebugLogs -// * iOS simulator: ~/GTMHTTPDebugLogs (in application sandbox) -// * iOS device: ~/Documents/GTMHTTPDebugLogs (in application sandbox) -// -// 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 GTMSessionFetcher (GTMSessionFetcherLogging) - -// Note: on macOS the default logs directory is ~/Desktop/GTMHTTPDebugLogs; on -// iOS simulators it will be the ~/GTMHTTPDebugLogs (in the app sandbox); on -// iOS devices it will be in ~/Documents/GTMHTTPDebugLogs (in the app sandbox). -// These directories will be created as needed, and are excluded from backups -// to iCloud and iTunes. -// -// If a custom directory is set, the directory should already exist. It is -// the application's responsibility to exclude any custom directory from -// backups, if desired. -+ (void)setLoggingDirectory:(NSString *)path; -+ (NSString *)loggingDirectory; - -// client apps can turn logging on and off -+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled; -+ (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)isLoggingToFileEnabled; -+ (BOOL)isLoggingToFileEnabled; - -// client apps can optionally specify process name and date string used in -// log file names -+ (void)setLoggingProcessName:(NSString *)processName; -+ (NSString *)loggingProcessName; - -+ (void)setLoggingDateStamp:(NSString *)dateStamp; -+ (NSString *)loggingDateStamp; - -// client apps can specify the directory for the log for this specific run, -// typically to match the directory used by another fetcher class, like: -// -// [GTMSessionFetcher setLogDirectoryForCurrentRun:[GTMHTTPFetcher logDirectoryForCurrentRun]]; -// -// Setting this overrides the logging directory, process name, and date stamp when writing -// the log file. -+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun; -+ (NSString *)logDirectoryForCurrentRun; - -// Prunes old log directories that have not been modified since the provided date. -// This will not delete the current run's log directory. -+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)date; - -// internal; called by fetcher -- (void)logFetchWithError:(NSError *)error; -- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; -- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: - (GTMSessionFetcherBodyStreamProvider)streamProvider; - -// internal; accessors useful for viewing logs -+ (NSString *)processNameLogPrefix; -+ (NSString *)symlinkNameSuffix; -+ (NSString *)htmlFileName; - -@end - -#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m @@ -1,982 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#include <sys/stat.h> -#include <unistd.h> - -#import "GTMSessionFetcherLogging.h" - -#ifndef STRIP_GTM_FETCH_LOGGING - #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. -#endif - -#if !STRIP_GTM_FETCH_LOGGING - -// 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 -#if !GTMSESSION_BUILD_COMBINED_SOURCES -@interface GTMReadMonitorInputStream : NSInputStream - -+ (instancetype)inputStreamWithStream:(NSInputStream *)input; - -@property (assign) id readDelegate; -@property (assign) SEL readSelector; - -@end -#else -@class GTMReadMonitorInputStream; -#endif // !GTMSESSION_BUILD_COMBINED_SOURCES - -@interface GTMSessionFetcher (GTMHTTPFetcherLoggingUtilities) - -+ (NSString *)headersStringForDictionary:(NSDictionary *)dict; -+ (NSString *)snipSubstringOfString:(NSString *)originalStr - betweenStartString:(NSString *)startStr - endString:(NSString *)endStr; -- (void)inputStream:(GTMReadMonitorInputStream *)stream - readIntoBuffer:(void *)buffer - length:(int64_t)length; - -@end - -@implementation GTMSessionFetcher (GTMSessionFetcherLogging) - -// fetchers come and fetchers go, but statics are forever -static BOOL gIsLoggingEnabled = NO; -static BOOL gIsLoggingToFile = YES; -static NSString *gLoggingDirectoryPath = nil; -static NSString *gLogDirectoryForCurrentRun = nil; -static NSString *gLoggingDateStamp = nil; -static NSString *gLoggingProcessName = nil; - -+ (void)setLoggingDirectory:(NSString *)path { - gLoggingDirectoryPath = [path copy]; -} - -+ (NSString *)loggingDirectory { - if (!gLoggingDirectoryPath) { - NSArray *paths = nil; -#if TARGET_IPHONE_SIMULATOR - // default to a directory called GTMHTTPDebugLogs into a sandbox-safe - // directory that a developer can find easily, the application home - paths = @[ NSHomeDirectory() ]; -#elif TARGET_OS_IPHONE - // Neither ~/Desktop nor ~/Home is writable on an actual iOS, watchOS, or tvOS device. - // Put it in ~/Documents. - paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); -#else - // default to a directory called GTMHTTPDebugLogs in the desktop folder - paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); -#endif - - NSString *desktopPath = paths.firstObject; - if (desktopPath) { - NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs"; - NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName]; - - NSFileManager *fileMgr = [NSFileManager defaultManager]; - BOOL isDir; - BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir]; - if (!doesFolderExist) { - // make the directory - doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath - withIntermediateDirectories:YES - attributes:nil - error:NULL]; - if (doesFolderExist) { - // The directory has been created. Exclude it from backups. - NSURL *pathURL = [NSURL fileURLWithPath:logsFolderPath isDirectory:YES]; - [pathURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL]; - } - } - - if (doesFolderExist) { - // it's there; store it in the global - gLoggingDirectoryPath = [logsFolderPath copy]; - } - } - } - return gLoggingDirectoryPath; -} - -+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun { - // Set the path for this run's logs. - gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy]; -} - -+ (NSString *)logDirectoryForCurrentRun { - // make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM - if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun; - - NSString *parentDir = [self loggingDirectory]; - NSString *logNamePrefix = [self processNameLogPrefix]; - NSString *dateStamp = [self loggingDateStamp]; - NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp]; - NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName]; - - if (gIsLoggingToFile) { - NSFileManager *fileMgr = [NSFileManager defaultManager]; - // Be sure that the first time this app runs, it's not writing to a preexisting folder - static BOOL gShouldReuseFolder = NO; - if (!gShouldReuseFolder) { - gShouldReuseFolder = YES; - NSString *origLogDir = logDirectory; - for (int ctr = 2; ctr < 20; ++ctr) { - if (![fileMgr fileExistsAtPath:logDirectory]) break; - - // append a digit - logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr]; - } - } - if (![fileMgr createDirectoryAtPath:logDirectory - withIntermediateDirectories:YES - attributes:nil - error:NULL]) return nil; - } - gLogDirectoryForCurrentRun = logDirectory; - - return gLogDirectoryForCurrentRun; -} - -+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled { - gIsLoggingEnabled = isLoggingEnabled; -} - -+ (BOOL)isLoggingEnabled { - return gIsLoggingEnabled; -} - -+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled { - gIsLoggingToFile = isLoggingToFileEnabled; -} - -+ (BOOL)isLoggingToFileEnabled { - return gIsLoggingToFile; -} - -+ (void)setLoggingProcessName:(NSString *)processName { - gLoggingProcessName = [processName copy]; -} - -+ (NSString *)loggingProcessName { - // get the process name (once per run) replacing spaces with underscores - if (!gLoggingProcessName) { - NSString *procName = [[NSProcessInfo processInfo] processName]; - gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"]; - } - return gLoggingProcessName; -} - -+ (void)setLoggingDateStamp:(NSString *)dateStamp { - gLoggingDateStamp = [dateStamp 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]; - [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; - [formatter setDateFormat:@"M-dd_hh-mm-ssa"]; - - gLoggingDateStamp = [formatter stringFromDate:[NSDate date]]; - } - return gLoggingDateStamp; -} - -+ (NSString *)processNameLogPrefix { - static NSString *gPrefix = nil; - if (!gPrefix) { - NSString *processName = [self loggingProcessName]; - gPrefix = [[NSString alloc] initWithFormat:@"%@_log_", processName]; - } - return gPrefix; -} - -+ (NSString *)symlinkNameSuffix { - return @"_log_newest.html"; -} - -+ (NSString *)htmlFileName { - return @"aperçu_http_log.html"; -} - -+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)cutoffDate { - NSFileManager *fileMgr = [NSFileManager defaultManager]; - NSURL *parentDir = [NSURL fileURLWithPath:[[self class] loggingDirectory]]; - NSURL *logDirectoryForCurrentRun = - [NSURL fileURLWithPath:[[self class] logDirectoryForCurrentRun]]; - NSError *error; - NSArray *contents = [fileMgr contentsOfDirectoryAtURL:parentDir - includingPropertiesForKeys:@[ NSURLContentModificationDateKey ] - options:0 - error:&error]; - for (NSURL *itemURL in contents) { - if ([itemURL isEqual:logDirectoryForCurrentRun]) continue; - - NSDate *modDate; - if ([itemURL getResourceValue:&modDate - forKey:NSURLContentModificationDateKey - error:&error]) { - if ([modDate compare:cutoffDate] == NSOrderedAscending) { - if (![fileMgr removeItemAtURL:itemURL error:&error]) { - NSLog(@"deleteLogDirectoriesOlderThanDate failed to delete %@: %@", - itemURL.path, error); - } - } - } else { - NSLog(@"deleteLogDirectoriesOlderThanDate failed to get mod date of %@: %@", - itemURL.path, error); - } - } -} - -// 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) 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 = [NSJSONSerialization JSONObjectWithData:inputData - options:NSJSONReadingMutableContainers - error:NULL]; - 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"]; - } - } - NSData *data = [NSJSONSerialization dataWithJSONObject:obj - options:NSJSONWritingPrettyPrinted - error:NULL]; - if (data) { - NSString *jsonStr = [[NSString alloc] initWithData:data - encoding:NSUTF8StringEncoding]; - return jsonStr; - } - } - } - -#if !TARGET_OS_IPHONE && !GTM_SKIP_LOG_XMLFORMAT - // verify that this data starts with the bytes indicating XML - - NSString *const kXMLLintPath = @"/usr/bin/xmllint"; - static BOOL gHasCheckedAvailability = NO; - static BOOL gIsXMLLintAvailable = NO; - - if (!gHasCheckedAvailability) { - gIsXMLLintAvailable = [[NSFileManager defaultManager] fileExistsAtPath:kXMLLintPath]; - gHasCheckedAvailability = YES; - } - if (gIsXMLLintAvailable - && inputData.length > 5 - && strncmp(inputData.bytes, "<?xml", 5) == 0) { - - // call xmllint to format the data - NSTask *task = [[NSTask alloc] init]; - [task setLaunchPath:kXMLLintPath]; - - // use the dash argument to specify stdin as the source file - [task setArguments:@[ @"--format", @"-" ]]; - [task setEnvironment:@{}]; - - NSPipe *inputPipe = [NSPipe pipe]; - NSPipe *outputPipe = [NSPipe pipe]; - [task setStandardInput:inputPipe]; - [task setStandardOutput:outputPipe]; - - [task launch]; - - [[inputPipe fileHandleForWriting] writeData:inputData]; - [[inputPipe fileHandleForWriting] closeFile]; - - // drain the stdout before waiting for the task to exit - NSData *formattedData = [[outputPipe fileHandleForReading] readDataToEndOfFile]; - - [task waitUntilExit]; - - int status = [task terminationStatus]; - if (status == 0 && formattedData.length > 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]; - return dataStr; -} - -// stringFromStreamData creates a string given the supplied data -// -// If NSString can create a UTF-8 string from the data, then that is returned. -// -// Otherwise, this routine tries to find a MIME boundary at the beginning of the data block, and -// uses that to break up the data into parts. Each part will be used to try to make a UTF-8 string. -// For parts that fail, a replacement string showing the part header and <<n bytes>> is supplied -// in place of the binary data. - -- (NSString *)stringFromStreamData:(NSData *)data - contentType:(NSString *)contentType { - - if (!data) 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 = (unsigned char *)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]; - if (mungedStr) { - - // 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 munged part 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; - NSData *origPartData = [data subdataWithRange:NSMakeRange(offset, partSize)]; - NSString *origPartStr = [[NSString alloc] initWithData:origPartData - encoding:NSUTF8StringEncoding]; - if (origPartStr) { - // we could make this original part into UTF-8; use the string - [origParts addObject:origPartStr]; - } else { - // this part can't be made into UTF-8; scan the header, if we can - NSString *header = nil; - NSScanner *headerScanner = [NSScanner scannerWithString:mungedPart]; - if (![headerScanner scanUpToString:@"\r\n\r\n" intoString:&header]) { - // we couldn't find a header - header = @""; - } - // make a part string with the header and <<n bytes>> - NSString *binStr = [NSString stringWithFormat:@"\r%@\r<<%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; - NSString *logDirectory = [[self class] logDirectoryForCurrentRun]; - if (!logDirectory) return; - NSString *processName = [[self class] loggingProcessName]; - - // TODO: add Javascript to display response data formatted in hex - - // 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 app run. - // - // 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 gResponseCounter = 0; - int responseCounter = ++gResponseCounter; - - NSURLResponse *response = [self response]; - NSDictionary *responseHeaders = [self responseHeaders]; - 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; - - // file name for an image data file - NSString *responseDataFileName = nil; - - int64_t responseDataLength = self.downloadedLength; - if (responseDataLength > 0) { - NSData *downloadedData = self.downloadedData; - if (downloadedData == nil - && responseDataLength > 0 - && responseDataLength < 20000 - && self.destinationFileURL) { - // There's a download file that's not too big, so get the data to display from the downloaded - // file. - NSURL *destinationURL = self.destinationFileURL; - downloadedData = [NSData dataWithContentsOfURL:destinationURL]; - } - NSString *responseType = [responseHeaders valueForKey:@"Content-Type"]; - responseDataStr = [self formattedStringFromData:downloadedData - contentType:responseType - JSON:&responseJSON]; - NSString *responseDataExtn = nil; - NSData *dataToWrite = nil; - 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) { - // generate a response file base name like - NSString *responseBaseName = [NSString stringWithFormat:@"fetch_%d_response", responseCounter]; - 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 = [[self class] htmlFileName]; - 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 - NSFileManager *fileMgr = [NSFileManager defaultManager]; - BOOL didFileExist = [fileMgr fileExistsAtPath:htmlPath]; - - NSMutableString* outputHTML = [NSMutableString string]; - - // we need a header to say we'll have UTF-8 text - if (!didFileExist) { - [outputHTML appendFormat:@"<html><head><meta http-equiv=\"content-type\" " - "content=\"text/html; charset=UTF-8\"><title>%@ HTTP fetch log %@</title>", - processName, [[self class] loggingDateStamp]]; - } - // now write the visible html elements - NSString *copyableFileName = [NSString stringWithFormat:@"fetch_%d.txt", responseCounter]; - - NSDate *now = [NSDate date]; - // write the date & time, the comment, and the link to the plain-text (copyable) log - [outputHTML appendFormat:@"<b>%@ &nbsp;&nbsp;&nbsp;&nbsp; ", now]; - - NSString *comment = [self comment]; - if (comment.length > 0) { - [outputHTML appendFormat:@"%@ &nbsp;&nbsp;&nbsp;&nbsp; ", comment]; - } - [outputHTML appendFormat:@"</b><a href='%@'><i>request/response log</i></a><br>", copyableFileName]; - NSTimeInterval elapsed = -self.initialBeginFetchDate.timeIntervalSinceNow; - [outputHTML appendFormat:@"elapsed: %5.3fsec<br>", elapsed]; - - // write the request URL - NSURLRequest *request = self.request; - NSString *requestMethod = request.HTTPMethod; - NSURL *requestURL = request.URL; - - // Save the request URL for next time in case this redirects. - NSString *redirectedFromURLString = [self.redirectedFromURL absoluteString]; - self.redirectedFromURL = [requestURL copy]; - if (redirectedFromURLString) { - [outputHTML appendFormat:@"<FONT COLOR='#990066'><i>redirected from %@</i></FONT><br>", - redirectedFromURLString]; - } - [outputHTML appendFormat:@"<b>request:</b> %@ <code>%@</code><br>\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) { - BOOL isInsecure = [[requestURL scheme] isEqual:@"http"]; - if (isInsecure) { - // 26A0 = ⚠ - headerDetails = - @"&nbsp;&nbsp;&nbsp;<i>authorized, non-SSL</i><FONT COLOR='#FF00FF'> &#x26A0;</FONT> "; - } else { - headerDetails = @"&nbsp;&nbsp;&nbsp;<i>authorized</i>"; - } - } - NSString *cookiesHdr = [requestHeaders objectForKey:@"Cookie"]; - if (cookiesHdr) { - headerDetails = [headerDetails stringByAppendingString:@"&nbsp;&nbsp;&nbsp;<i>cookies</i>"]; - } - NSString *matchHdr = [requestHeaders objectForKey:@"If-Match"]; - if (matchHdr) { - headerDetails = [headerDetails stringByAppendingString:@"&nbsp;&nbsp;&nbsp;<i>if-match</i>"]; - } - matchHdr = [requestHeaders objectForKey:@"If-None-Match"]; - if (matchHdr) { - headerDetails = [headerDetails stringByAppendingString:@"&nbsp;&nbsp;&nbsp;<i>if-none-match</i>"]; - } - [outputHTML appendFormat:@"&nbsp;&nbsp; headers: %d %@<br>", - (int)numberOfRequestHeaders, headerDetails]; - } else { - [outputHTML appendFormat:@"&nbsp;&nbsp; headers: none<br>"]; - } - // write the request post data - NSData *bodyData = nil; - NSData *loggedStreamData = self.loggedStreamData; - if (loggedStreamData) { - bodyData = loggedStreamData; - } else { - bodyData = self.bodyData; - if (bodyData == nil) { - bodyData = self.request.HTTPBody; - } - } - uint64_t bodyDataLength = bodyData.length; - - if (bodyData.length == 0) { - // If the data is in a body upload file URL, read that in if it's not huge. - NSURL *bodyFileURL = self.bodyFileURL; - if (bodyFileURL) { - NSNumber *fileSizeNum = nil; - NSError *fileSizeError = nil; - if ([bodyFileURL getResourceValue:&fileSizeNum - forKey:NSURLFileSizeKey - error:&fileSizeError]) { - bodyDataLength = [fileSizeNum unsignedLongLongValue]; - if (bodyDataLength > 0 && bodyDataLength < 50000) { - bodyData = [NSData dataWithContentsOfURL:bodyFileURL - options:NSDataReadingUncached - error:&fileSizeError]; - } - } - } - } - NSString *bodyDataStr = nil; - NSString *postType = [requestHeaders valueForKey:@"Content-Type"]; - - if (bodyDataLength > 0) { - [outputHTML appendFormat:@"&nbsp;&nbsp; data: %llu bytes, <code>%@</code><br>\n", - bodyDataLength, postType ? postType : @"(no type)"]; - NSString *logRequestBody = self.logRequestBody; - if (logRequestBody) { - bodyDataStr = [logRequestBody copy]; - self.logRequestBody = nil; - } else { - bodyDataStr = [self stringFromStreamData:bodyData - contentType:postType]; - if (bodyDataStr) { - // remove OAuth 2 client secret and refresh token - bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr - betweenStartString:@"client_secret=" - endString:@"&"]; - bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr - betweenStartString:@"refresh_token=" - endString:@"&"]; - // remove ClientLogin password - bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr - 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) { - // 2691 = ⚑ - NSString *const jsonErrFmt = - @"&nbsp;&nbsp;&nbsp;<i>JSON error:</i> <FONT COLOR='#FF00FF'>%@ %@ &nbsp;&#x2691;</FONT>"; - statusString = [statusString stringByAppendingFormat:jsonErrFmt, - jsonCode ? jsonCode : @"", - jsonMessage ? jsonMessage : @""]; - } - } - } - } else { - // purple for anything other than 200 or 201 - NSString *flag = status >= 400 ? @"&nbsp;&#x2691;" : @""; // 2691 = ⚑ - NSString *explanation = [NSHTTPURLResponse localizedStringForStatusCode:status]; - NSString *const statusFormat = @"<FONT COLOR='#FF00FF'>%ld %@ %@</FONT>"; - statusString = [NSString stringWithFormat:statusFormat, (long)status, explanation, 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 *const responseURLFormat = - @"<FONT COLOR='#FF00FF'>response URL:</FONT> <code>%@</code><br>\n"; - responseURLStr = [NSString stringWithFormat:responseURLFormat, [responseURL absoluteString]]; - } - [outputHTML appendFormat:@"<b>response:</b>&nbsp;&nbsp;status %@<br>\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 ? @"&nbsp;&nbsp;<FONT COLOR='#990066'><i>sets cookies</i></FONT>" : @""; - // Indicate if the server is redirecting - NSString *location = [responseHeaders valueForKey:@"Location"]; - BOOL isRedirect = status >= 300 && status <= 399 && location != nil; - NSString *redirectsStr = - isRedirect ? @"&nbsp;&nbsp;<FONT COLOR='#990066'><i>redirects</i></FONT>" : @""; - [outputHTML appendFormat:@"&nbsp;&nbsp; headers: %d %@ %@<br>\n", - (int)numberOfResponseHeaders, cookiesStr, redirectsStr]; - } else { - [outputHTML appendString:@"&nbsp;&nbsp; headers: none<br>\n"]; - } - } - // error - if (error) { - [outputHTML appendFormat:@"<b>Error:</b> %@ <br>\n", error.description]; - } - // Write the response data - if (responseDataFileName) { - if (isResponseImage) { - // Make a small inline image that links to the full image file - [outputHTML appendFormat:@"&nbsp;&nbsp; data: %lld bytes, <code>%@</code><br>", - responseDataLength, responseMIMEType]; - NSString *const fmt = - @"<a href=\"%@\"><img src='%@' alt='image' style='border:solid thin;max-height:32'></a>\n"; - [outputHTML appendFormat:fmt, responseDataFileName, responseDataFileName]; - } else { - // The response data was XML; link to the xml file - NSString *const fmt = - @"&nbsp;&nbsp; data: %lld bytes, <code>%@</code>&nbsp;&nbsp;&nbsp;<i><a href=\"%@\">%@</a></i>\n"; - [outputHTML appendFormat:fmt, responseDataLength, responseMIMEType, - responseDataFileName, [responseDataFileName pathExtension]]; - } - } else { - // The response data was not an image; just show the length and MIME type - [outputHTML appendFormat:@"&nbsp;&nbsp; data: %lld bytes, <code>%@</code>\n", - responseDataLength, responseMIMEType ? responseMIMEType : @"(no response type)"]; - } - // 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:@"%@ elapsed: %5.3fsec\n", now, elapsed]; - if (redirectedFromURLString) { - [copyable appendFormat:@"Redirected from %@\n", redirectedFromURLString]; - } - [copyable appendFormat:@"Request: %@ %@\n", requestMethod, requestURL]; - if (requestHeaders.count > 0) { - [copyable appendFormat:@"Request headers:\n%@\n", - [[self class] headersStringForDictionary:requestHeaders]]; - } - if (bodyDataLength > 0) { - [copyable appendFormat:@"Request body: (%llu bytes)\n", bodyDataLength]; - if (bodyDataStr) { - [copyable appendFormat:@"%@\n", bodyDataStr]; - } - [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: (%lld bytes)\n", responseDataLength]; - if (responseDataLength > 0) { - NSString *logResponseBody = self.logResponseBody; - if (logResponseBody) { - // The user has provided the response body text. - responseDataStr = [logResponseBody copy]; - self.logResponseBody = nil; - } - if (responseDataStr != nil) { - [copyable appendFormat:@"%@\n", responseDataStr]; - } else { - // Even though it's redundant, we'll put in text to indicate that all the bytes are binary. - if (self.destinationFileURL) { - [copyable appendFormat:@"<<%lld bytes>> to file %@\n", - responseDataLength, self.destinationFileURL.path]; - } else { - [copyable appendFormat:@"<<%lld bytes>>\n", 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 - // <span onCopy='window.event.clipboardData.setData(\"Text\", - // \"copyable stuff\");return false;'>Copy here.</span>" - // would work everywhere, but it only works in Safari as of 8/2010 - if (gIsLoggingToFile) { - NSString *parentDir = [[self class] loggingDirectory]; - NSString *copyablePath = [logDirectory stringByAppendingPathComponent:copyableFileName]; - NSError *copyableError = nil; - if (![copyable writeToFile:copyablePath - atomically:NO - encoding:NSUTF8StringEncoding - error:&copyableError]) { - // Error writing to file - NSLog(@"%@ logging write error:%@ (%@)", [self class], copyableError, copyablePath); - } - [outputHTML appendString:@"<br><hr><p>"]; - - // Append the HTML to the main output file - const char* htmlBytes = outputHTML.UTF8String; - NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:htmlPath - append:YES]; - [stream open]; - [stream write:(const uint8_t *) htmlBytes maxLength:strlen(htmlBytes)]; - [stream close]; - - // Make a symlink to the latest html - NSString *const symlinkNameSuffix = [[self class] symlinkNameSuffix]; - NSString *symlinkName = [processName stringByAppendingString:symlinkNameSuffix]; - NSString *symlinkPath = [parentDir stringByAppendingPathComponent:symlinkName]; - - [fileMgr removeItemAtPath:symlinkPath error:NULL]; - [fileMgr createSymbolicLinkAtPath:symlinkPath - withDestinationPath:htmlPath - error:NULL]; -#if TARGET_OS_IPHONE - static BOOL gReportedLoggingPath = NO; - if (!gReportedLoggingPath) { - gReportedLoggingPath = YES; - NSLog(@"GTMSessionFetcher logging to \"%@\"", parentDir); - } -#endif - } -} - -- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream { - if (!inputStream) return nil; - if (![GTMSessionFetcher isLoggingEnabled]) return inputStream; - - [self clearLoggedStreamData]; // Clear any previous data. - Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream"); - if (!monitorClass) { - NSString const *str = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>"; - NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding]; - [self appendLoggedStreamData:stringData]; - return inputStream; - } - inputStream = [monitorClass inputStreamWithStream:inputStream]; - - GTMReadMonitorInputStream *readMonitorInputStream = (GTMReadMonitorInputStream *)inputStream; - [readMonitorInputStream setReadDelegate:self]; - SEL readSel = @selector(inputStream:readIntoBuffer:length:); - [readMonitorInputStream setReadSelector:readSel]; - - return inputStream; -} - -- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: - (GTMSessionFetcherBodyStreamProvider)streamProvider { - if (!streamProvider) return nil; - if (![GTMSessionFetcher isLoggingEnabled]) return streamProvider; - - [self clearLoggedStreamData]; // Clear any previous data. - Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream"); - if (!monitorClass) { - NSString const *str = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>"; - NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding]; - [self appendLoggedStreamData:stringData]; - return streamProvider; - } - GTMSessionFetcherBodyStreamProvider loggedStreamProvider = - ^(GTMSessionFetcherBodyStreamProviderResponse response) { - streamProvider(^(NSInputStream *bodyStream) { - bodyStream = [self loggedInputStreamForInputStream:bodyStream]; - response(bodyStream); - }); - }; - return loggedStreamProvider; -} - -@end - -@implementation GTMSessionFetcher (GTMSessionFetcherLoggingUtilities) - -- (void)inputStream:(GTMReadMonitorInputStream *)stream - readIntoBuffer:(void *)buffer - length:(int64_t)length { - // append the captured data - NSData *data = [NSData dataWithBytesNoCopy:buffer - length:(NSUInteger)length - freeWhenDone:NO]; - [self appendLoggedStreamData:data]; -} - -#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) 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; -} - -@end - -#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h @@ -1,193 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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. - */ - -// For best performance and convenient usage, fetchers should be generated by a common -// GTMSessionFetcherService instance, like -// -// _fetcherService = [[GTMSessionFetcherService alloc] init]; -// GTMSessionFetcher* myFirstFetcher = [_fetcherService fetcherWithRequest:request1]; -// GTMSessionFetcher* mySecondFetcher = [_fetcherService fetcherWithRequest:request2]; - -#import "GTMSessionFetcher.h" - -GTM_ASSUME_NONNULL_BEGIN - -// Notifications. - -// This notification indicates a reusable session has become invalid. It is intended mainly for the -// service's unit tests. -// -// The notification object is the fetcher service. -// The invalid session is provided via the userInfo kGTMSessionFetcherServiceSessionKey key. -extern NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification; -extern NSString *const kGTMSessionFetcherServiceSessionKey; - -@interface GTMSessionFetcherService : NSObject<GTMSessionFetcherServiceProtocol> - -// Queues of delayed and running fetchers. Each dictionary contains arrays -// of GTMSessionFetcher *fetchers, keyed by NSString *host -@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *delayedFetchersByHost; -@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *runningFetchersByHost; - -// A max value of 0 means no fetchers should be delayed. -// The default limit is 10 simultaneous fetchers targeting each host. -// This does not apply to fetchers whose useBackgroundSession property is YES. Since services are -// not resurrected on an app relaunch, delayed fetchers would effectively be abandoned. -@property(atomic, assign) NSUInteger maxRunningFetchersPerHost; - -// Properties to be applied to each fetcher; see GTMSessionFetcher.h for descriptions -@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration; -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock; -@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage; -@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue; -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock; -@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential; -@property(atomic, strong) NSURLCredential *proxyCredential; -@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes; -@property(atomic, assign) BOOL allowLocalhostRequest; -@property(atomic, assign) BOOL allowInvalidServerCertificates; -@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled; -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock; -@property(atomic, assign) NSTimeInterval maxRetryInterval; -@property(atomic, assign) NSTimeInterval minRetryInterval; -@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties; - -#if GTM_BACKGROUND_TASK_FETCHING -@property(atomic, assign) BOOL skipBackgroundTask; -#endif - -// A default useragent of GTMFetcherStandardUserAgentString(nil) will be given to each fetcher -// created by this service unless the request already has a user-agent header set. -// This default will be added starting with builds with the SDKs for OS X 10.11 and iOS 9. -// -// To use the configuration's default user agent, set this property to nil. -@property(atomic, copy, GTM_NULLABLE) NSString *userAgent; - -// The authorizer to attach to the created fetchers. If a specific fetcher should -// not authorize its requests, the fetcher's authorizer property may be set to nil -// before the fetch begins. -@property(atomic, strong, GTM_NULLABLE) id<GTMFetcherAuthorizationProtocol> authorizer; - -// Delegate queue used by the session when calling back to the fetcher. The default -// is the main queue. Changing this does not affect the queue used to call back to the -// application; that is specified by the callbackQueue property above. -@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue; - -// When enabled, indicates the same session should be used by subsequent fetchers. -// -// This is enabled by default. -@property(atomic, assign) BOOL reuseSession; - -// Sets the delay until an unused session is invalidated. -// The default interval is 60 seconds. -// -// If the interval is set to 0, then any reused session is not invalidated except by -// explicitly invoking -resetSession. Be aware that setting the interval to 0 thus -// causes the session's delegate to be retained until the session is explicitly reset. -@property(atomic, assign) NSTimeInterval unusedSessionTimeout; - -// If shouldReuseSession is enabled, this will force creation of a new session when future -// fetchers begin. -- (void)resetSession; - -// Create a fetcher -// -// These methods will return a fetcher. If successfully created, the connection -// will hold a strong reference to it for the life of the connection as well. -// So the caller doesn't have to hold onto the fetcher explicitly unless they -// want to be able to monitor or cancel it. -- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request; -- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL; -- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString; - -// Common method for fetcher creation. -// -// -fetcherWithRequest:fetcherClass: may be overridden to customize creation of -// fetchers. This is the ONLY method in the GTMSessionFetcher library intended to -// be overridden. -- (id)fetcherWithRequest:(NSURLRequest *)request - fetcherClass:(Class)fetcherClass; - -- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher; - -- (NSUInteger)numberOfFetchers; // running + delayed fetchers -- (NSUInteger)numberOfRunningFetchers; -- (NSUInteger)numberOfDelayedFetchers; - -// Return a list of all running or delayed fetchers. This includes fetchers created -// by the service which have been started and have not yet stopped. -// -// Returns an array of fetcher objects, or nil if none. -- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchers; - -// Search for running or delayed fetchers with the specified URL. -// -// Returns an array of fetcher objects found, or nil if none found. -- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchersWithRequestURL:(NSURL *)requestURL; - -- (void)stopAllFetchers; - -// Methods for use by the fetcher class only. -- (GTM_NULLABLE NSURLSession *)session; -- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation; -- (GTM_NULLABLE id<NSURLSessionDelegate>)sessionDelegate; -- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate; - -// The testBlock can inspect its fetcher parameter's request property to -// determine which fetcher is being faked. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock; - -@end - -@interface GTMSessionFetcherService (TestingSupport) - -// Convenience methods to create a fetcher service for testing. -// -// Fetchers generated by this mock fetcher service will not perform any -// network operation, but will invoke callbacks and provide the supplied data -// or error to the completion handler. -// -// You can make more customized mocks by setting the test block property of the service -// or fetcher; the test block can inspect the fetcher's request or other properties. -// -// See the description of the testBlock property below. -+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil - fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; -+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil - fakedResponse:(NSHTTPURLResponse *)fakedResponse - fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; - -// Spin the run loop and discard events (or, if not on the main thread, just sleep the thread) -// 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. -// -// Returns NO if timed out. -- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds; - -@end - -@interface GTMSessionFetcherService (BackwardsCompatibilityOnly) - -// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. -// This method is just for compatibility with the old fetcher. -@property(atomic, assign) NSInteger cookieStorageMethod; - -@end - -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m @@ -1,1365 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#import "GTMSessionFetcherService.h" - -NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification - = @"kGTMSessionFetcherServiceSessionBecameInvalidNotification"; -NSString *const kGTMSessionFetcherServiceSessionKey - = @"kGTMSessionFetcherServiceSessionKey"; - -#if !GTMSESSION_BUILD_COMBINED_SOURCES -@interface GTMSessionFetcher (ServiceMethods) -- (BOOL)beginFetchMayDelay:(BOOL)mayDelay - mayAuthorize:(BOOL)mayAuthorize; -@end -#endif // !GTMSESSION_BUILD_COMBINED_SOURCES - -@interface GTMSessionFetcherService () - -@property(atomic, strong, readwrite) NSDictionary *delayedFetchersByHost; -@property(atomic, strong, readwrite) NSDictionary *runningFetchersByHost; - -@end - -// Since NSURLSession doesn't support a separate delegate per task (!), instances of this -// class serve as a session delegate trampoline. -// -// This class maps a session's tasks to fetchers, and resends delegate messages to the task's -// fetcher. -@interface GTMSessionFetcherSessionDelegateDispatcher : NSObject<NSURLSessionDelegate> - -// The session for the tasks in this dispatcher's task-to-fetcher map. -@property(atomic) NSURLSession *session; - -// The timer interval for invalidating a session that has no active tasks. -@property(atomic) NSTimeInterval discardInterval; - -// The current discard timer. -@property(atomic, readonly) NSTimer *discardTimer; - - -- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService - sessionDiscardInterval:(NSTimeInterval)discardInterval; - -- (void)setFetcher:(GTMSessionFetcher *)fetcher - forTask:(NSURLSessionTask *)task; -- (void)removeFetcher:(GTMSessionFetcher *)fetcher; - -// Before using a session, tells the delegate dispatcher to stop the discard timer. -- (void)startSessionUsage; - -// When abandoning a delegate dispatcher, we want to avoid the session retaining -// the delegate after tasks complete. -- (void)abandon; - -@end - - -@implementation GTMSessionFetcherService { - NSMutableDictionary *_delayedFetchersByHost; - NSMutableDictionary *_runningFetchersByHost; - NSUInteger _maxRunningFetchersPerHost; - - // When this ivar is nil, the service will not reuse sessions. - GTMSessionFetcherSessionDelegateDispatcher *_delegateDispatcher; - - // Fetchers will wait on this if another fetcher is creating the shared NSURLSession. - dispatch_semaphore_t _sessionCreationSemaphore; - - dispatch_queue_t _callbackQueue; - NSOperationQueue *_delegateQueue; - NSHTTPCookieStorage *_cookieStorage; - NSString *_userAgent; - NSTimeInterval _timeout; - - NSURLCredential *_credential; // Username & password. - NSURLCredential *_proxyCredential; // Credential supplied to proxy servers. - - NSInteger _cookieStorageMethod; - - id<GTMFetcherAuthorizationProtocol> _authorizer; - - // For waitForCompletionOfAllFetchersWithTimeout: we need to wait on stopped fetchers since - // they've not yet finished invoking their queued callbacks. This array is nil except when - // waiting on fetchers. - NSMutableArray *_stoppedFetchersToWaitFor; - - // For fetchers that enqueued their callbacks before stopAllFetchers was called on the service, - // set a barrier so the callbacks know to bail out. - NSDate *_stoppedAllFetchersDate; -} - -@synthesize maxRunningFetchersPerHost = _maxRunningFetchersPerHost, - configuration = _configuration, - configurationBlock = _configurationBlock, - cookieStorage = _cookieStorage, - userAgent = _userAgent, - challengeBlock = _challengeBlock, - credential = _credential, - proxyCredential = _proxyCredential, - allowedInsecureSchemes = _allowedInsecureSchemes, - allowLocalhostRequest = _allowLocalhostRequest, - allowInvalidServerCertificates = _allowInvalidServerCertificates, - retryEnabled = _retryEnabled, - retryBlock = _retryBlock, - maxRetryInterval = _maxRetryInterval, - minRetryInterval = _minRetryInterval, - properties = _properties, - unusedSessionTimeout = _unusedSessionTimeout, - testBlock = _testBlock; - -#if GTM_BACKGROUND_TASK_FETCHING -@synthesize skipBackgroundTask = _skipBackgroundTask; -#endif - -- (instancetype)init { - self = [super init]; - if (self) { - _delayedFetchersByHost = [[NSMutableDictionary alloc] init]; - _runningFetchersByHost = [[NSMutableDictionary alloc] init]; - _maxRunningFetchersPerHost = 10; - _cookieStorageMethod = -1; - _unusedSessionTimeout = 60.0; - _delegateDispatcher = - [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self - sessionDiscardInterval:_unusedSessionTimeout]; - _callbackQueue = dispatch_get_main_queue(); - - _delegateQueue = [[NSOperationQueue alloc] init]; - _delegateQueue.maxConcurrentOperationCount = 1; - _delegateQueue.name = @"com.google.GTMSessionFetcher.NSURLSessionDelegateQueue"; - - _sessionCreationSemaphore = dispatch_semaphore_create(1); - - // Starting with the SDKs for OS X 10.11/iOS 9, the service has a default useragent. - // Apps can remove this and get the default system "CFNetwork" useragent by setting the - // fetcher service's userAgent property to nil. -#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0) - _userAgent = GTMFetcherStandardUserAgentString(nil); -#endif - } - return self; -} - -- (void)dealloc { - [self detachAuthorizer]; - [_delegateDispatcher abandon]; -} - -#pragma mark Generate a new fetcher - -// Clients may override this method. Clients should not override any other library methods. -- (id)fetcherWithRequest:(NSURLRequest *)request - fetcherClass:(Class)fetcherClass { - GTMSessionFetcher *fetcher = [[fetcherClass alloc] initWithRequest:request - configuration:self.configuration]; - fetcher.callbackQueue = self.callbackQueue; - fetcher.sessionDelegateQueue = self.sessionDelegateQueue; - fetcher.challengeBlock = self.challengeBlock; - fetcher.credential = self.credential; - fetcher.proxyCredential = self.proxyCredential; - fetcher.authorizer = self.authorizer; - fetcher.cookieStorage = self.cookieStorage; - fetcher.allowedInsecureSchemes = self.allowedInsecureSchemes; - fetcher.allowLocalhostRequest = self.allowLocalhostRequest; - fetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates; - fetcher.configurationBlock = self.configurationBlock; - fetcher.retryEnabled = self.retryEnabled; - fetcher.retryBlock = self.retryBlock; - fetcher.maxRetryInterval = self.maxRetryInterval; - fetcher.minRetryInterval = self.minRetryInterval; - fetcher.properties = self.properties; - fetcher.service = self; - if (self.cookieStorageMethod >= 0) { - [fetcher setCookieStorageMethod:self.cookieStorageMethod]; - } - -#if GTM_BACKGROUND_TASK_FETCHING - fetcher.skipBackgroundTask = self.skipBackgroundTask; -#endif - - NSString *userAgent = self.userAgent; - if (userAgent.length > 0 - && [request valueForHTTPHeaderField:@"User-Agent"] == nil) { - [fetcher setRequestValue:userAgent - forHTTPHeaderField:@"User-Agent"]; - } - fetcher.testBlock = self.testBlock; - - return fetcher; -} - -- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request { - return [self fetcherWithRequest:request - fetcherClass:[GTMSessionFetcher class]]; -} - -- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL { - return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; -} - -- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString { - NSURL *url = [NSURL URLWithString:requestURLString]; - return [self fetcherWithURL:url]; -} - -// Returns a session for the fetcher's host, or nil. -- (NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSURLSession *session = _delegateDispatcher.session; - return session; - } -} - -// Returns a session for the fetcher's host, or nil. For shared sessions, this -// waits on a semaphore, blocking other fetchers while the caller creates the -// session if needed. -- (NSURLSession *)sessionForFetcherCreation { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - if (!_delegateDispatcher) { - // This fetcher is creating a non-shared session, so skip the semaphore usage. - return nil; - } - } - - // Wait if another fetcher is currently creating a session; avoid waiting - // inside the @synchronized block, as that can deadlock. - dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Before getting the NSURLSession for task creation, it is - // important to invalidate and nil out the session discard timer; otherwise - // the session can be invalidated between when it is returned to the - // fetcher, and when the fetcher attempts to create its NSURLSessionTask. - [_delegateDispatcher startSessionUsage]; - - NSURLSession *session = _delegateDispatcher.session; - if (session) { - // The calling fetcher will receive a preexisting session, so - // we can allow other fetchers to create a session. - dispatch_semaphore_signal(_sessionCreationSemaphore); - } else { - // No existing session was obtained, so the calling fetcher will create the session; - // it *must* invoke fetcherDidCreateSession: to signal the dispatcher's semaphore after - // the session has been created (or fails to be created) to avoid a hang. - } - return session; - } -} - -- (id<NSURLSessionDelegate>)sessionDelegate { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateDispatcher; - } -} - -#pragma mark Queue Management - -- (void)addRunningFetcher:(GTMSessionFetcher *)fetcher - forHost:(NSString *)host { - // Add to the array of running fetchers for this host, creating the array if needed. - NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; - if (runningForHost == nil) { - runningForHost = [NSMutableArray arrayWithObject:fetcher]; - [_runningFetchersByHost setObject:runningForHost forKey:host]; - } else { - [runningForHost addObject:fetcher]; - } -} - -- (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher - forHost:(NSString *)host { - // Add to the array of delayed fetchers for this host, creating the array if needed. - NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; - if (delayedForHost == nil) { - delayedForHost = [NSMutableArray arrayWithObject:fetcher]; - [_delayedFetchersByHost setObject:delayedForHost forKey:host]; - } else { - [delayedForHost addObject:fetcher]; - } -} - -- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSString *host = fetcher.request.URL.host; - if (host == nil) { - return NO; - } - NSArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; - NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher]; - BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound); - return isDelayed; - } -} - -- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher { - // Entry point from the fetcher - NSURL *requestURL = fetcher.request.URL; - NSString *host = requestURL.host; - - // Addresses "file:///path" case where localhost is the implicit host. - if (host.length == 0 && [requestURL isFileURL]) { - host = @"localhost"; - } - - if (host.length == 0) { - // Data URIs legitimately have no host, reject other hostless URLs. - GTMSESSION_ASSERT_DEBUG([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher); - return YES; - } - - BOOL shouldBeginResult; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; - if (runningForHost != nil - && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) { - GTMSESSION_ASSERT_DEBUG(NO, @"%@ was already running", fetcher); - return YES; - } - - BOOL shouldRunNow = (fetcher.usingBackgroundSession - || _maxRunningFetchersPerHost == 0 - || _maxRunningFetchersPerHost > - [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]); - if (shouldRunNow) { - [self addRunningFetcher:fetcher forHost:host]; - shouldBeginResult = YES; - } else { - [self addDelayedFetcher:fetcher forHost:host]; - shouldBeginResult = NO; - } - } // @synchronized(self) - - // 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; - - return shouldBeginResult; -} - -- (void)startFetcher:(GTMSessionFetcher *)fetcher { - [fetcher beginFetchMayDelay:NO - mayAuthorize:YES]; -} - -// Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher -// is its own delegate and has no dispatcher. -- (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:(GTMSessionFetcher *)fetcher { - GTMSessionCheckNotSynchronized(self); - - NSURLSession *fetcherSession = fetcher.session; - if (fetcherSession) { - id<NSURLSessionDelegate> fetcherDelegate = fetcherSession.delegate; - BOOL hasDispatcher = (fetcherDelegate != nil && fetcherDelegate != fetcher); - if (hasDispatcher) { - GTMSESSION_ASSERT_DEBUG([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]], - @"Fetcher delegate class: %@", [fetcherDelegate class]); - return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate; - } - } - return nil; -} - -- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher { - if (fetcher.canShareSession) { - NSURLSession *fetcherSession = fetcher.session; - GTMSESSION_ASSERT_DEBUG(fetcherSession != nil, @"Fetcher missing its session: %@", fetcher); - - GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = - [self delegateDispatcherForFetcher:fetcher]; - if (delegateDispatcher) { - GTMSESSION_ASSERT_DEBUG(delegateDispatcher.session == nil, - @"Fetcher made an extra session: %@", fetcher); - - // Save this fetcher's session. - delegateDispatcher.session = fetcherSession; - - // Allow other fetchers to request this session now. - dispatch_semaphore_signal(_sessionCreationSemaphore); - } - } -} - -- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher { - // If this fetcher has a separate delegate with a shared session, then - // this fetcher should be added to the delegate's map of tasks to fetchers. - GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = - [self delegateDispatcherForFetcher:fetcher]; - if (delegateDispatcher) { - GTMSESSION_ASSERT_DEBUG(fetcher.canShareSession, - @"Inappropriate shared session: %@", fetcher); - - // There should already be a session, from this or a previous fetcher. - // - // Sanity check that the fetcher's session is the delegate's shared session. - NSURLSession *sharedSession = delegateDispatcher.session; - NSURLSession *fetcherSession = fetcher.session; - GTMSESSION_ASSERT_DEBUG(sharedSession != nil, @"Missing delegate session: %@", fetcher); - GTMSESSION_ASSERT_DEBUG(fetcherSession == sharedSession, - @"Inconsistent session: %@ %@ (shared: %@)", - fetcher, fetcherSession, sharedSession); - - if (sharedSession != nil && fetcherSession == sharedSession) { - NSURLSessionTask *task = fetcher.sessionTask; - GTMSESSION_ASSERT_DEBUG(task != nil, @"Missing session task: %@", fetcher); - - if (task) { - [delegateDispatcher setFetcher:fetcher - forTask:task]; - } - } - } -} - -- (void)stopFetcher:(GTMSessionFetcher *)fetcher { - [fetcher stopFetching]; -} - -- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher { - // Entry point from the fetcher - NSString *host = fetcher.serviceHost; - if (!host) { - // fetcher has been stopped previously - return; - } - - // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task - // map when the task completes. - GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = - [self delegateDispatcherForFetcher:fetcher]; - [delegateDispatcher removeFetcher:fetcher]; - - NSMutableArray *fetchersToStart; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // If a test is waiting for all fetchers to stop, it needs to wait for this one - // to invoke its callbacks on the callback queue. - [_stoppedFetchersToWaitFor addObject:fetcher]; - - NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; - [runningForHost removeObject:fetcher]; - - NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; - [delayedForHost removeObject:fetcher]; - - while (delayedForHost.count > 0 - && [[self class] numberOfNonBackgroundSessionFetchers:runningForHost] - < _maxRunningFetchersPerHost) { - // Start another delayed fetcher running, scanning for the minimum - // priority value, defaulting to FIFO for equal priorities - GTMSessionFetcher *nextFetcher = nil; - for (GTMSessionFetcher *delayedFetcher in delayedForHost) { - if (nextFetcher == nil - || delayedFetcher.servicePriority < nextFetcher.servicePriority) { - nextFetcher = delayedFetcher; - } - } - - if (nextFetcher) { - [self addRunningFetcher:nextFetcher forHost:host]; - runningForHost = [_runningFetchersByHost objectForKey:host]; - - [delayedForHost removeObjectIdenticalTo:nextFetcher]; - - if (!fetchersToStart) { - fetchersToStart = [NSMutableArray array]; - } - [fetchersToStart addObject:nextFetcher]; - } - } - - if (runningForHost.count == 0) { - // None left; remove the empty array - [_runningFetchersByHost removeObjectForKey:host]; - } - - if (delayedForHost.count == 0) { - [_delayedFetchersByHost removeObjectForKey:host]; - } - } // @synchronized(self) - - // Start fetchers outside of the synchronized block to avoid a deadlock. - for (GTMSessionFetcher *nextFetcher in fetchersToStart) { - [self startFetcher:nextFetcher]; - } - - // The fetcher is no longer in the running or the delayed array, - // so remove its host and thread properties - fetcher.serviceHost = nil; -} - -- (NSUInteger)numberOfFetchers { - NSUInteger running = [self numberOfRunningFetchers]; - NSUInteger delayed = [self numberOfDelayedFetchers]; - return running + delayed; -} - -- (NSUInteger)numberOfRunningFetchers { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSUInteger sum = 0; - for (NSString *host in _runningFetchersByHost) { - NSArray *fetchers = [_runningFetchersByHost objectForKey:host]; - sum += fetchers.count; - } - return sum; - } -} - -- (NSUInteger)numberOfDelayedFetchers { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSUInteger sum = 0; - for (NSString *host in _delayedFetchersByHost) { - NSArray *fetchers = [_delayedFetchersByHost objectForKey:host]; - sum += fetchers.count; - } - return sum; - } -} - -- (NSArray *)issuedFetchers { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSMutableArray *allFetchers = [NSMutableArray array]; - void (^accumulateFetchers)(id, id, BOOL *) = ^(NSString *host, - NSArray *fetchersForHost, - BOOL *stop) { - [allFetchers addObjectsFromArray:fetchersForHost]; - }; - [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; - [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; - - GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count, - @"Fetcher appears multiple times\n running: %@\n delayed: %@", - _runningFetchersByHost, _delayedFetchersByHost); - - return allFetchers.count > 0 ? allFetchers : nil; - } -} - -- (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL { - NSString *host = requestURL.host; - if (host.length == 0) return nil; - - NSURL *targetURL = [requestURL absoluteURL]; - - NSArray *allFetchers = [self issuedFetchers]; - NSIndexSet *indexes = [allFetchers indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher, - NSUInteger idx, - BOOL *stop) { - NSURL *fetcherURL = [fetcher.request.URL absoluteURL]; - return [fetcherURL isEqual:targetURL]; - }]; - - NSArray *result = nil; - if (indexes.count > 0) { - result = [allFetchers objectsAtIndexes:indexes]; - } - return result; -} - -- (void)stopAllFetchers { - NSArray *delayedFetchersByHost; - NSArray *runningFetchersByHost; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Set the time barrier so fetchers know not to call back even if - // the stop calls below occur after the fetchers naturally - // stopped and so were removed from _runningFetchersByHost, - // but while the callbacks were already enqueued before stopAllFetchers - // was invoked. - _stoppedAllFetchersDate = [[NSDate alloc] init]; - - // Remove fetchers from the delayed list to avoid fetcherDidStop: from - // starting more fetchers running as a side effect of stopping one - delayedFetchersByHost = _delayedFetchersByHost.allValues; - [_delayedFetchersByHost removeAllObjects]; - - runningFetchersByHost = _runningFetchersByHost.allValues; - [_runningFetchersByHost removeAllObjects]; - } - - for (NSArray *delayedForHost in delayedFetchersByHost) { - for (GTMSessionFetcher *fetcher in delayedForHost) { - [self stopFetcher:fetcher]; - } - } - - for (NSArray *runningForHost in runningFetchersByHost) { - for (GTMSessionFetcher *fetcher in runningForHost) { - [self stopFetcher:fetcher]; - } - } -} - -- (NSDate *)stoppedAllFetchersDate { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _stoppedAllFetchersDate; - } -} - -#pragma mark Accessors - -- (BOOL)reuseSession { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateDispatcher != nil; - } -} - -- (void)setReuseSession:(BOOL)shouldReuse { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - BOOL wasReusing = (_delegateDispatcher != nil); - if (shouldReuse != wasReusing) { - [self abandonDispatcher]; - if (shouldReuse) { - _delegateDispatcher = - [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self - sessionDiscardInterval:_unusedSessionTimeout]; - } else { - _delegateDispatcher = nil; - } - } - } -} - -- (void)resetSession { - GTMSessionCheckNotSynchronized(self); - dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - [self resetSessionInternal]; - } - - dispatch_semaphore_signal(_sessionCreationSemaphore); -} - -- (void)resetSessionInternal { - GTMSessionCheckSynchronized(self); - - // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions. - if (_delegateDispatcher) { - [self abandonDispatcher]; - _delegateDispatcher = - [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self - sessionDiscardInterval:_unusedSessionTimeout]; - } -} - -- (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer { - GTMSessionCheckNotSynchronized(self); - - dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_delegateDispatcher.discardTimer == timer) { - // If the delegate dispatcher's current discardTimer is the same object as the timer - // that fired, no fetcher has recently attempted to start using the session by calling - // startSessionUsage, which invalidates and nils out the timer. - [self resetSessionInternal]; - } else { - // A fetcher has invalidated the timer between its triggering and now, potentially - // meaning a fetcher has requested access to the NSURLSession, and may be in the process - // of starting a new task. The dispatcher should not be abandoned, as this can lead - // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession - // and the fetcher attempting to create a new task. - } - } - - dispatch_semaphore_signal(_sessionCreationSemaphore); -} - -- (NSTimeInterval)unusedSessionTimeout { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _unusedSessionTimeout; - } -} - -- (void)setUnusedSessionTimeout:(NSTimeInterval)timeout { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _unusedSessionTimeout = timeout; - _delegateDispatcher.discardInterval = timeout; - } -} - -// This method should be called inside of @synchronized(self) -- (void)abandonDispatcher { - GTMSessionCheckSynchronized(self); - [_delegateDispatcher abandon]; -} - -- (NSDictionary *)runningFetchersByHost { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_runningFetchersByHost copy]; - } -} - -- (void)setRunningFetchersByHost:(NSDictionary *)dict { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _runningFetchersByHost = [dict mutableCopy]; - } -} - -- (NSDictionary *)delayedFetchersByHost { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_delayedFetchersByHost copy]; - } -} - -- (void)setDelayedFetchersByHost:(NSDictionary *)dict { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delayedFetchersByHost = [dict mutableCopy]; - } -} - -- (id<GTMFetcherAuthorizationProtocol>)authorizer { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _authorizer; - } -} - -- (void)setAuthorizer:(id<GTMFetcherAuthorizationProtocol>)obj { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (obj != _authorizer) { - [self detachAuthorizer]; - } - - _authorizer = obj; - } - - // Use the fetcher service for the authorization fetches if the auth - // object supports fetcher services - if ([obj respondsToSelector:@selector(setFetcherService:)]) { -#if GTM_USE_SESSION_FETCHER - [obj setFetcherService:self]; -#else - [obj setFetcherService:(id)self]; -#endif - } -} - -// This should be called inside a @synchronized(self) block except during dealloc. -- (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)]) { - id authFetcherService = [_authorizer fetcherService]; - if (authFetcherService == self) { - [_authorizer setFetcherService:nil]; - } - } -} - -- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _callbackQueue; - } // @synchronized(self) -} - -- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _callbackQueue = queue ?: dispatch_get_main_queue(); - } // @synchronized(self) -} - -- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateQueue; - } // @synchronized(self) -} - -- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delegateQueue = queue ?: [NSOperationQueue mainQueue]; - } // @synchronized(self) -} - -- (NSOperationQueue *)delegateQueue { - // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects - // any custom delegate queue for calling the app. - return nil; -} - -+ (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers { - NSUInteger sum = 0; - for (GTMSessionFetcher *fetcher in fetchers) { - if (!fetcher.usingBackgroundSession) { - ++sum; - } - } - return sum; -} - -@end - -@implementation GTMSessionFetcherService (TestingSupport) - -+ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil - fakedError:(NSError *)fakedErrorOrNil { -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - NSURL *url = [NSURL URLWithString:@"http://example.invalid"]; - NSHTTPURLResponse *fakedResponse = - [[NSHTTPURLResponse alloc] initWithURL:url - statusCode:(fakedErrorOrNil ? 500 : 200) - HTTPVersion:@"HTTP/1.1" - headerFields:nil]; - return [self mockFetcherServiceWithFakedData:fakedDataOrNil - fakedResponse:fakedResponse - fakedError:fakedErrorOrNil]; -#else - GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); - return nil; -#endif // GTM_DISABLE_FETCHER_TEST_BLOCK -} - -+ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil - fakedResponse:(NSHTTPURLResponse *)fakedResponse - fakedError:(NSError *)fakedErrorOrNil { -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - GTMSessionFetcherService *service = [[self alloc] init]; - service.allowedInsecureSchemes = @[ @"http" ]; - service.testBlock = ^(GTMSessionFetcher *fetcherToTest, - GTMSessionFetcherTestResponse testResponse) { - testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil); - }; - return service; -#else - GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); - return nil; -#endif // GTM_DISABLE_FETCHER_TEST_BLOCK -} - -#pragma mark Synchronous Wait for Unit Testing - -- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds { - NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; - _stoppedFetchersToWaitFor = [NSMutableArray array]; - - BOOL shouldSpinRunLoop = [NSThread isMainThread]; - const NSTimeInterval kSpinInterval = 0.001; - BOOL didTimeOut = NO; - while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) { - didTimeOut = [giveUpDate timeIntervalSinceNow] < 0; - if (didTimeOut) break; - - GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject; - if (stoppedFetcher) { - [_stoppedFetchersToWaitFor removeObject:stoppedFetcher]; - [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval]; - } - - if (shouldSpinRunLoop) { - NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; - [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; - } else { - [NSThread sleepForTimeInterval:kSpinInterval]; - } - } - _stoppedFetchersToWaitFor = nil; - - return !didTimeOut; -} - -@end - -@implementation GTMSessionFetcherService (BackwardsCompatibilityOnly) - -- (NSInteger)cookieStorageMethod { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _cookieStorageMethod; - } -} - -- (void)setCookieStorageMethod:(NSInteger)cookieStorageMethod { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _cookieStorageMethod = cookieStorageMethod; - } -} - -@end - -@implementation GTMSessionFetcherSessionDelegateDispatcher { - __weak GTMSessionFetcherService *_parentService; - NSURLSession *_session; - - // The task map maps NSURLSessionTasks to GTMSessionFetchers - NSMutableDictionary *_taskToFetcherMap; - // The discard timer will invalidate sessions after the session's last task completes. - NSTimer *_discardTimer; - NSTimeInterval _discardInterval; -} - -@synthesize discardInterval = _discardInterval, - session = _session; - -- (instancetype)init { - [self doesNotRecognizeSelector:_cmd]; - return nil; -} - -- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService - sessionDiscardInterval:(NSTimeInterval)discardInterval { - self = [super init]; - if (self) { - _discardInterval = discardInterval; - _parentService = parentService; - } - return self; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p %@ %@", - [self class], self, - _session ?: @"<no session>", - _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @"<no tasks>"]; -} - -- (NSTimer *)discardTimer { - GTMSessionCheckNotSynchronized(self); - @synchronized(self) { - return _discardTimer; - } -} - -// This method should be called inside of a @synchronized(self) block. -- (void)startDiscardTimer { - GTMSessionCheckSynchronized(self); - [_discardTimer invalidate]; - _discardTimer = nil; - if (_discardInterval > 0) { - _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval - target:self - selector:@selector(discardTimerFired:) - userInfo:nil - repeats:NO]; - [_discardTimer setTolerance:(_discardInterval / 10)]; - [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes]; - } -} - -// This method should be called inside of a @synchronized(self) block. -- (void)destroyDiscardTimer { - GTMSessionCheckSynchronized(self); - [_discardTimer invalidate]; - _discardTimer = nil; -} - -- (void)discardTimerFired:(NSTimer *)timer { - GTMSessionFetcherService *service; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSUInteger numberOfTasks = _taskToFetcherMap.count; - if (numberOfTasks == 0) { - service = _parentService; - } - } - - // Inform the service that the discard timer has fired, and should check whether the - // service can abandon us. -resetSession cannot be called directly, as there is a - // race condition that must be guarded against with the NSURLSession being returned - // from sessionForFetcherCreation outside other locks. The service can take steps - // to prevent resetting the session if that has occurred. - // - // The service must be called from outside the @synchronized block. - [service resetSessionForDispatcherDiscardTimer:timer]; -} - -- (void)abandon { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self destroySessionAndTimer]; - } -} - -- (void)startSessionUsage { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self destroyDiscardTimer]; - } -} - -// This method should be called inside of a @synchronized(self) block. -- (void)destroySessionAndTimer { - GTMSessionCheckSynchronized(self); - [self destroyDiscardTimer]; - - // Break any retain cycle from the session holding the delegate. - [_session finishTasksAndInvalidate]; - - // Immediately clear the session so no new task may be issued with it. - // - // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish. - _session = nil; -} - -- (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task { - GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher"); - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_taskToFetcherMap == nil) { - _taskToFetcherMap = [[NSMutableDictionary alloc] init]; - } - - if (fetcher) { - [_taskToFetcherMap setObject:fetcher forKey:task]; - [self destroyDiscardTimer]; - } - } -} - -- (void)removeFetcher:(GTMSessionFetcher *)fetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Typically, a fetcher should be removed when its task invokes - // URLSession:task:didCompleteWithError:. - // - // When fetching with a testBlock, though, the task completed delegate - // method may not be invoked, requiring cleanup here. - NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher]; - GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks); - [_taskToFetcherMap removeObjectsForKeys:tasks]; - - if (_taskToFetcherMap.count == 0) { - [self startDiscardTimer]; - } - } -} - -// This helper method provides synchronized access to the task map for the delegate -// methods below. -- (id)fetcherForTask:(NSURLSessionTask *)task { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_taskToFetcherMap objectForKey:task]; - } -} - -- (void)removeTaskFromMap:(NSURLSessionTask *)task { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [_taskToFetcherMap removeObjectForKey:task]; - } -} - -- (void)setSession:(NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _session = session; - } -} - -- (NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _session; - } -} - -- (NSTimeInterval)discardInterval { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _discardInterval; - } -} - -- (void)setDiscardInterval:(NSTimeInterval)interval { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _discardInterval = interval; - } -} - -// NSURLSessionDelegate protocol methods. - -// - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session; -// -// TODO(seh): How do we route this to an appropriate fetcher? - - -- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", - [self class], self, session, error); - NSDictionary *localTaskToFetcherMap; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _session = nil; - - localTaskToFetcherMap = [_taskToFetcherMap copy]; - } - - // Any "suspended" tasks may not have received callbacks from NSURLSession when the session - // completes; we'll call them now. - [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(NSURLSessionTask *task, - GTMSessionFetcher *fetcher, - BOOL *stop) { - if (fetcher.session == session) { - // Our delegate method URLSession:task:didCompleteWithError: will rely on - // _taskToFetcherMap so that should still contain this fetcher. - NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain - code:NSURLErrorCancelled - userInfo:nil]; - [self URLSession:session task:task didCompleteWithError:canceledError]; - } else { - GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)", - fetcher, fetcher.session, session); - } - }]; - - // Our tests rely on this notification to know the session discard timer fired. - NSDictionary *userInfo = @{ kGTMSessionFetcherServiceSessionKey : session }; - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification - object:_parentService - userInfo:userInfo]; -} - - -#pragma mark - NSURLSessionTaskDelegate - -// NSURLSessionTaskDelegate protocol methods. -// -// We won't test here if the fetcher responds to these since we only want this -// class to implement the same delegate methods the fetcher does (so NSURLSession's -// tests for respondsToSelector: will have the same result whether the session -// delegate is the fetcher or this dispatcher.) - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -willPerformHTTPRedirection:(NSHTTPURLResponse *)response - newRequest:(NSURLRequest *)request - completionHandler:(void (^)(NSURLRequest *))completionHandler { - id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; - [fetcher URLSession:session - task:task -willPerformHTTPRedirection:response - newRequest:request - completionHandler:completionHandler]; -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler { - id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; - [fetcher URLSession:session - task:task - didReceiveChallenge:challenge - completionHandler:handler]; -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler { - id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; - [fetcher URLSession:session - task:task - needNewBodyStream:handler]; -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - didSendBodyData:(int64_t)bytesSent - totalBytesSent:(int64_t)totalBytesSent -totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { - id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; - [fetcher URLSession:session - task:task - didSendBodyData:bytesSent - totalBytesSent:totalBytesSent -totalBytesExpectedToSend:totalBytesExpectedToSend]; -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didCompleteWithError:(NSError *)error { - id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task]; - - // This is the usual way tasks are removed from the task map. - [self removeTaskFromMap:task]; - - [fetcher URLSession:session - task:task - didCompleteWithError:error]; -} - -// NSURLSessionDataDelegate protocol methods. - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didReceiveResponse:(NSURLResponse *)response - completionHandler:(void (^)(NSURLSessionResponseDisposition))handler { - id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; - [fetcher URLSession:session - dataTask:dataTask - didReceiveResponse:response - completionHandler:handler]; -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { - id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; - GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask); - [self removeTaskFromMap:dataTask]; - if (fetcher) { - GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]], - @"Expecting GTMSessionFetcher"); - [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask]; - } - - [fetcher URLSession:session - dataTask:dataTask -didBecomeDownloadTask:downloadTask]; -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - didReceiveData:(NSData *)data { - id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; - [fetcher URLSession:session - dataTask:dataTask - didReceiveData:data]; -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - willCacheResponse:(NSCachedURLResponse *)proposedResponse - completionHandler:(void (^)(NSCachedURLResponse *))handler { - id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; - [fetcher URLSession:session - dataTask:dataTask - willCacheResponse:proposedResponse - completionHandler:handler]; -} - -// NSURLSessionDownloadDelegate protocol methods. - -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask -didFinishDownloadingToURL:(NSURL *)location { - id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask]; - [fetcher URLSession:session - downloadTask:downloadTask -didFinishDownloadingToURL:location]; -} - -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask - didWriteData:(int64_t)bytesWritten - totalBytesWritten:(int64_t)totalWritten -totalBytesExpectedToWrite:(int64_t)totalExpected { - id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask]; - [fetcher URLSession:session - downloadTask:downloadTask - didWriteData:bytesWritten - totalBytesWritten:totalWritten -totalBytesExpectedToWrite:totalExpected]; -} - -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask - didResumeAtOffset:(int64_t)fileOffset -expectedTotalBytes:(int64_t)expectedTotalBytes { - id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask]; - [fetcher URLSession:session - downloadTask:downloadTask - didResumeAtOffset:fileOffset - expectedTotalBytes:expectedTotalBytes]; -} - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h @@ -1,166 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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. - */ - -// GTMSessionUploadFetcher implements Google's resumable upload protocol. - -// -// This subclass of GTMSessionFetcher simulates the series of fetches -// needed for chunked upload as a single fetch operation. -// -// Protocol document: TBD -// -// To the client, the only fetcher that exists is this class; the subsidiary -// fetchers needed for uploading chunks are not visible (though the most recent -// chunk fetcher may be accessed via the -activeFetcher or -chunkFetcher methods, and -// -responseHeaders and -statusCode reflect results from the most recent chunk -// fetcher.) -// -// Chunk fetchers are discarded as soon as they have completed. -// -// The protocol also allows for a cancellation notification request to be sent to the -// server to allow discarding of the currently uploaded data and this will be sent -// automatically upon calling stopFetching if the upload has already started. -// -// Note: Unlike the fetcher superclass, the methods of GTMSessionUploadFetcher should -// only be used from the main thread until further work is done to make this subclass -// thread-safe. - -#import "GTMSessionFetcher.h" -#import "GTMSessionFetcherService.h" - -GTM_ASSUME_NONNULL_BEGIN - -// The value to use for file size parameters when the file size is not yet known. -extern int64_t const kGTMSessionUploadFetcherUnknownFileSize; - -// Unless an application knows it needs a smaller chunk size, it should use the standard -// chunk size, which sends the entire file as a single chunk to minimize upload overhead. -// Setting an explicit chunk size that comfortably fits in memory is advisable for large -// uploads. -extern int64_t const kGTMSessionUploadFetcherStandardChunkSize; - -// When uploading requires data buffer allocations (such as uploading from an NSData or -// an NSFileHandle) this is the maximum buffer size that will be created by the fetcher. -extern int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize; - -// Notification that the upload location URL was provided by the server. -extern NSString *const kGTMSessionFetcherUploadLocationObtainedNotification; - -// Block to provide data during uploads. -// -// Response data may be allocated with dataWithBytesNoCopy:length:freeWhenDone: for efficiency, -// and released after the response block returns. -// -// If the length of the file being uploaded is unknown or already set, send -// kGTMSessionUploadFetcherUnknownFileSize for |fullUploadLength|. Otherwise, set |fullUploadLength| -// to its proper value. -// -// Pass nil as the data (and optionally an NSError) for a failure. -typedef void (^GTMSessionUploadFetcherDataProviderResponse)(NSData * GTM_NULLABLE_TYPE data, - int64_t fullUploadLength, - NSError * GTM_NULLABLE_TYPE error); -// Do not call the response with an NSData object with less data than the requested length unless -// you are passing the fullUploadLength to the fetcher for the first time and it is the last chunk -// of data in the file being uploaded. -typedef void (^GTMSessionUploadFetcherDataProvider)(int64_t offset, int64_t length, - GTMSessionUploadFetcherDataProviderResponse response); - -// Block to be notified about the final status of the cancellation request started in stopFetching. -// -// |fetcher| will be the cancel request that was sent to the server, or nil if stopFetching is not -// going to send a cancel request. If |fetcher| is provided, the other parameters correspond to the -// completion handler of the cancellation request fetcher. -typedef void (^GTMSessionUploadFetcherCancellationHandler)( - GTMSessionFetcher * GTM_NULLABLE_TYPE fetcher, - NSData * GTM_NULLABLE_TYPE data, - NSError * GTM_NULLABLE_TYPE error); - -@interface GTMSessionUploadFetcher : GTMSessionFetcher - -// Create an upload fetcher specifying either the request or the resume location URL, -// then set an upload data source using one of these: -// -// setUploadFileURL: -// setUploadDataLength:provider: -// setUploadFileHandle: -// setUploadData: - -+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; - -+ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; - -// Allows dataProviders for files of unknown length. Pass kGTMSessionUploadFetcherUnknownFileSize as -// |fullLength| if the length is unknown. -- (void)setUploadDataLength:(int64_t)fullLength - provider:(GTM_NULLABLE GTMSessionUploadFetcherDataProvider)block; - -+ (NSArray *)uploadFetchersForBackgroundSessions; -+ (GTM_NULLABLE instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier; - -- (void)pauseFetching; -- (void)resumeFetching; -- (BOOL)isPaused; - -@property(atomic, strong, GTM_NULLABLE) NSURL *uploadLocationURL; -@property(atomic, strong, GTM_NULLABLE) NSData *uploadData; -@property(atomic, strong, GTM_NULLABLE) NSURL *uploadFileURL; -@property(atomic, strong, GTM_NULLABLE) NSFileHandle *uploadFileHandle; -@property(atomic, copy, readonly, GTM_NULLABLE) GTMSessionUploadFetcherDataProvider uploadDataProvider; -@property(atomic, copy) NSString *uploadMIMEType; -@property(atomic, assign) int64_t chunkSize; -@property(atomic, readonly, assign) int64_t currentOffset; - -// The fetcher for the current data chunk, if any -@property(atomic, strong, GTM_NULLABLE) GTMSessionFetcher *chunkFetcher; - -// The active fetcher is the current chunk fetcher, or the upload fetcher itself -// if no chunk fetcher has yet been created. -@property(atomic, readonly) GTMSessionFetcher *activeFetcher; - -// The last request made by an active fetcher. Useful for testing. -@property(atomic, readonly, GTM_NULLABLE) NSURLRequest *lastChunkRequest; - -// The status code from the most recently-completed fetch. -@property(atomic, assign) NSInteger statusCode; - -// Invoked as part of the stop fetching process. Invoked immediately if there is no upload in -// progress, otherwise invoked with the results of the attempt to notify the server that the -// upload will not continue. -// -// Unlike other callbacks, since this is related specifically to the stopFetching flow it is not -// cleared by stopFetching. It will instead clear itself after it is invoked or if the completion -// has occured before stopFetching is called. -@property(atomic, copy, GTM_NULLABLE) GTMSessionUploadFetcherCancellationHandler - cancellationHandler; - -// Exposed for testing only. -@property(atomic, readonly, GTM_NULLABLE) dispatch_queue_t delegateCallbackQueue; -@property(atomic, readonly, GTM_NULLABLE) GTMSessionFetcherCompletionHandler delegateCompletionHandler; - -@end - -@interface GTMSessionFetcher (GTMSessionUploadFetcherMethods) - -@property(readonly, GTM_NULLABLE) GTMSessionUploadFetcher *parentUploadFetcher; - -@end - -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m @@ -1,1954 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * 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 !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#import "GTMSessionUploadFetcher.h" - -static NSString *const kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey = @"_upChunk"; -static NSString *const kGTMSessionIdentifierUploadFileURLMetadataKey = @"_upFileURL"; -static NSString *const kGTMSessionIdentifierUploadFileLengthMetadataKey = @"_upFileLen"; -static NSString *const kGTMSessionIdentifierUploadLocationURLMetadataKey = @"_upLocURL"; -static NSString *const kGTMSessionIdentifierUploadMIMETypeMetadataKey = @"_uploadMIME"; -static NSString *const kGTMSessionIdentifierUploadChunkSizeMetadataKey = @"_upChSize"; -static NSString *const kGTMSessionIdentifierUploadCurrentOffsetMetadataKey = @"_upOffset"; - -static NSString *const kGTMSessionHeaderXGoogUploadChunkGranularity = @"X-Goog-Upload-Chunk-Granularity"; -static NSString *const kGTMSessionHeaderXGoogUploadCommand = @"X-Goog-Upload-Command"; -static NSString *const kGTMSessionHeaderXGoogUploadContentLength = @"X-Goog-Upload-Content-Length"; -static NSString *const kGTMSessionHeaderXGoogUploadContentType = @"X-Goog-Upload-Content-Type"; -static NSString *const kGTMSessionHeaderXGoogUploadOffset = @"X-Goog-Upload-Offset"; -static NSString *const kGTMSessionHeaderXGoogUploadProtocol = @"X-Goog-Upload-Protocol"; -static NSString *const kGTMSessionXGoogUploadProtocolResumable = @"resumable"; -static NSString *const kGTMSessionHeaderXGoogUploadSizeReceived = @"X-Goog-Upload-Size-Received"; -static NSString *const kGTMSessionHeaderXGoogUploadStatus = @"X-Goog-Upload-Status"; -static NSString *const kGTMSessionHeaderXGoogUploadURL = @"X-Goog-Upload-URL"; - -// Property of chunk fetchers identifying the parent upload fetcher. Non-retained NSValue. -static NSString *const kGTMSessionUploadFetcherChunkParentKey = @"_uploadFetcherChunkParent"; - -int64_t const kGTMSessionUploadFetcherUnknownFileSize = -1; - -int64_t const kGTMSessionUploadFetcherStandardChunkSize = (int64_t)LLONG_MAX; - -#if TARGET_OS_IPHONE -int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 10 * 1024 * 1024; // 10 MB for iOS, watchOS, tvOS -#else -int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 100 * 1024 * 1024; // 100 MB for macOS -#endif - -typedef NS_ENUM(NSUInteger, GTMSessionUploadFetcherStatus) { - kStatusUnknown, - kStatusActive, - kStatusFinal, - kStatusCancelled, -}; - -NSString *const kGTMSessionFetcherUploadLocationObtainedNotification = - @"kGTMSessionFetcherUploadLocationObtainedNotification"; - -#if !GTMSESSION_BUILD_COMBINED_SOURCES -@interface GTMSessionFetcher (ProtectedMethods) - -// Access to non-public method on the parent fetcher class. -- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks; -- (void)createSessionIdentifierWithMetadata:(NSDictionary *)metadata; -- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(id)target - didFinishSelector:(SEL)finishedSelector; -- (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue - afterUserStopped:(BOOL)afterStopped - block:(void (^)(void))block; -- (NSTimer *)retryTimer; -- (void)beginFetchForRetry; - -@property(readwrite, strong) NSData *downloadedData; -- (void)releaseCallbacks; - -- (NSInteger)statusCodeUnsynchronized; - -- (BOOL)userStoppedFetching; - -@end -#endif // !GTMSESSION_BUILD_COMBINED_SOURCES - -@interface GTMSessionUploadFetcher () - -// Changing readonly to readwrite. -@property(atomic, strong, readwrite) NSURLRequest *lastChunkRequest; -@property(atomic, readwrite, assign) int64_t currentOffset; - -// Internal properties. -@property(strong, atomic, GTM_NULLABLE) GTMSessionFetcher *fetcherInFlight; // Synchronized on self. - -@property(assign, atomic, getter=isSubdataGenerating) BOOL subdataGenerating; -@property(assign, atomic) BOOL shouldInitiateOffsetQuery; -@property(assign, atomic) int64_t uploadGranularity; - -@end - -@implementation GTMSessionUploadFetcher { - GTMSessionFetcher *_chunkFetcher; - - // We'll call through to the delegate's completion handler. - GTMSessionFetcherCompletionHandler _delegateCompletionHandler; - dispatch_queue_t _delegateCallbackQueue; - - // The initial fetch's body length and bytes actually sent are - // needed for calculating progress during subsequent chunk uploads - int64_t _initialBodyLength; - int64_t _initialBodySent; - - // The upload server address for the chunks of this upload session. - NSURL *_uploadLocationURL; - - // _uploadData, _uploadDataProvider, or _uploadFileHandle may be set, but only one. - NSData *_uploadData; - NSFileHandle *_uploadFileHandle; - GTMSessionUploadFetcherDataProvider _uploadDataProvider; - NSURL *_uploadFileURL; - int64_t _uploadFileLength; - NSString *_uploadMIMEType; - int64_t _chunkSize; - int64_t _uploadGranularity; - BOOL _isPaused; - BOOL _isRestartedUpload; - BOOL _shouldInitiateOffsetQuery; - - // Tied to useBackgroundSession property, since this property is applicable to chunk fetchers. - BOOL _useBackgroundSessionOnChunkFetchers; - - // We keep the latest offset into the upload data just for progress reporting. - int64_t _currentOffset; - - NSDictionary *_recentChunkReponseHeaders; - NSInteger _recentChunkStatusCode; - - // For waiting, we need to know the fetcher in flight, if any, and if subdata generation - // is in progress. - GTMSessionFetcher *_fetcherInFlight; - BOOL _isSubdataGenerating; - BOOL _isCancelInFlight; - - GTMSessionUploadFetcherCancellationHandler _cancellationHandler; -} - -+ (void)load { - [self uploadFetchersForBackgroundSessions]; -} - -+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTMSessionFetcherService *)fetcherService { - GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:request - fetcherService:fetcherService]; - [fetcher setLocationURL:nil - uploadMIMEType:uploadMIMEType - chunkSize:chunkSize]; - return fetcher; -} - -+ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTMSessionFetcherService *)fetcherService { - GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:nil - fetcherService:fetcherService]; - [fetcher setLocationURL:uploadLocationURL - uploadMIMEType:uploadMIMEType - chunkSize:chunkSize]; - return fetcher; -} - -+ (instancetype)uploadFetcherForSessionIdentifierMetadata:(NSDictionary *)metadata { - GTMSESSION_ASSERT_DEBUG( - [metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue], - @"Session identifier metadata is not for an upload fetcher: %@", metadata); - - NSNumber *uploadFileLengthNum = metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey]; - GTMSESSION_ASSERT_DEBUG(uploadFileLengthNum != nil, - @"Session metadata missing an UploadFileSize"); - if (uploadFileLengthNum == nil) return nil; - - int64_t uploadFileLength = [uploadFileLengthNum longLongValue]; - GTMSESSION_ASSERT_DEBUG(uploadFileLength >= 0, @"Session metadata UploadFileSize is unknown"); - - NSString *uploadFileURLString = metadata[kGTMSessionIdentifierUploadFileURLMetadataKey]; - GTMSESSION_ASSERT_DEBUG(uploadFileURLString, @"Session metadata missing an UploadFileURL"); - if (uploadFileURLString == nil) return nil; - - NSURL *uploadFileURL = [NSURL URLWithString:uploadFileURLString]; - // There used to be a call here to NSURL checkResourceIsReachableAndReturnError: to check for the - // existence of the file (also tried NSFileManager fileExistsAtPath:). We've determined - // empirically that the check can fail at startup even when the upload file does in fact exist. - // For now, we'll go ahead and restore the background upload fetcher. If the file doesn't exist, - // it will fail later. - - NSString *uploadLocationURLString = metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey]; - NSURL *uploadLocationURL = - uploadLocationURLString ? [NSURL URLWithString:uploadLocationURLString] : nil; - - NSString *uploadMIMEType = - metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey]; - int64_t uploadChunkSize = - [metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] longLongValue]; - if (uploadChunkSize <= 0) { - uploadChunkSize = kGTMSessionUploadFetcherStandardChunkSize; - } - int64_t currentOffset = - [metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] longLongValue]; - GTMSESSION_ASSERT_DEBUG(currentOffset <= uploadFileLength, - @"CurrentOffset (%lld) exceeds UploadFileSize (%lld)", - currentOffset, uploadFileLength); - if (currentOffset > uploadFileLength) return nil; - - GTMSessionUploadFetcher *uploadFetcher = [self uploadFetcherWithLocation:uploadLocationURL - uploadMIMEType:uploadMIMEType - chunkSize:uploadChunkSize - fetcherService:nil]; - // Set the upload file length before setting the upload file URL tries to determine the length. - [uploadFetcher setUploadFileLength:uploadFileLength]; - - uploadFetcher.uploadFileURL = uploadFileURL; - uploadFetcher.sessionUserInfo = metadata; - uploadFetcher.useBackgroundSession = YES; - uploadFetcher.currentOffset = currentOffset; - uploadFetcher.delegateCallbackQueue = uploadFetcher.callbackQueue; - uploadFetcher.allowedInsecureSchemes = @[ @"http" ]; // Allowed on restored upload fetcher. - return uploadFetcher; -} - -+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request - fetcherService:(GTMSessionFetcherService *)fetcherService { - // Internal utility method for instantiating fetchers - GTMSessionUploadFetcher *fetcher; - if ([fetcherService isKindOfClass:[GTMSessionFetcherService class]]) { - fetcher = [fetcherService fetcherWithRequest:request - fetcherClass:self]; - } else { - fetcher = [self fetcherWithRequest:request]; - } - fetcher.useBackgroundSession = YES; - return fetcher; -} - -+ (NSPointerArray *)uploadFetcherPointerArrayForBackgroundSessions { - static NSPointerArray *gUploadFetcherPointerArrayForBackgroundSessions = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - gUploadFetcherPointerArrayForBackgroundSessions = [NSPointerArray weakObjectsPointerArray]; - }); - return gUploadFetcherPointerArrayForBackgroundSessions; -} - -+ (instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier { - GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); - NSArray *uploadFetchersForBackgroundSessions = [self uploadFetchersForBackgroundSessions]; - for (GTMSessionUploadFetcher *uploadFetcher in uploadFetchersForBackgroundSessions) { - if ([uploadFetcher.chunkFetcher.sessionIdentifier isEqual:sessionIdentifier]) { - return uploadFetcher; - } - } - return nil; -} - -+ (NSArray *)uploadFetchersForBackgroundSessions { - // Collect the background session upload fetchers that are still in memory. - NSPointerArray *uploadFetcherPointerArray = [self uploadFetcherPointerArrayForBackgroundSessions]; - [uploadFetcherPointerArray compact]; - NSMutableSet *restoredSessionIdentifiers = [[NSMutableSet alloc] init]; - NSMutableArray *uploadFetchers = [[NSMutableArray alloc] init]; - for (GTMSessionUploadFetcher *uploadFetcher in uploadFetcherPointerArray) { - NSString *sessionIdentifier = uploadFetcher.chunkFetcher.sessionIdentifier; - if (sessionIdentifier) { - [restoredSessionIdentifiers addObject:sessionIdentifier]; - [uploadFetchers addObject:uploadFetcher]; - } - } - - // The system may have other ongoing background upload sessions. Restore upload fetchers for those - // too. - NSArray *fetchers = [GTMSessionFetcher fetchersForBackgroundSessions]; - for (GTMSessionFetcher *fetcher in fetchers) { - NSString *sessionIdentifier = fetcher.sessionIdentifier; - if (!sessionIdentifier || [restoredSessionIdentifiers containsObject:sessionIdentifier]) { - continue; - } - NSDictionary *sessionIdentifierMetadata = [fetcher sessionIdentifierMetadata]; - if (sessionIdentifierMetadata == nil) { - continue; - } - if (![sessionIdentifierMetadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue]) { - continue; - } - GTMSessionUploadFetcher *uploadFetcher = - [self uploadFetcherForSessionIdentifierMetadata:sessionIdentifierMetadata]; - if (uploadFetcher == nil) { - // Something went wrong with this upload fetcher, so kill the restored chunk fetcher. - [fetcher stopFetching]; - continue; - } - [uploadFetchers addObject:uploadFetcher]; - uploadFetcher->_chunkFetcher = fetcher; - uploadFetcher->_fetcherInFlight = fetcher; - [uploadFetcher attachSendProgressBlockToChunkFetcher:fetcher]; - fetcher.completionHandler = - [fetcher completionHandlerWithTarget:uploadFetcher - didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)]; - - GTMSESSION_LOG_DEBUG(@"%@ restoring upload fetcher %@ for chunk fetcher %@", - [self class], uploadFetcher, fetcher); - } - return uploadFetchers; -} - -- (void)setUploadData:(NSData *)data { - BOOL changed = NO; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_uploadData != data) { - _uploadData = data; - changed = YES; - } - } - if (changed) { - [self setupRequestHeaders]; - } -} - -- (NSData *)uploadData { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _uploadData; - } -} - -- (void)setUploadFileHandle:(NSFileHandle *)fh { - BOOL changed = NO; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_uploadFileHandle != fh) { - _uploadFileHandle = fh; - changed = YES; - } - } - if (changed) { - [self setupRequestHeaders]; - } -} - -- (NSFileHandle *)uploadFileHandle { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _uploadFileHandle; - } -} - -- (void)setUploadFileURL:(NSURL *)uploadURL { - BOOL changed = NO; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_uploadFileURL != uploadURL) { - _uploadFileURL = uploadURL; - changed = YES; - } - } - if (changed) { - [self setupRequestHeaders]; - } -} - -- (NSURL *)uploadFileURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _uploadFileURL; - } -} - -- (void)setUploadFileLength:(int64_t)fullLength { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize && - fullLength != kGTMSessionUploadFetcherUnknownFileSize) { - _uploadFileLength = fullLength; - } - } -} - -- (void)setUploadDataLength:(int64_t)fullLength - provider:(GTMSessionUploadFetcherDataProvider)block { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _uploadDataProvider = [block copy]; - _uploadFileLength = fullLength; - } - [self setupRequestHeaders]; -} - -- (GTMSessionUploadFetcherDataProvider)uploadDataProvider { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _uploadDataProvider; - } -} - - -- (void)setUploadMIMEType:(NSString *)uploadMIMEType { - GTMSESSION_ASSERT_DEBUG(0, @"TODO: disallow setUploadMIMEType by making declaration readonly"); - // (and uploadMIMEType, chunksize, currentOffset) - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _uploadMIMEType = uploadMIMEType; - } -} - -- (NSString *)uploadMIMEType { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _uploadMIMEType; - } -} - -- (void)setChunkSize:(int64_t)chunkSize { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _chunkSize = chunkSize; - } -} - -- (int64_t)chunkSize { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _chunkSize; - } -} - -- (void)setupRequestHeaders { - GTMSessionCheckNotSynchronized(self); - -#if DEBUG - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - int hasData = (_uploadData != nil) ? 1 : 0; - int hasFileHandle = (_uploadFileHandle != nil) ? 1 : 0; - int hasFileURL = (_uploadFileURL != nil) ? 1 : 0; - int hasUploadDataProvider = (_uploadDataProvider != nil) ? 1 : 0; - int numberOfSources = hasData + hasFileHandle + hasFileURL + hasUploadDataProvider; - #pragma unused(numberOfSources) - GTMSESSION_ASSERT_DEBUG(numberOfSources == 1, - @"Need just one upload source (%d)", numberOfSources); - } // @synchronized(self) -#endif - - // Add our custom headers to the initial request indicating the data - // type and total size to be delivered later in the chunk requests. - NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; - - GTMSESSION_ASSERT_DEBUG((mutableRequest == nil) != (_uploadLocationURL == nil), - @"Request and location are mutually exclusive"); - if (!mutableRequest) return; - - [mutableRequest setValue:kGTMSessionXGoogUploadProtocolResumable - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol]; - [mutableRequest setValue:@"start" - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; - [mutableRequest setValue:_uploadMIMEType - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentType]; - [mutableRequest setValue:@([self fullUploadLength]).stringValue - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength]; - - NSString *method = mutableRequest.HTTPMethod; - if (method == nil || [method caseInsensitiveCompare:@"GET"] == NSOrderedSame) { - [mutableRequest setHTTPMethod:@"POST"]; - } - - // Ensure the user agent header identifies this to the upload server as a - // GTMSessionUploadFetcher client. The /1 can be incremented in the unlikely circumstance - // we need to make a bug fix in the client that the server can recognize. - NSString *const kUserAgentStub = @"(GTMSUF/1)"; - NSString *userAgent = [mutableRequest valueForHTTPHeaderField:@"User-Agent"]; - if (userAgent == nil - || [userAgent rangeOfString:kUserAgentStub].location == NSNotFound) { - if (userAgent.length == 0) { - userAgent = GTMFetcherStandardUserAgentString(nil); - } - userAgent = [userAgent stringByAppendingFormat:@" %@", kUserAgentStub]; - [mutableRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; - } - [self setRequest:mutableRequest]; -} - -- (void)setLocationURL:(NSURL * GTM_NULLABLE_TYPE)location - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSESSION_ASSERT_DEBUG(chunkSize > 0, @"chunk size is zero"); - - // When resuming an upload, set the known upload target URL. - _uploadLocationURL = location; - - _uploadMIMEType = uploadMIMEType; - _chunkSize = chunkSize; - - // Indicate that we've not yet determined the file handle's length - _uploadFileLength = kGTMSessionUploadFetcherUnknownFileSize; - - // Indicate that we've not yet determined the upload fetcher status - _recentChunkStatusCode = -1; - - // If this is restarting an upload begun by another fetcher, - // the location is specified but the request is nil - _isRestartedUpload = (location != nil); - } // @synchronized(self) -} - -- (int64_t)fullUploadLength { - int64_t result; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_uploadData) { - result = (int64_t)_uploadData.length; - } else { - if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize) { - if (_uploadFileHandle) { - // First time through, seek to end to determine file length - _uploadFileLength = (int64_t)[_uploadFileHandle seekToEndOfFile]; - } else if (_uploadDataProvider) { - // _uploadFileLength is set when the _uploadDataProvider is set. - GTMSESSION_ASSERT_DEBUG(_uploadFileLength >= 0, @"No uploadDataProvider length set"); - } else { - NSNumber *filesizeNum; - NSError *valueError; - if ([_uploadFileURL getResourceValue:&filesizeNum - forKey:NSURLFileSizeKey - error:&valueError]) { - _uploadFileLength = filesizeNum.longLongValue; - } else { - GTMSESSION_ASSERT_DEBUG(NO, @"Cannot get file size: %@\n %@", - valueError, _uploadFileURL.path); - _uploadFileLength = 0; - } - } - } - result = _uploadFileLength; - } - } // @synchronized(self) - return result; -} - -// Make a subdata of the upload data. -- (void)generateChunkSubdataWithOffset:(int64_t)offset - length:(int64_t)length - response:(GTMSessionUploadFetcherDataProviderResponse)response { - GTMSessionUploadFetcherDataProvider uploadDataProvider = self.uploadDataProvider; - if (uploadDataProvider) { - uploadDataProvider(offset, length, response); - return; - } - - NSData *uploadData = self.uploadData; - if (uploadData) { - // NSData provided. - NSData *resultData; - if (offset == 0 && length == (int64_t)uploadData.length) { - resultData = uploadData; - } else { - int64_t dataLength = (int64_t)uploadData.length; - // Ensure our range is valid. b/18007814 - if (offset + length > dataLength) { - NSString *errorMessage = [NSString stringWithFormat: - @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld", - offset, length, dataLength]; - GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); - response(nil, - kGTMSessionUploadFetcherUnknownFileSize, - [self uploadChunkUnavailableErrorWithDescription:errorMessage]); - return; - } - NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length); - - @try { - resultData = [uploadData subdataWithRange:range]; - } - @catch (NSException *exception) { - NSString *errorMessage = exception.description; - GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); - response(nil, - kGTMSessionUploadFetcherUnknownFileSize, - [self uploadChunkUnavailableErrorWithDescription:errorMessage]); - return; - } - } - response(resultData, kGTMSessionUploadFetcherUnknownFileSize, nil); - return; - } - NSURL *uploadFileURL = self.uploadFileURL; - if (uploadFileURL) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self generateChunkSubdataFromFileURL:uploadFileURL - offset:offset - length:length - response:response]; - }); - return; - } - GTMSESSION_ASSERT_DEBUG(_uploadFileHandle, @"Unexpectedly missing upload data package"); - NSFileHandle *uploadFileHandle = self.uploadFileHandle; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self generateChunkSubdataFromFileHandle:uploadFileHandle - offset:offset - length:length - response:response]; - }); -} - -- (void)generateChunkSubdataFromFileHandle:(NSFileHandle *)fileHandle - offset:(int64_t)offset - length:(int64_t)length - response:(GTMSessionUploadFetcherDataProviderResponse)response { - NSData *resultData; - NSError *error; - @try { - [fileHandle seekToFileOffset:(unsigned long long)offset]; - resultData = [fileHandle readDataOfLength:(NSUInteger)length]; - } - @catch (NSException *exception) { - GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileHandle failed to read, %@", exception); - error = [self uploadChunkUnavailableErrorWithDescription:exception.description]; - } - // The response always re-dispatches to the main thread, so we skip doing that here. - response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); -} - -- (void)generateChunkSubdataFromFileURL:(NSURL *)fileURL - offset:(int64_t)offset - length:(int64_t)length - response:(GTMSessionUploadFetcherDataProviderResponse)response { - GTMSessionCheckNotSynchronized(self); - - NSData *resultData; - NSError *error; - int64_t fullUploadLength = [self fullUploadLength]; - NSData *mappedData = - [NSData dataWithContentsOfURL:fileURL - options:NSDataReadingMappedAlways + NSDataReadingUncached - error:&error]; - if (!mappedData) { - // We could not create an NSData by memory-mapping the file. -#if TARGET_IPHONE_SIMULATOR - // NSTemporaryDirectory() can differ in the simulator between app restarts, - // yet the contents for the new path remains unchanged, so try the latest temp path. - if ([error.domain isEqual:NSCocoaErrorDomain] && (error.code == NSFileReadNoSuchFileError)) { - NSString *filename = [fileURL lastPathComponent]; - NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; - NSURL *newFileURL = [NSURL fileURLWithPath:filePath]; - if (![newFileURL isEqual:fileURL]) { - [self generateChunkSubdataFromFileURL:newFileURL - offset:offset - length:length - response:response]; - return; - } - } -#endif - - // If the file is just too large to create an NSData for, or if for some other reason we can't - // map it, create an NSFileHandle instead to read a subset into an NSData. -#if DEBUG - NSNumber *fileSizeNum; - BOOL hasFileSize = [fileURL getResourceValue:&fileSizeNum forKey:NSURLFileSizeKey error:NULL]; - GTMSESSION_LOG_DEBUG(@"Note: uploadFileURL is falling back to creating upload chunks by reading" - @" an NSFileHandle since uploadFileURL failed to map the upload file," - @" file size %@, %@", - hasFileSize ? fileSizeNum : @"unknown", error); -#endif - - NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL - error:&error]; - if (fileHandle != nil) { - [self generateChunkSubdataFromFileHandle:fileHandle - offset:offset - length:length - response:response]; - return; - } - GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileURL failed to read, %@", error); - // Fall through with the error. - } else { - // Successfully created an NSData by memory-mapping the file. - if ((NSUInteger)(offset + length) > mappedData.length) { - NSString *errorMessage = [NSString stringWithFormat: - @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld\texpected UploadLength: %lld", - offset, length, (long long)mappedData.length, fullUploadLength]; - GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); - response(nil, - kGTMSessionUploadFetcherUnknownFileSize, - [self uploadChunkUnavailableErrorWithDescription:errorMessage]); - return; - } - if (offset > 0 || length < fullUploadLength) { - NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length); - resultData = [mappedData subdataWithRange:range]; - } else { - resultData = mappedData; - } - } - // The response always re-dispatches to the main thread, so we skip re-dispatching here. - response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); -} - -- (NSError *)uploadChunkUnavailableErrorWithDescription:(NSString *)description { - // The description in the userInfo is intended as a clue to programmers, not - // for client code to examine or rely on. - NSDictionary *userInfo = @{ @"description" : description }; - return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain - code:GTMSessionFetcherErrorUploadChunkUnavailable - userInfo:userInfo]; -} - -- (NSError *)prematureFailureErrorWithUserInfo:(NSDictionary *)userInfo { - // An error for if we get an unexpected status from the upload server or - // otherwise cannot continue. This is an issue beyond the upload protocol; - // there's no way the client can do anything useful except give up. - NSError *error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain - code:501 // Not implemented - userInfo:userInfo]; - return error; -} - -+ (GTMSessionUploadFetcherStatus)uploadStatusFromResponseHeaders:(NSDictionary *)responseHeaders { - NSString *statusString = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus]; - if ([statusString isEqual:@"active"]) { - return kStatusActive; - } - if ([statusString isEqual:@"final"]) { - return kStatusFinal; - } - if ([statusString isEqual:@"cancelled"]) { - return kStatusCancelled; - } - return kStatusUnknown; -} - -#pragma mark Method overrides affecting the initial fetch only - -- (void)setCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delegateCompletionHandler = handler; - } -} - -- (void)setDelegateCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delegateCallbackQueue = queue; - } -} - -- (dispatch_queue_t GTM_NULLABLE_TYPE)delegateCallbackQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateCallbackQueue; - } -} - -- (BOOL)isRestartedUpload { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _isRestartedUpload; - } -} - -- (GTMSessionFetcher * GTM_NULLABLE_TYPE)chunkFetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _chunkFetcher; - } -} - -- (void)setChunkFetcher:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _chunkFetcher = fetcher; - } -} - -- (void)setFetcherInFlight:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _fetcherInFlight = fetcher; - } -} - -- (GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcherInFlight { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _fetcherInFlight; - } -} - -- (void)setCancellationHandler:(GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE) - cancellationHandler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _cancellationHandler = cancellationHandler; - } -} - -- (GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE)cancellationHandler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _cancellationHandler; - } -} - -- (void)beginFetchForRetry { - GTMSessionCheckNotSynchronized(self); - - // Override the superclass to reset the initial body length and fetcher-in-flight, - // then call the superclass implementation. - [self setInitialBodyLength:[self bodyLength]]; - - GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", - self.fetcherInFlight); - self.fetcherInFlight = self; - [super beginFetchForRetry]; -} - -- (void)beginFetchWithCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { - GTMSessionCheckNotSynchronized(self); - - [self setInitialBodyLength:[self bodyLength]]; - - // We'll hold onto the superclass's callback queue so we can invoke the handler - // even after the superclass has released the queue and its callback handler, as - // happens during auth failure. - [self setDelegateCallbackQueue:self.callbackQueue]; - self.completionHandler = handler; - - if ([self isRestartedUpload]) { - // When restarting an upload, we know the destination location for chunk fetches, - // but we need to query to find the initial offset. - if (![self isPaused]) { - [self sendQueryForUploadOffsetWithFetcherProperties:self.properties]; - } - return; - } - // We don't want to call into the client's completion block immediately - // after the finish of the initial connection (the delegate is called only - // when uploading finishes), so we substitute our own completion block to be - // called when the initial connection finishes - GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", - self.fetcherInFlight); - - self.fetcherInFlight = self; - [super beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { - self.fetcherInFlight = nil; - // callback - - BOOL hasTestBlock = (self.testBlock != nil); - if (![self isRestartedUpload] && !hasTestBlock) { - if (error == nil) { - [self beginChunkFetches]; - } else { - if ([self retryTimer] == nil) { - [self invokeFinalCallbackWithData:nil - error:error - shouldInvalidateLocation:YES]; - } - } - } else { - // If there was no initial request, then this fetch is resuming some - // other uploadFetcher's initial request, and the superclass's connection - // is never used, so at this point we call the user's actual completion - // block. - if (!hasTestBlock) { - [self invokeFinalCallbackWithData:data - error:error - shouldInvalidateLocation:YES]; - } else { - // There was a test block, so we won't do chunk fetches, but we simulate obtaining - // the data to be uploaded from the upload data provider block or the file handle, - // and then call back. - [self generateChunkSubdataWithOffset:0 - length:[self fullUploadLength] - response:^(NSData *generateData, int64_t fullUploadLength, NSError *generateError) { - [self invokeFinalCallbackWithData:data - error:error - shouldInvalidateLocation:YES]; - }]; - } - } - }]; -} - -- (void)beginChunkFetches { - GTMSessionCheckNotSynchronized(self); - -#if DEBUG - // The initial response of the resumable upload protocol should have an - // empty body - // - // This assert typically happens because the upload create/edit link URL was - // not supplied with the request, and the server is thus expecting a non- - // resumable request/response. - if (self.downloadedData.length > 0) { - NSData *downloadedData = self.downloadedData; - NSString *str = [[NSString alloc] initWithData:downloadedData - encoding:NSUTF8StringEncoding]; - #pragma unused(str) - GTMSESSION_ASSERT_DEBUG(NO, @"unexpected response data (uploading to the wrong URL?)\n%@", str); - } -#endif - - // We need to get the upload URL from the location header to continue. - NSDictionary *responseHeaders = [self responseHeaders]; - - [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders]; - - GTMSessionUploadFetcherStatus uploadStatus = - [[self class] uploadStatusFromResponseHeaders:responseHeaders]; - GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown, - @"beginChunkFetches has unexpected upload status for headers %@", responseHeaders); - - BOOL isPrematureStop = (uploadStatus == kStatusFinal) || (uploadStatus == kStatusCancelled); - - NSString *uploadLocationURLStr = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadURL]; - BOOL hasUploadLocation = (uploadLocationURLStr.length > 0); - - if (isPrematureStop || !hasUploadLocation) { - GTMSESSION_ASSERT_DEBUG(NO, @"Premature failure: upload-status:\"%@\" location:%@", - [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], uploadLocationURLStr); - // We cannot continue since we do not know the location to use - // as our upload destination. - NSDictionary *userInfo = nil; - NSData *downloadedData = self.downloadedData; - if (downloadedData.length > 0) { - userInfo = @{ kGTMSessionFetcherStatusDataKey : downloadedData }; - } - NSError *failureError = [self prematureFailureErrorWithUserInfo:userInfo]; - [self invokeFinalCallbackWithData:nil - error:failureError - shouldInvalidateLocation:YES]; - return; - } - - self.uploadLocationURL = [NSURL URLWithString:uploadLocationURLStr]; - - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc postNotificationName:kGTMSessionFetcherUploadLocationObtainedNotification - object:self]; - - // we've now sent all of the initial post body data, so we need to include - // its size in future progress indicator callbacks - [self setInitialBodySent:[self initialBodyLength]]; - - // just in case the user paused us during the initial fetch... - if (![self isPaused]) { - [self uploadNextChunkWithOffset:0]; - } -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - didSendBodyData:(int64_t)bytesSent - totalBytesSent:(int64_t)totalBytesSent - totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { - // Overrides the superclass. - [self invokeDelegateWithDidSendBytes:bytesSent - totalBytesSent:totalBytesSent - totalBytesExpectedToSend:totalBytesExpectedToSend + [self fullUploadLength]]; -} - -- (BOOL)shouldReleaseCallbacksUponCompletion { - // Overrides the superclass. - - // We don't want the superclass to release the delegate and callback - // blocks once the initial fetch has finished - // - // This is invoked for only successful completion of the connection; - // an error always will invoke and release the callbacks - return NO; -} - -- (void)invokeFinalCallbackWithData:(NSData *)data - error:(NSError *)error - shouldInvalidateLocation:(BOOL)shouldInvalidateLocation { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (shouldInvalidateLocation) { - _uploadLocationURL = nil; - } - - dispatch_queue_t queue = _delegateCallbackQueue; - GTMSessionFetcherCompletionHandler handler = _delegateCompletionHandler; - if (queue && handler) { - [self invokeOnCallbackQueue:queue - afterUserStopped:NO - block:^{ - handler(data, error); - }]; - } - } // @synchronized(self) - - [self releaseUploadAndBaseCallbacks:!self.userStoppedFetching]; -} - -- (void)releaseUploadAndBaseCallbacks:(BOOL)shouldReleaseCancellation { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delegateCallbackQueue = nil; - _delegateCompletionHandler = nil; - _uploadDataProvider = nil; - if (shouldReleaseCancellation) { - _cancellationHandler = nil; - } - } - - // Release the base class's callbacks, too, if needed. - [self releaseCallbacks]; -} - -- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks { - GTMSessionCheckNotSynchronized(self); - - // Clear _fetcherInFlight when stopped. Moved from stopFetching, since that's a public method, - // where this method does the work. Fixes issue clearing value when retryBlock included. - GTMSessionFetcher *fetcherInFlight = self.fetcherInFlight; - if (fetcherInFlight == self) { - self.fetcherInFlight = nil; - } - - [super stopFetchReleasingCallbacks:shouldReleaseCallbacks]; - - if (shouldReleaseCallbacks) { - [self releaseUploadAndBaseCallbacks:NO]; - } -} - -#pragma mark Chunk fetching methods - -- (void)uploadNextChunkWithOffset:(int64_t)offset { - // use the properties in each chunk fetcher - NSDictionary *props = [self properties]; - - [self uploadNextChunkWithOffset:offset - fetcherProperties:props]; -} - -- (void)sendQueryForUploadOffsetWithFetcherProperties:(NSDictionary *)props { - GTMSessionFetcher *queryFetcher = [self uploadFetcherWithProperties:props - isQueryFetch:YES]; - queryFetcher.bodyData = [NSData data]; - - NSString *originalComment = self.comment; - [queryFetcher setCommentWithFormat:@"%@ (query offset)", - originalComment ? originalComment : @"upload"]; - - [queryFetcher setRequestValue:@"query" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; - - self.fetcherInFlight = queryFetcher; - [queryFetcher beginFetchWithDelegate:self - didFinishSelector:@selector(queryFetcher:finishedWithData:error:)]; -} - -- (void)queryFetcher:(GTMSessionFetcher *)queryFetcher - finishedWithData:(NSData *)data - error:(NSError *)error { - self.fetcherInFlight = nil; - - NSDictionary *responseHeaders = [queryFetcher responseHeaders]; - NSString *sizeReceivedHeader; - - GTMSessionUploadFetcherStatus uploadStatus = - [[self class] uploadStatusFromResponseHeaders:responseHeaders]; - GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown || error != nil, - @"query fetcher completion has unexpected upload status for headers %@", responseHeaders); - - if (error == nil) { - sizeReceivedHeader = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadSizeReceived]; - - if (uploadStatus == kStatusCancelled || - (uploadStatus == kStatusActive && sizeReceivedHeader == nil)) { - NSDictionary *userInfo = nil; - if (data.length > 0) { - userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; - } - error = [self prematureFailureErrorWithUserInfo:userInfo]; - } - } - - if (error == nil) { - int64_t offset = [sizeReceivedHeader longLongValue]; - int64_t fullUploadLength = [self fullUploadLength]; - if (uploadStatus == kStatusFinal || - (offset >= fullUploadLength && - fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize)) { - // Handle we're done - [self chunkFetcher:queryFetcher finishedWithData:data error:nil]; - } else { - [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders]; - [self uploadNextChunkWithOffset:offset]; - } - } else { - // Handle query error - [self chunkFetcher:queryFetcher finishedWithData:data error:error]; - } -} - -- (void)sendCancelUploadWithFetcherProperties:(NSDictionary *)props { - @synchronized(self) { - _isCancelInFlight = YES; - } - GTMSessionFetcher *cancelFetcher = [self uploadFetcherWithProperties:props - isQueryFetch:YES]; - cancelFetcher.bodyData = [NSData data]; - - NSString *originalComment = self.comment; - [cancelFetcher setCommentWithFormat:@"%@ (cancel)", - originalComment ? originalComment : @"upload"]; - - [cancelFetcher setRequestValue:@"cancel" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; - - self.fetcherInFlight = cancelFetcher; - [cancelFetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { - self.fetcherInFlight = nil; - if (![self triggerCancellationHandlerForFetch:cancelFetcher data:data error:error]) { - if (error) { - GTMSESSION_LOG_DEBUG(@"cancelFetcher %@", error); - } - } - @synchronized(self) { - self->_isCancelInFlight = NO; - } - }]; -} - -- (void)uploadNextChunkWithOffset:(int64_t)offset - fetcherProperties:(NSDictionary *)props { - GTMSessionCheckNotSynchronized(self); - - // Example chunk headers: - // X-Goog-Upload-Command: upload, finalize - // X-Goog-Upload-Offset: 0 - // Content-Length: 2000000 - // Content-Type: image/jpeg - // - // {bytes 0-1999999} - - // The chunk upload URL requires no authentication header. - GTMSessionFetcher *chunkFetcher = [self uploadFetcherWithProperties:props - isQueryFetch:NO]; - [self attachSendProgressBlockToChunkFetcher:chunkFetcher]; - int64_t chunkSize = [self updateChunkFetcher:chunkFetcher - forChunkAtOffset:offset]; - BOOL isUploadingFileURL = (self.uploadFileURL != nil); - int64_t fullUploadLength = [self fullUploadLength]; - - // The chunk size may have changed, so determine again if we're uploading the full file. - BOOL isUploadingFullFile = (offset == 0 && - fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize && - chunkSize >= fullUploadLength); - if (isUploadingFullFile && isUploadingFileURL) { - // The data is the full upload file URL. - chunkFetcher.bodyFileURL = self.uploadFileURL; - [self beginChunkFetcher:chunkFetcher - offset:offset]; - } else { - // Make an NSData for the subset for this upload chunk. - self.subdataGenerating = YES; - [self generateChunkSubdataWithOffset:offset - length:chunkSize - response:^(NSData *chunkData, int64_t uploadFileLength, NSError *chunkError) { - // The subdata methods may leave us on a background thread. - dispatch_async(dispatch_get_main_queue(), ^{ - self.subdataGenerating = NO; - - // dont allow the updating of fileLength for uploads not using a data provider as they - // should know the file length before the upload starts. - if (self->_uploadDataProvider != nil && uploadFileLength > 0) { - [self setUploadFileLength:uploadFileLength]; - // Update the command and content-length headers if this is the last chunk to be sent. - if (offset + chunkSize >= uploadFileLength) { - int64_t updatedChunkSize = [self updateChunkFetcher:chunkFetcher - forChunkAtOffset:offset]; - if (updatedChunkSize == 0) { - // Calling beginChunkFetcher early when there is no more data to send allows us to - // properly handle nil chunkData below without having to account for the case where - // we are just finalizing the file. - chunkFetcher.bodyData = [[NSData alloc] init]; - [self beginChunkFetcher:chunkFetcher - offset:offset]; - return; - } - } - } - - if (chunkData == nil) { - NSError *responseError = chunkError; - if (!responseError) { - responseError = [self uploadChunkUnavailableErrorWithDescription:@"chunkData is nil"]; - } - [self invokeFinalCallbackWithData:nil - error:responseError - shouldInvalidateLocation:YES]; - return; - } - - BOOL didWriteFile = NO; - if (isUploadingFileURL) { - // Make a temporary file with the data subset. - NSString *tempName = - [NSString stringWithFormat:@"GTMUpload_temp_%@", [[NSUUID UUID] UUIDString]]; - NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempName]; - NSError *writeError; - didWriteFile = [chunkData writeToFile:tempPath - options:NSDataWritingAtomic - error:&writeError]; - if (didWriteFile) { - chunkFetcher.bodyFileURL = [NSURL fileURLWithPath:tempPath]; - } else { - GTMSESSION_LOG_DEBUG(@"writeToFile failed: %@\n%@", writeError, tempPath); - } - } - if (!didWriteFile) { - chunkFetcher.bodyData = [chunkData copy]; - } - [self beginChunkFetcher:chunkFetcher - offset:offset]; - }); - }]; - } -} - -- (void)beginChunkFetcher:(GTMSessionFetcher *)chunkFetcher - offset:(int64_t)offset { - - // Track the current offset for progress reporting - self.currentOffset = offset; - - // Hang on to the fetcher in case we need to cancel it. We set these before beginning the - // chunk fetch so the observers notified of chunk fetches can inspect the upload fetcher to - // match to the chunk. - self.chunkFetcher = chunkFetcher; - self.fetcherInFlight = chunkFetcher; - - // Update the last chunk request, including any request headers. - self.lastChunkRequest = chunkFetcher.request; - - [chunkFetcher beginFetchWithDelegate:self - didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)]; -} - -- (void)attachSendProgressBlockToChunkFetcher:(GTMSessionFetcher *)chunkFetcher { - chunkFetcher.sendProgressBlock = ^(int64_t bytesSent, int64_t totalBytesSent, - int64_t totalBytesExpectedToSend) { - // The total bytes expected include the initial body and the full chunked - // data, independent of how big this fetcher's chunk is. - int64_t initialBodySent = [self bodyLength]; // TODO(grobbins) use [self initialBodySent] - int64_t totalSent = initialBodySent + self.currentOffset + totalBytesSent; - int64_t totalExpected = initialBodySent + [self fullUploadLength]; - - [self invokeDelegateWithDidSendBytes:bytesSent - totalBytesSent:totalSent - totalBytesExpectedToSend:totalExpected]; - }; -} - -- (NSDictionary *)uploadSessionIdentifierMetadata { - NSMutableDictionary *metadata = [NSMutableDictionary dictionary]; - metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] = @YES; - GTMSESSION_ASSERT_DEBUG(self.uploadFileURL, - @"Invalid upload fetcher to create session identifier for metadata"); - metadata[kGTMSessionIdentifierUploadFileURLMetadataKey] = [self.uploadFileURL absoluteString]; - metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey] = @([self fullUploadLength]); - - if (self.uploadLocationURL) { - metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey] = - [self.uploadLocationURL absoluteString]; - } - if (self.uploadMIMEType) { - metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey] = self.uploadMIMEType; - } - metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] = @(self.chunkSize); - metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] = @(self.currentOffset); - return metadata; -} - -- (GTMSessionFetcher *)uploadFetcherWithProperties:(NSDictionary *)properties - isQueryFetch:(BOOL)isQueryFetch { - GTMSessionCheckNotSynchronized(self); - - // Common code to make a request for a query command or for a chunk upload. - NSURL *uploadLocationURL = self.uploadLocationURL; - NSMutableURLRequest *chunkRequest = [NSMutableURLRequest requestWithURL:uploadLocationURL]; - [chunkRequest setHTTPMethod:@"PUT"]; - - // copy the user-agent from the original connection - // n.b. that self.request is nil for upload fetchers created with an existing upload location - // URL. - NSURLRequest *origRequest = self.request; - NSString *userAgent = [origRequest valueForHTTPHeaderField:@"User-Agent"]; - if (userAgent.length > 0) { - [chunkRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; - } - - [chunkRequest setValue:kGTMSessionXGoogUploadProtocolResumable - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol]; - - // To avoid timeouts when debugging, copy the timeout of the initial fetcher. - NSTimeInterval origTimeout = [origRequest timeoutInterval]; - [chunkRequest setTimeoutInterval:origTimeout]; - - // - // Make a new chunk fetcher. - // - GTMSessionFetcher *chunkFetcher = [GTMSessionFetcher fetcherWithRequest:chunkRequest]; - chunkFetcher.callbackQueue = self.callbackQueue; - chunkFetcher.sessionUserInfo = self.sessionUserInfo; - chunkFetcher.configurationBlock = self.configurationBlock; - chunkFetcher.allowedInsecureSchemes = self.allowedInsecureSchemes; - chunkFetcher.allowLocalhostRequest = self.allowLocalhostRequest; - chunkFetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates; - chunkFetcher.useUploadTask = !isQueryFetch; - - if (self.uploadFileURL && !isQueryFetch && self.useBackgroundSession) { - [chunkFetcher createSessionIdentifierWithMetadata:[self uploadSessionIdentifierMetadata]]; - } - - // Give the chunk fetcher the same properties as the previous chunk fetcher - chunkFetcher.properties = [properties mutableCopy]; - [chunkFetcher setProperty:[NSValue valueWithNonretainedObject:self] - forKey:kGTMSessionUploadFetcherChunkParentKey]; - - // copy other fetcher settings to the new fetcher - chunkFetcher.retryEnabled = self.retryEnabled; - chunkFetcher.maxRetryInterval = self.maxRetryInterval; - - if ([self isRetryEnabled]) { - // We interpose our own retry method both so we can change the request to ask the server to - // tell us where to resume the chunk. - chunkFetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *chunkError, - GTMSessionFetcherRetryResponse response) { - void (^finish)(BOOL) = ^(BOOL shouldRetry){ - // We'll retry by sending an offset query. - if (shouldRetry) { - self.shouldInitiateOffsetQuery = !isQueryFetch; - - // We don't know what our actual offset is anymore, but the server will tell us. - self.currentOffset = 0; - } - // We don't actually want to retry this specific fetcher. - response(NO); - }; - - GTMSessionFetcherRetryBlock retryBlock = self.retryBlock; - if (retryBlock) { - // Ask the client, then call the finish block above. - retryBlock(suggestedWillRetry, chunkError, finish); - } else { - finish(suggestedWillRetry); - } - }; - } - - return chunkFetcher; -} - -- (void)chunkFetcher:(GTMSessionFetcher *)chunkFetcher - finishedWithData:(NSData *)data - error:(NSError *)error { - BOOL hasDestroyedOldChunkFetcher = NO; - self.fetcherInFlight = nil; - - NSDictionary *responseHeaders = [chunkFetcher responseHeaders]; - GTMSessionUploadFetcherStatus uploadStatus = - [[self class] uploadStatusFromResponseHeaders:responseHeaders]; - GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown - || error != nil - || self.wasCreatedFromBackgroundSession, - @"chunk fetcher completion has kStatusUnknown upload status for headers %@ fetcher %@", - responseHeaders, self); - BOOL isUploadStatusStopped = (uploadStatus == kStatusFinal || uploadStatus == kStatusCancelled); - - // Check if the fetcher was actually querying. If it failed, do not retry, - // as it would enter an infinite retry loop. - NSString *uploadCommand = - chunkFetcher.request.allHTTPHeaderFields[kGTMSessionHeaderXGoogUploadCommand]; - BOOL isQueryFetch = [uploadCommand isEqual:@"query"]; - - // TODO - // Maybe here we can check to see if the request had x goog content length set. (the file length one). - int64_t previousContentLength = - [[chunkFetcher.request valueForHTTPHeaderField:@"Content-Length"] longLongValue]; - // The Content-Length header may not be present if the chunk fetcher was recreated from - // a background session. - BOOL hasKnownChunkSize = (previousContentLength > 0); - BOOL needsQuery = (!hasKnownChunkSize && !isUploadStatusStopped); - - if (error || (needsQuery && !isQueryFetch)) { - NSInteger status = error.code; - - // Status 4xx indicates a bad offset in the Google upload protocol. However, do not retry status - // 404 per spec, nor if the upload size appears to have been zero (since the server will just - // keep asking us to retry.) - if (self.shouldInitiateOffsetQuery || - (needsQuery && !isQueryFetch) || - ([error.domain isEqual:kGTMSessionFetcherStatusDomain] && - status >= 400 && status <= 499 && - status != 404 && - uploadStatus == kStatusActive && - previousContentLength > 0)) { - self.shouldInitiateOffsetQuery = NO; - [self destroyChunkFetcher]; - hasDestroyedOldChunkFetcher = YES; - [self sendQueryForUploadOffsetWithFetcherProperties:chunkFetcher.properties]; - } else { - // Some unexpected status has occurred; handle it as we would a regular - // object fetcher failure. - [self invokeFinalCallbackWithData:data - error:error - shouldInvalidateLocation:NO]; - } - } else { - // The chunk has uploaded successfully. - int64_t newOffset = self.currentOffset + previousContentLength; -#if DEBUG - // Verify that if we think all of the uploading data has been sent, the server responded with - // the "final" upload status. - BOOL hasUploadAllData = (newOffset == [self fullUploadLength]); - BOOL isFinalStatus = (uploadStatus == kStatusFinal); - #pragma unused(hasUploadAllData,isFinalStatus) - GTMSESSION_ASSERT_DEBUG(hasUploadAllData == isFinalStatus || !hasKnownChunkSize, - @"uploadStatus:%@ newOffset:%lld (%lld + %lld) fullUploadLength:%lld" - @" chunkFetcher:%@ requestHeaders:%@ responseHeaders:%@", - [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], - newOffset, self.currentOffset, previousContentLength, - [self fullUploadLength], - chunkFetcher, chunkFetcher.request.allHTTPHeaderFields, - responseHeaders); -#endif - if (isUploadStatusStopped || (_currentOffset > _uploadFileLength && _uploadFileLength > 0)) { - // This was the last chunk. - if (error == nil && uploadStatus == kStatusCancelled) { - // Report cancelled status as an error. - NSDictionary *userInfo = nil; - if (data.length > 0) { - userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; - } - data = nil; - error = [self prematureFailureErrorWithUserInfo:userInfo]; - } else { - // The upload is in final status. - // - // Take the chunk fetcher's data as the superclass data. - self.downloadedData = data; - self.statusCode = chunkFetcher.statusCode; - } - - // we're done - [self invokeFinalCallbackWithData:data - error:error - shouldInvalidateLocation:YES]; - } else { - // Start the next chunk. - self.currentOffset = newOffset; - - // We want to destroy this chunk fetcher before creating the next one, but - // we want to pass on its properties - NSDictionary *props = [chunkFetcher properties]; - - // We no longer need to be able to cancel this chunkFetcher. Destroy it - // before we create a new chunk fetcher. - [self destroyChunkFetcher]; - hasDestroyedOldChunkFetcher = YES; - - [self uploadNextChunkWithOffset:newOffset - fetcherProperties:props]; - } - } - if (!hasDestroyedOldChunkFetcher) { - [self destroyChunkFetcher]; - } -} - -- (void)destroyChunkFetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_fetcherInFlight == _chunkFetcher) { - _fetcherInFlight = nil; - } - - [_chunkFetcher stopFetching]; - - NSURL *chunkFileURL = _chunkFetcher.bodyFileURL; - BOOL wasTemporaryUploadFile = ![chunkFileURL isEqual:_uploadFileURL]; - if (wasTemporaryUploadFile) { - NSError *error; - [[NSFileManager defaultManager] removeItemAtURL:chunkFileURL - error:&error]; - if (error) { - GTMSESSION_LOG_DEBUG(@"removingItemAtURL failed: %@\n%@", error, chunkFileURL); - } - } - - _recentChunkReponseHeaders = _chunkFetcher.responseHeaders; - - // To avoid retain cycles, remove all properties except the parent identifier. - _chunkFetcher.properties = - @{ kGTMSessionUploadFetcherChunkParentKey : [NSValue valueWithNonretainedObject:self] }; - - _chunkFetcher.retryBlock = nil; - _chunkFetcher.sendProgressBlock = nil; - _chunkFetcher = nil; - } // @synchronized(self) -} - -// This method calculates the proper values to pass to the client's send progress block. -// -// The actual total bytes sent include the initial body sent, plus the -// offset into the batched data prior to the current chunk fetcher - -- (void)invokeDelegateWithDidSendBytes:(int64_t)bytesSent - totalBytesSent:(int64_t)totalBytesSent - totalBytesExpectedToSend:(int64_t)totalBytesExpected { - GTMSessionCheckNotSynchronized(self); - - // Ensure the chunk fetcher survives the callback in case the user pauses the upload process. - __block GTMSessionFetcher *holdFetcher = self.chunkFetcher; - - [self invokeOnCallbackQueue:self.delegateCallbackQueue - afterUserStopped:NO - block:^{ - GTMSessionFetcherSendProgressBlock sendProgressBlock = self.sendProgressBlock; - if (sendProgressBlock) { - sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpected); - } - holdFetcher = nil; - }]; -} - -- (void)retrieveUploadChunkGranularityFromResponseHeaders:(NSDictionary *)responseHeaders { - GTMSessionCheckNotSynchronized(self); - - // Standard granularity for Google uploads is 256K. - NSString *chunkGranularityHeader = - [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadChunkGranularity]; - self.uploadGranularity = chunkGranularityHeader.longLongValue; -} - -#pragma mark - - -- (BOOL)isPaused { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _isPaused; - } // @synchronized(self) -} - -- (void)pauseFetching { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _isPaused = YES; - } // @synchronized(self) - - // Pausing just means stopping the current chunk from uploading; - // when we resume, we will send a query request to the server to - // figure out what bytes to resume sending. - // - // We won't try to cancel the initial data upload, but rather will check - // for being paused in beginChunkFetches. - [self destroyChunkFetcher]; -} - -- (void)resumeFetching { - BOOL wasPaused; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - wasPaused = _isPaused; - _isPaused = NO; - } // @synchronized(self) - - if (wasPaused) { - [self sendQueryForUploadOffsetWithFetcherProperties:self.properties]; - } -} - -- (void)stopFetching { - // Overrides the superclass - [self destroyChunkFetcher]; - - // If we think the server is waiting for more data, then tell it there won't be more. - if (self.uploadLocationURL) { - [self sendCancelUploadWithFetcherProperties:[self properties]]; - self.uploadLocationURL = nil; - } else { - [self invokeOnCallbackQueue:self.callbackQueue - afterUserStopped:YES - block:^{ - // Repeated calls to stopFetching may cause this path to be reached despite having sent a real - // cancel request, check here to ensure that the cancellation handler invocation which fires - // will definitely be for the real request sent previously. - @synchronized(self) { - if (self->_isCancelInFlight) { - return; - } - } - [self triggerCancellationHandlerForFetch:nil data:nil error:nil]; - }]; - } - - [super stopFetching]; -} - -// Fires the cancellation handler, returning whether there was a handler to be fired. -- (BOOL)triggerCancellationHandlerForFetch:(GTMSessionFetcher *)fetcher - data:(NSData *)data - error:(NSError *)error { - GTMSessionUploadFetcherCancellationHandler handler = self.cancellationHandler; - if (handler) { - handler(fetcher, data, error); - self.cancellationHandler = nil; - return YES; - } - return NO; -} - -#pragma mark - - -- (int64_t)updateChunkFetcher:(GTMSessionFetcher *)chunkFetcher - forChunkAtOffset:(int64_t)offset { - BOOL isUploadingFileURL = (self.uploadFileURL != nil); - - // Upload another chunk, meeting server-required granularity. - int64_t chunkSize = self.chunkSize; - - int64_t fullUploadLength = [self fullUploadLength]; - BOOL isFileLengthKnown = fullUploadLength >= 0; - - BOOL isUploadingFullFile = (offset == 0 && isFileLengthKnown && chunkSize >= fullUploadLength); - if (!isUploadingFileURL || !isUploadingFullFile) { - // We're not uploading the entire file and given the file URL. Since we'll be - // allocating a subdata block for a chunk, we need to bound it to something that - // won't blow the process's memory. - if (chunkSize > kGTMSessionUploadFetcherMaximumDemandBufferSize) { - chunkSize = kGTMSessionUploadFetcherMaximumDemandBufferSize; - } - } - - int64_t granularity = self.uploadGranularity; - if (granularity > 0) { - if (chunkSize < granularity) { - chunkSize = granularity; - } else { - chunkSize = chunkSize - (chunkSize % granularity); - } - } - - GTMSESSION_ASSERT_DEBUG(offset < fullUploadLength || fullUploadLength == 0, - @"offset %lld exceeds data length %lld", offset, fullUploadLength); - - if (granularity > 0) { - offset = offset - (offset % granularity); - } - - // If the chunk size is bigger than the remaining data, or else - // it's close enough in size to the remaining data that we'd rather - // avoid having a whole extra http fetch for the leftover bit, then make - // this chunk size exactly match the remaining data size - NSString *command; - int64_t thisChunkSize = chunkSize; - - BOOL isChunkTooBig = (thisChunkSize >= (fullUploadLength - offset)); - BOOL isChunkAlmostBigEnough = (fullUploadLength - offset - 2500 < thisChunkSize); - BOOL isFinalChunk = (isChunkTooBig || isChunkAlmostBigEnough) && isFileLengthKnown; - if (isFinalChunk) { - thisChunkSize = fullUploadLength - offset; - if (thisChunkSize > 0) { - command = @"upload, finalize"; - } else { - command = @"finalize"; - } - } else { - command = @"upload"; - } - NSString *lengthStr = @(thisChunkSize).stringValue; - NSString *offsetStr = @(offset).stringValue; - - [chunkFetcher setRequestValue:command forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; - [chunkFetcher setRequestValue:lengthStr forHTTPHeaderField:@"Content-Length"]; - [chunkFetcher setRequestValue:offsetStr forHTTPHeaderField:kGTMSessionHeaderXGoogUploadOffset]; - if (_uploadFileLength != kGTMSessionUploadFetcherUnknownFileSize) { - [chunkFetcher setRequestValue:@([self fullUploadLength]).stringValue - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength]; - } - - // Append the range of bytes in this chunk to the fetcher comment. - NSString *baseComment = self.comment; - [chunkFetcher setCommentWithFormat:@"%@ (%lld-%lld)", - baseComment ? baseComment : @"upload", offset, MAX(0, offset + thisChunkSize - 1)]; - - return thisChunkSize; -} - -// Public properties. -@synthesize currentOffset = _currentOffset, - delegateCompletionHandler = _delegateCompletionHandler, - chunkFetcher = _chunkFetcher, - lastChunkRequest = _lastChunkRequest, - subdataGenerating = _subdataGenerating, - shouldInitiateOffsetQuery = _shouldInitiateOffsetQuery, - uploadGranularity = _uploadGranularity; - -// Internal properties. -@dynamic fetcherInFlight; -@dynamic activeFetcher; -@dynamic statusCode; -@dynamic delegateCallbackQueue; - -+ (void)removePointer:(void *)pointer fromPointerArray:(NSPointerArray *)pointerArray { - for (NSUInteger index = 0, count = pointerArray.count; index < count; ++index) { - void *pointerAtIndex = [pointerArray pointerAtIndex:index]; - if (pointerAtIndex == pointer) { - [pointerArray removePointerAtIndex:index]; - return; - } - } -} - -- (BOOL)useBackgroundSession { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _useBackgroundSessionOnChunkFetchers; - } // @synchronized(self -} - -- (void)setUseBackgroundSession:(BOOL)useBackgroundSession { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_useBackgroundSessionOnChunkFetchers != useBackgroundSession) { - _useBackgroundSessionOnChunkFetchers = useBackgroundSession; - NSPointerArray *uploadFetcherPointerArrayForBackgroundSessions = - [[self class] uploadFetcherPointerArrayForBackgroundSessions]; - if (_useBackgroundSessionOnChunkFetchers) { - [uploadFetcherPointerArrayForBackgroundSessions addPointer:(__bridge void *)self]; - } else { - [[self class] removePointer:(__bridge void *)self - fromPointerArray:uploadFetcherPointerArrayForBackgroundSessions]; - } - } - } // @synchronized(self -} - -- (BOOL)canFetchWithBackgroundSession { - // The initial upload fetcher is always a foreground session; the - // useBackgroundSession property will apply only to chunk fetchers, - // not to queries. - return NO; -} - -- (NSDictionary *)responseHeaders { - GTMSessionCheckNotSynchronized(self); - // Overrides the superclass - - // If asked for the fetcher's response, use the most recent chunk fetcher's response, - // since the original request's response lacks useful information like the actual - // Content-Type. - NSDictionary *dict = self.chunkFetcher.responseHeaders; - if (dict) { - return dict; - } - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_recentChunkReponseHeaders) { - return _recentChunkReponseHeaders; - } - } // @synchronized(self - - // No chunk fetcher yet completed, so return whatever we have from the initial fetch. - return [super responseHeaders]; -} - -- (NSInteger)statusCodeUnsynchronized { - GTMSessionCheckSynchronized(self); - - if (_recentChunkStatusCode != -1) { - // Overrides the superclass to indicate status appropriate to the initial - // or latest chunk fetch - return _recentChunkStatusCode; - } else { - return [super statusCodeUnsynchronized]; - } -} - - -- (void)setStatusCode:(NSInteger)val { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _recentChunkStatusCode = val; - } -} - -- (int64_t)initialBodyLength { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _initialBodyLength; - } -} - -- (void)setInitialBodyLength:(int64_t)length { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _initialBodyLength = length; - } -} - -- (int64_t)initialBodySent { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _initialBodySent; - } -} - -- (void)setInitialBodySent:(int64_t)length { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _initialBodySent = length; - } -} - -- (NSURL *)uploadLocationURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _uploadLocationURL; - } -} - -- (void)setUploadLocationURL:(NSURL *)locationURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _uploadLocationURL = locationURL; - } -} - -- (GTMSessionFetcher *)activeFetcher { - GTMSessionFetcher *result = self.fetcherInFlight; - if (result) return result; - - return self; -} - -- (BOOL)isFetching { - // If there is an active chunk fetcher, then the upload fetcher is considered - // to still be fetching. - if (self.fetcherInFlight != nil) return YES; - - return [super isFetching]; -} - -- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { - NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; - - while (self.fetcherInFlight || self.subdataGenerating) { - if ([timeoutDate timeIntervalSinceNow] < 0) return NO; - - if (self.subdataGenerating) { - // Allow time for subdata generation. - NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001]; - [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; - } else { - // Wait for any chunk or query fetchers that still have pending callbacks or - // notifications. - BOOL timedOut; - - if (self.fetcherInFlight == self) { - timedOut = ![super waitForCompletionWithTimeout:timeoutInSeconds]; - } else { - timedOut = ![self.fetcherInFlight waitForCompletionWithTimeout:timeoutInSeconds]; - } - if (timedOut) return NO; - } - } - return YES; -} - -@end - -@implementation GTMSessionFetcher (GTMSessionUploadFetcherMethods) - -- (GTMSessionUploadFetcher *)parentUploadFetcher { - NSValue *property = [self propertyForKey:kGTMSessionUploadFetcherChunkParentKey]; - if (!property) return nil; - - GTMSessionUploadFetcher *uploadFetcher = property.nonretainedObjectValue; - - GTMSESSION_ASSERT_DEBUG([uploadFetcher isKindOfClass:[GTMSessionUploadFetcher class]], - @"Unexpected parent upload fetcher class: %@", [uploadFetcher class]); - return uploadFetcher; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/LICENSE b/Pods/GoogleAPIClientForREST/LICENSE @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/Pods/GoogleAPIClientForREST/README.md b/Pods/GoogleAPIClientForREST/README.md @@ -1,48 +0,0 @@ -# Google APIs Client Library for Objective-C for REST # - -**Project site** <https://github.com/google/google-api-objectivec-client-for-rest><br> -**Discussion group** <http://groups.google.com/group/google-api-objectivec-client> - -[![Build Status](https://travis-ci.org/google/google-api-objectivec-client-for-rest.svg?branch=master)](https://travis-ci.org/google/google-api-objectivec-client-for-rest) - -Written by Google, this library is a flexible and efficient Objective-C -framework for accessing JSON APIs. - -This is the recommended library for accessing JSON-based Google APIs for iOS and -Mac OS X applications. The library is compatible with applications built for -iOS 7 and later, and Mac OS X 10.9 and later. - -**To get started** with Google APIs and the Objective-C client library, Read the -[wiki](https://github.com/google/google-api-objectivec-client-for-rest/wiki). -See -[BuildingTheLibrary](https://github.com/google/google-api-objectivec-client-for-rest/wiki/BuildingTheLibrary) -for how to add the library to a Mac or iPhone application project, it covers -directly adding sources or using CocoaPods. Study the -[example applications](https://github.com/google/google-api-objectivec-client-for-rest/tree/master/Examples). - -Generated interfaces for Google APIs are in the -[GeneratedServices folder](https://github.com/google/google-api-objectivec-client-for-rest/tree/master/Source/GeneratedServices). - -In addition to the pre generated classes included with the library, you can -generate your own source for other services that have a -[discovery document](https://developers.google.com/discovery/v1/reference/apis#resource-representations) -by using the -[ServiceGenerator](https://github.com/google/google-api-objectivec-client-for-rest/wiki/ServiceGenerator). - -**If you have a problem** or want a new feature to be included in the library, -please join the -[discussion group](http://groups.google.com/group/google-api-objectivec-client). -Be sure to include -[http logs](https://github.com/google/google-api-objectivec-client-for-rest/wiki#logging-http-server-traffic) -for requests and responses when posting questions. Bugs may also be submitted -on the [issues list](https://github.com/google/google-api-objectivec-client-for-rest/issues). - -**Externally-included projects**: The library includes code from the separate -projects [GTM Session Fetcher](https://github.com/google/gtm-session-fetcher), -[GTMAppAuth](https://github.com/google/GTMAppAuth). - -**Google Data APIs**: The much older library for XML-based APIs is -[still available](https://github.com/google/gdata-objectivec-client). - -Other useful classes for Mac and iOS developers are available in the -[Google Toolbox for Mac](https://github.com/google/google-toolbox-for-mac). diff --git a/Pods/GoogleAPIClientForREST/Source/GTLRDefines.h b/Pods/GoogleAPIClientForREST/Source/GTLRDefines.h @@ -1,109 +0,0 @@ -/* 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. - */ - -// -// GTLRDefines.h -// - -// Ensure Apple's conditionals we depend on are defined. -#import <TargetConditionals.h> -#import <AvailabilityMacros.h> - -// These can be redefined via a prefix if you are prefixing symbols to prefix -// the names used in strings. Something like: -// #define _HELPER(x) "MyPrefix" #x -// #define GTLR_CLASSNAME_STR(x) @_HELPER(x) -// #define GTLR_CLASSNAME_CSTR(x) _HELPER(x) -#ifndef GTLR_CLASSNAME_STR - #define _GTLR_CLASSNAME_HELPER(x) #x - #define GTLR_CLASSNAME_STR(x) @_GTLR_CLASSNAME_HELPER(x) - #define GTLR_CLASSNAME_CSTR(x) _GTLR_CLASSNAME_HELPER(x) -#endif - -// Provide a common definition for externing constants/functions -#if defined(__cplusplus) - #define GTLR_EXTERN extern "C" -#else - #define GTLR_EXTERN extern -#endif - -// -// GTLR_ASSERT defaults to bridging to NSAssert. This macro exists just in case -// it needs to be remapped. -// GTLR_DEBUG_ASSERT is similar, but compiles in only for debug builds -// - -#ifndef GTLR_ASSERT - // NSCAssert to avoid capturing self if used in a block. - #define GTLR_ASSERT(condition, ...) NSCAssert(condition, __VA_ARGS__) -#endif // GTLR_ASSERT - -#ifndef GTLR_DEBUG_ASSERT - #if DEBUG && !defined(NS_BLOCK_ASSERTIONS) - #define GTLR_DEBUG_ASSERT(condition, ...) GTLR_ASSERT(condition, __VA_ARGS__) - #elif DEBUG - // In DEBUG builds with assertions blocked, log to avoid unused variable warnings. - #define GTLR_DEBUG_ASSERT(condition, ...) if (!(condition)) { NSLog(__VA_ARGS__); } - #else - #define GTLR_DEBUG_ASSERT(condition, ...) do { } while (0) - #endif -#endif - -#ifndef GTLR_DEBUG_LOG - #if DEBUG - #define GTLR_DEBUG_LOG(...) NSLog(__VA_ARGS__) - #else - #define GTLR_DEBUG_LOG(...) do { } while (0) - #endif -#endif - -#ifndef GTLR_DEBUG_ASSERT_CURRENT_QUEUE - #define GTLR_ASSERT_CURRENT_QUEUE_DEBUG(targetQueue) \ - GTLR_DEBUG_ASSERT(0 == strcmp(GTLR_QUEUE_NAME(targetQueue), \ - GTLR_QUEUE_NAME(DISPATCH_CURRENT_QUEUE_LABEL)), \ - @"Current queue is %s (expected %s)", \ - GTLR_QUEUE_NAME(DISPATCH_CURRENT_QUEUE_LABEL), \ - GTLR_QUEUE_NAME(targetQueue)) - - #define GTLR_QUEUE_NAME(queue) \ - (strlen(dispatch_queue_get_label(queue)) > 0 ? dispatch_queue_get_label(queue) : "unnamed") -#endif // GTLR_ASSERT_CURRENT_QUEUE_DEBUG - -// Sanity check the min versions. - -#if (defined(TARGET_OS_TV) && TARGET_OS_TV) || (defined(TARGET_OS_WATCH) && TARGET_OS_WATCH) - // No min checks for these two. -#elif TARGET_OS_IPHONE - #if !defined(__IPHONE_9_0) || (__IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0) - #error "This project expects to be compiled with the iOS 9.0 SDK (or later)." - #endif - #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0 - #error "The minimum supported iOS version is 7.0." - #endif -#elif TARGET_OS_MAC - #if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) - #error "This project expects to be compiled with the OS X 10.10 SDK (or later)." - #endif - #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9 - #error "The minimum supported OS X version is 10.9." - #endif -#else - #error "Unknown target platform." -#endif - -// Version marker used to validate the generated sources against the library -// version. The will be changed any time the library makes a change that means -// past sources need to be regenerated. -#define GTLR_RUNTIME_VERSION 3000 diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchQuery.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchQuery.h @@ -1,85 +0,0 @@ -/* 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. - */ - -// Batch query documentation: -// https://github.com/google/google-api-objectivec-client-for-rest/wiki#batch-operations - -#import "GTLRQuery.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface GTLRBatchQuery : NSObject <GTLRQueryProtocol> - -/** - * Queries included in this batch. Each query should have a unique @c requestID. - */ -@property(atomic, copy, nullable) NSArray<GTLRQuery *> *queries; - -/** - * Flag indicating if query execution should skip authorization. Defaults to NO. - */ -@property(atomic, assign) BOOL shouldSkipAuthorization; - -/** - * Any additional HTTP headers for this batch. - * - * These headers override the same keys from the service object's - * @c additionalHTTPHeaders. - */ -@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalHTTPHeaders; - -/** - * Any additional URL query parameters to add to the batch query. - * - * These query parameters override the same keys from the service object's - * @c additionalURLQueryParameters - */ -@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalURLQueryParameters; - -/** - * The batch request multipart boundary, once determined. - */ -@property(atomic, copy, nullable) NSString *boundary; - -/** - * The brief string to identify this query in @c GTMSessionFetcher http logs. - * - * The default logging name for batch requests includes the API method names. - */ -@property(atomic, copy, nullable) NSString *loggingName; - -/** - * Constructor for a batch query, for use with @c addQuery: - */ -+ (instancetype)batchQuery; - -/** - * Constructor for a batch query, from an array of @c GTLRQuery objects. - */ -+ (instancetype)batchQueryWithQueries:(NSArray<GTLRQuery *> *)array; - -/** - * Add a single @c GTLRQuery to the batch. - */ -- (void)addQuery:(GTLRQuery *)query; - -/** - * Search the batch for a query with the specified ID. - */ -- (nullable GTLRQuery *)queryForRequestID:(NSString *)requestID; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchQuery.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchQuery.m @@ -1,179 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRBatchQuery.h" - -#import "GTLRService.h" - -#if DEBUG -static void DebugAssertValidBatchQueryItem(GTLRQuery *query) { - GTLR_DEBUG_ASSERT([query isKindOfClass:[GTLRQuery class]], - @"unexpected query class: %@", [query class]); - GTLR_DEBUG_ASSERT(query.uploadParameters == nil, - @"batch may not contain upload: %@", query); - GTLR_DEBUG_ASSERT(!query.hasExecutionParameters, - @"queries added to a batch may not contain executionParameters: %@", query); - GTLR_DEBUG_ASSERT(!query.queryInvalid, - @"batch may not contain query already executed: %@", query); -} -#else -static void DebugAssertValidBatchQueryItem(GTLRQuery *query) { } -#endif - -@implementation GTLRBatchQuery { - NSMutableArray<GTLRQuery *> *_queries; - NSMutableDictionary *_requestIDMap; - GTLRServiceExecutionParameters *_executionParameters; -} - -@synthesize shouldSkipAuthorization = _shouldSkipAuthorization, - additionalHTTPHeaders = _additionalHTTPHeaders, - additionalURLQueryParameters = _additionalURLQueryParameters, - boundary = _boundary, - loggingName = _loggingName; - -+ (instancetype)batchQuery { - GTLRBatchQuery *obj = [[self alloc] init]; - return obj; -} - -+ (instancetype)batchQueryWithQueries:(NSArray<GTLRQuery *> *)queries { - GTLRBatchQuery *obj = [self batchQuery]; - obj.queries = queries; - -#if DEBUG - for (GTLRQuery *query in queries) { - DebugAssertValidBatchQueryItem(query); - } -#endif - return obj; -} - -- (id)copyWithZone:(NSZone *)zone { - // Deep copy the list of queries - GTLRBatchQuery *newBatch = [[[self class] allocWithZone:zone] init]; - if (_queries) { - newBatch.queries = [[NSArray alloc] initWithArray:_queries - copyItems:YES]; - } - - // Using the executionParameters ivar avoids creating the object. - newBatch.executionParameters = _executionParameters; - - // Copied in the same order as synthesized above. - newBatch.shouldSkipAuthorization = _shouldSkipAuthorization; - newBatch.additionalHTTPHeaders = _additionalHTTPHeaders; - newBatch.additionalURLQueryParameters = _additionalURLQueryParameters; - newBatch.boundary = _boundary; - newBatch.loggingName = _loggingName; - - // No need to copy _requestIDMap as it's created on demand. - return newBatch; -} - -- (NSString *)description { - NSArray *queries = self.queries; - NSArray *loggingNames = [queries valueForKey:@"loggingName"]; - NSMutableSet *dedupedNames = [NSMutableSet setWithArray:loggingNames]; // de-dupe - [dedupedNames removeObject:[NSNull null]]; // In case any didn't have a loggingName. - NSString *namesStr = [[dedupedNames allObjects] componentsJoinedByString:@","]; - - return [NSString stringWithFormat:@"%@ %p (queries:%tu - %@)", - [self class], self, queries.count, namesStr]; -} - -#pragma mark - - -- (BOOL)isBatchQuery { - return YES; -} - -- (GTLRUploadParameters *)uploadParameters { - // File upload is not supported for batches - return nil; -} - -- (void)invalidateQuery { - NSArray *queries = self.queries; - [queries makeObjectsPerformSelector:@selector(invalidateQuery)]; - - _executionParameters = nil; -} - -- (GTLRQuery *)queryForRequestID:(NSString *)requestID { - GTLRQuery *result = [_requestIDMap objectForKey:requestID]; - if (result) return result; - - // We've not before tried to look up a query, or the map is stale - _requestIDMap = [[NSMutableDictionary alloc] init]; - - for (GTLRQuery *query in _queries) { - [_requestIDMap setObject:query forKey:query.requestID]; - } - - result = [_requestIDMap objectForKey:requestID]; - return result; -} - -#pragma mark - - -- (void)setQueries:(NSArray<GTLRQuery *> *)array { -#if DEBUG - for (GTLRQuery *query in array) { - DebugAssertValidBatchQueryItem(query); - } -#endif - - _queries = [array mutableCopy]; -} - -- (NSArray<GTLRQuery *> *)queries { - return _queries; -} - -- (void)addQuery:(GTLRQuery *)query { - DebugAssertValidBatchQueryItem(query); - - if (_queries == nil) { - _queries = [[NSMutableArray alloc] init]; - } - - [_queries addObject:query]; -} - -- (GTLRServiceExecutionParameters *)executionParameters { - @synchronized(self) { - if (!_executionParameters) { - _executionParameters = [[GTLRServiceExecutionParameters alloc] init]; - } - } - return _executionParameters; -} - -- (void)setExecutionParameters:(GTLRServiceExecutionParameters *)executionParameters { - @synchronized(self) { - _executionParameters = executionParameters; - } -} - -- (BOOL)hasExecutionParameters { - return _executionParameters.hasParameters; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchResult.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchResult.h @@ -1,78 +0,0 @@ -/* 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 "GTLRObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@class GTLRErrorObject; - -/** - * A batch result includes a dictionary of successes, a dictionary of failures, and a dictionary of - * HTTP response headers. - * - * Dictionary keys are request ID strings; dictionary values are @c GTLRObject for - * successes, @c GTLRErrorObject for failures, @c NSDictionary for responseHeaders. - * - * For successes with no returned object (such as from delete operations), - * the object for the dictionary entry is @c NSNull. - * - * The original query for each result is available from the service ticket, as shown in - * the code snippet here. - * - * When the queries in the batch are unrelated, adding a @c completionBlock to each of - * the queries may be a simpler way to handle the batch results. - * - * @code - * NSDictionary *successes = batchResults.successes; - * for (NSString *requestID in successes) { - * GTLRObject *obj = successes[requestID]; - * GTLRQuery *query = [ticket queryForRequestID:requestID]; - * NSLog(@"Query %@ returned object %@", query, obj); - * } - * - * NSDictionary *failures = batchResults.failures; - * for (NSString *requestID in failures) { - * GTLRErrorObject *errorObj = failures[requestID]; - * GTLRQuery *query = [ticket queryForRequestID:requestID]; - * NSLog(@"Query %@ failed with error %@", query, errorObj); - * } - * @endcode - */ -@interface GTLRBatchResult : GTLRObject - -/** - * Object results of successful queries in the batch, keyed by request ID. - * - * Queries which do not return an object when successful have a @c NSNull value. - */ -@property(atomic, strong, nullable) NSDictionary<NSString *, __kindof GTLRObject *> *successes; - -/** - * Object results of unsuccessful queries in the batch, keyed by request ID. - */ -@property(atomic, strong, nullable) NSDictionary<NSString *, GTLRErrorObject *> *failures; - -/** - * Any HTTP response headers that were returned for a query request. Headers are optional therefore - * not all queries will have them. Query request with response headers are stored in a - * dictionary and keyed by request ID. - */ -@property(atomic, strong, nullable) - NSDictionary<NSString *, NSDictionary *> *responseHeaders; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchResult.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRBatchResult.m @@ -1,168 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRBatchResult.h" - -#import "GTLRErrorObject.h" -#import "GTLRUtilities.h" - -static NSString *const kGTLRBatchResultSuccessesKeys = @"successesKeys"; -static NSString *const kGTLRBatchResultSuccessKeyPrefix = @"Success-"; -static NSString *const kGTLRBatchResultFailuresKeys = @"failuresKeys"; -static NSString *const kGTLRBatchResultFailurKeyPrefix = @"Failure-"; -static NSString *const kGTLRBatchResultResponseHeaders = @"responseHeaders"; - -@implementation GTLRBatchResult - -@synthesize successes = _successes, - failures = _failures, - responseHeaders = _responseHeaders; - -// Since this class doesn't use the json property, provide the basic NSObject -// methods needed to ensure proper behaviors. - -- (id)copyWithZone:(NSZone *)zone { - GTLRBatchResult* newObject = [super copyWithZone:zone]; - newObject.successes = [self.successes copyWithZone:zone]; - newObject.failures = [self.failures copyWithZone:zone]; - newObject.responseHeaders = [self.responseHeaders copyWithZone:zone]; - return newObject; -} - -- (NSUInteger)hash { - NSUInteger result = [super hash]; - result += result * 13 + [self.successes hash]; - result += result * 13 + [self.failures hash]; - result += result * 13 + [self.responseHeaders hash]; - return result; -} - -- (BOOL)isEqual:(id)object { - if (self == object) return YES; - - if (![super isEqual:object]) { - return NO; - } - - if (![object isKindOfClass:[GTLRBatchResult class]]) { - return NO; - } - - GTLRBatchResult *other = (GTLRBatchResult *)object; - if (!GTLR_AreEqualOrBothNil(self.successes, other.successes)) { - return NO; - } - if (!GTLR_AreEqualOrBothNil(self.failures, other.failures)) { - return NO; - } - return GTLR_AreEqualOrBothNil(self.responseHeaders, other.responseHeaders); -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p (successes:%tu failures:%tu responseHeaders:%tu)", - [self class], self, - self.successes.count, self.failures.count, self.responseHeaders.count]; -} - -// This class is a subclass of GTLRObject, which declares NSSecureCoding -// conformance. Since this class does't really use the json property, provide -// a custom implementation to maintain the contract. -// -// For success/failures, one could do: -// [encoder encodeObject:self.successes forKey:kGTLRBatchResultSuccesses]; -// [encoder encodeObject:self.failures forKey:kGTLRBatchResultFailuresKeys]; -// and then use -decodeObjectOfClasses:forKey:, but nothing actually checks the -// structure of the dictionary, so instead the dicts are blown out to provide -// better validation by the encoder/decoder. - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - self = [super initWithCoder:decoder]; - if (self) { - NSArray<NSString *> *keys = - [decoder decodeObjectOfClass:[NSArray class] - forKey:kGTLRBatchResultSuccessesKeys]; - if (keys.count) { - NSMutableDictionary *dict = - [NSMutableDictionary dictionaryWithCapacity:keys.count]; - for (NSString *key in keys) { - NSString *storageKey = - [kGTLRBatchResultSuccessKeyPrefix stringByAppendingString:key]; - GTLRObject *obj = [decoder decodeObjectOfClass:[GTLRObject class] - forKey:storageKey]; - if (obj) { - [dict setObject:obj forKey:key]; - } - } - self.successes = dict; - } - - keys = [decoder decodeObjectOfClass:[NSArray class] - forKey:kGTLRBatchResultFailuresKeys]; - if (keys.count) { - NSMutableDictionary *dict = - [NSMutableDictionary dictionaryWithCapacity:keys.count]; - for (NSString *key in keys) { - NSString *storageKey = - [kGTLRBatchResultFailurKeyPrefix stringByAppendingString:key]; - GTLRObject *obj = [decoder decodeObjectOfClass:[GTLRObject class] - forKey:storageKey]; - if (obj) { - [dict setObject:obj forKey:key]; - } - } - self.failures = dict; - } - - self.responseHeaders = - [decoder decodeObjectOfClass:[NSDictionary class] - forKey:kGTLRBatchResultResponseHeaders]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)encoder { - [super encodeWithCoder:encoder]; - [encoder encodeObject:self.successes.allKeys - forKey:kGTLRBatchResultSuccessesKeys]; - [self.successes enumerateKeysAndObjectsUsingBlock:^(NSString *key, - GTLRObject * obj, - BOOL * stop) { - NSString *storageKey = - [kGTLRBatchResultSuccessKeyPrefix stringByAppendingString:key]; - [encoder encodeObject:obj forKey:storageKey]; - }]; - - [encoder encodeObject:self.failures.allKeys forKey:kGTLRBatchResultFailuresKeys]; - [self.failures enumerateKeysAndObjectsUsingBlock:^(NSString *key, - GTLRObject * obj, - BOOL * stop) { - NSString *storageKey = - [kGTLRBatchResultFailurKeyPrefix stringByAppendingString:key]; - [encoder encodeObject:obj forKey:storageKey]; - }]; - - [encoder encodeObject:self.responseHeaders - forKey:kGTLRBatchResultResponseHeaders]; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDateTime.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDateTime.h @@ -1,115 +0,0 @@ -/* Copyright (c) 2011 Google Inc. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -#import <Foundation/Foundation.h> -#import "GTLRDefines.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * An immutable class representing a date and optionally a time in UTC. - */ -@interface GTLRDateTime : NSObject <NSCopying> - -/** - * Constructor from a string representation. - */ -+ (nullable instancetype)dateTimeWithRFC3339String:(nullable NSString *)str; - -/** - * Constructor from a date and time representation. - */ -+ (instancetype)dateTimeWithDate:(NSDate *)date; - -/** - * Constructor from a date and time representation, along with an offset - * minutes value used when creating a RFC3339 string representation. - * - * The date value is independent of time zone; the offset affects how the - * date will be rendered as a string. - * - * The offsetMinutes may be initialized from a NSTimeZone as - * (timeZone.secondsFromGMT / 60) - */ -+ (instancetype)dateTimeWithDate:(NSDate *)date - offsetMinutes:(NSInteger)offsetMinutes; - -/** - * Constructor from a date for an all-day event. - * - * Use this constructor to create a @c GTLRDateTime that is "date only". - * - * @note @c hasTime will be set to NO. - */ -+ (instancetype)dateTimeForAllDayWithDate:(NSDate *)date; - -/** - * Constructor from date components. - */ -+ (instancetype)dateTimeWithDateComponents:(NSDateComponents *)date; - -/** - * The represented date and time. - * - * If @c hasTime is NO, the time is set to noon GMT so the date is valid for all time zones. - */ -@property(nonatomic, readonly) NSDate *date; - -/** - * The date and time as a RFC3339 string representation. - */ -@property(nonatomic, readonly) NSString *RFC3339String; - -/** - * The date and time as a RFC3339 string representation. - * - * This returns the same string as @c RFC3339String. - */ -@property(nonatomic, readonly) NSString *stringValue; - -/** - * The represented date and time as date components. - */ -@property(nonatomic, readonly, copy) NSDateComponents *dateComponents; - -/** - * The fraction of seconds represented, 0-999. - */ -@property(nonatomic, readonly) NSInteger milliseconds; - -/** - * The time offset displayed in the string representation, if any. - * - * If the offset is not nil, the date and time will be rendered as a string - * for the time zone indicated by the offset. - * - * An app may create a NSTimeZone for this with - * [NSTimeZone timeZoneForSecondsFromGMT:(offsetMinutes.integerValue * 60)] - */ -@property(nonatomic, readonly, nullable) NSNumber *offsetMinutes; - -/** - * Flag indicating if the object represents date only, or date with time. - */ -@property(nonatomic, readonly) BOOL hasTime; - -/** - * The calendar used by this class, Gregorian and UTC. - */ -+ (NSCalendar *)calendar; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDateTime.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDateTime.m @@ -1,373 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRDateTime.h" - -static NSUInteger const kGTLRDateComponentBits = (NSCalendarUnitYear | NSCalendarUnitMonth - | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute - | NSCalendarUnitSecond); - -@interface GTLRDateTime () - -- (void)setFromDate:(NSDate *)date; -- (void)setFromRFC3339String:(NSString *)str; - -@property(nonatomic, copy, readwrite) NSDateComponents *dateComponents; -@property(nonatomic, assign, readwrite) NSInteger milliseconds; -@property(nonatomic, strong, readwrite, nullable) NSNumber *offsetMinutes; - -@property(nonatomic, assign, readwrite) BOOL hasTime; - -@end - - -@implementation GTLRDateTime { - NSDate *_cachedDate; - NSString *_cachedRFC3339String; -} - -// 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. GTLR 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 RFC3339String; -@dynamic stringValue; -@dynamic hasTime; - -@synthesize dateComponents = _dateComponents, - milliseconds = _milliseconds, - offsetMinutes = _offsetMinutes; - -+ (instancetype)dateTimeWithRFC3339String:(NSString *)str { - if (str == nil) return nil; - - GTLRDateTime *result = [[self alloc] init]; - [result setFromRFC3339String:str]; - return result; -} - -+ (instancetype)dateTimeWithDate:(NSDate *)date { - if (date == nil) return nil; - - GTLRDateTime *result = [[self alloc] init]; - [result setFromDate:date]; - return result; -} - -+ (instancetype)dateTimeWithDate:(NSDate *)date - offsetMinutes:(NSInteger)offsetMinutes { - GTLRDateTime *result = [self dateTimeWithDate:date]; - result.offsetMinutes = @(offsetMinutes); - return result; -} - -+ (instancetype)dateTimeForAllDayWithDate:(NSDate *)date { - if (date == nil) return nil; - - GTLRDateTime *result = [[self alloc] init]; - [result setFromDate:date]; - result.hasTime = NO; - return result; -} - -+ (instancetype)dateTimeWithDateComponents:(NSDateComponents *)components { - NSCalendar *cal = components.calendar ?: [self calendar]; - NSDate *date = [cal dateFromComponents:components]; - - return [self dateTimeWithDate:date]; -} - -- (id)copyWithZone:(NSZone *)zone { - // Object is immutable - return self; -} - -- (BOOL)isEqual:(GTLRDateTime *)other { - if (self == other) return YES; - if (![other isKindOfClass:[GTLRDateTime class]]) return NO; - - BOOL areDateComponentsEqual = [self.dateComponents isEqual:other.dateComponents]; - if (!areDateComponentsEqual) return NO; - - NSNumber *offsetMinutes = self.offsetMinutes; - NSNumber *otherOffsetMinutes = other.offsetMinutes; - if ((offsetMinutes == nil) != (otherOffsetMinutes == nil) - || (offsetMinutes.integerValue != otherOffsetMinutes.integerValue)) return NO; - - return (self.milliseconds == other.milliseconds); -} - -- (NSUInteger)hash { - return [[self date] hash]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p: {%@}", - [self class], self, self.RFC3339String]; -} - -- (NSDate *)date { - @synchronized(self) { - if (_cachedDate) return _cachedDate; - } - - NSDateComponents *dateComponents = self.dateComponents; - NSTimeInterval extraMillisecondsAsSeconds = 0.0; - NSCalendar *cal = [[self class] calendar]; - - 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]; - [noonDateComponents setHour:12]; - [noonDateComponents setMinute:0]; - [noonDateComponents setSecond:0]; - dateComponents = noonDateComponents; - } 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) { - date = [date dateByAddingTimeInterval:extraMillisecondsAsSeconds]; - } - - @synchronized(self) { - _cachedDate = date; - } - return date; -} - -- (NSString *)stringValue { - return self.RFC3339String; -} - -- (NSString *)RFC3339String { - @synchronized(self) { - if (_cachedRFC3339String) return _cachedRFC3339String; - } - - NSDateComponents *dateComponents = self.dateComponents; - - NSString *timeString = @""; // timeString like "T15:10:46-08:00" - - if (self.hasTime) { - NSString *fractionalSecondsString = @""; - if (self.milliseconds > 0.0) { - fractionalSecondsString = [NSString stringWithFormat:@".%03ld", (long)self.milliseconds]; - } - - // If the dateTime was created from a string with a time offset, render that back in - // and adjust the time. - NSString *offsetStr = @"Z"; - NSNumber *offsetMinutes = self.offsetMinutes; - if (offsetMinutes != nil) { - BOOL isNegative = NO; - NSInteger offsetVal = offsetMinutes.integerValue; - if (offsetVal < 0) { - isNegative = YES; - offsetVal = -offsetVal; - } - NSInteger mins = offsetVal % 60; - NSInteger hours = (offsetVal - mins) / 60; - offsetStr = [NSString stringWithFormat:@"%c%02ld:%02ld", - isNegative ? '-' : '+', (long)hours, (long)mins]; - - // Adjust date components back to account for the offset. - // - // This is the inverse of the adjustment done in setFromRFC3339String:. - if (offsetVal != 0) { - NSDate *adjustedDate = - [self.date dateByAddingTimeInterval:(offsetMinutes.integerValue * 60)]; - NSCalendar *calendar = [[self class] calendar]; - dateComponents = [calendar components:kGTLRDateComponentBits - fromDate:adjustedDate]; - } - } - - timeString = [NSString stringWithFormat:@"T%02ld:%02ld:%02ld%@%@", - (long)dateComponents.hour, (long)dateComponents.minute, - (long)dateComponents.second, fractionalSecondsString, - offsetStr]; - } - - // 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]; - - @synchronized(self) { - _cachedRFC3339String = dateString; - } - return dateString; -} - -- (void)setFromDate:(NSDate *)date { - NSCalendar *cal = [[self class] calendar]; - - NSDateComponents *components = [cal components:kGTLRDateComponentBits - 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); -} - -- (void)setFromRFC3339String:(NSString *)str { - static NSCharacterSet *gDashSet; - static NSCharacterSet *gTSet; - static NSCharacterSet *gColonSet; - static NSCharacterSet *gPlusMinusZSet; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - gDashSet = [NSCharacterSet characterSetWithCharactersInString:@"-"]; - gTSet = [NSCharacterSet characterSetWithCharactersInString:@"Tt "]; - gColonSet = [NSCharacterSet characterSetWithCharactersInString:@":"]; - gPlusMinusZSet = [NSCharacterSet characterSetWithCharactersInString:@"+-zZ"]; - }); - - NSInteger year = NSDateComponentUndefined; - NSInteger month = NSDateComponentUndefined; - NSInteger day = NSDateComponentUndefined; - NSInteger hour = NSDateComponentUndefined; - NSInteger minute = NSDateComponentUndefined; - NSInteger sec = NSDateComponentUndefined; - 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]; - - // 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:gDashSet intoString:NULL] && - [scanner scanInteger:&month] && - [scanner scanCharactersFromSet:gDashSet intoString:NULL] && - [scanner scanInteger:&day] && - // Thh:mm:ss - [scanner scanCharactersFromSet:gTSet intoString:NULL] && - [scanner scanInteger:&hour] && - [scanner scanCharactersFromSet:gColonSet intoString:NULL] && - [scanner scanInteger:&minute] && - [scanner scanCharactersFromSet:gColonSet 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:gPlusMinusZSet intoString:&sign] && - [scanner scanInteger:&offsetHour] && - [scanner scanCharactersFromSet:gColonSet intoString:NULL] && - [scanner scanInteger:&offsetMinute]) { - } - } - } - - NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; - [dateComponents setYear:year]; - [dateComponents setMonth:month]; - [dateComponents setDay:day]; - [dateComponents setHour:hour]; - [dateComponents setMinute:minute]; - [dateComponents setSecond:sec]; - - BOOL isMinusOffset = [sign isEqual:@"-"]; - if (isMinusOffset || [sign isEqual:@"+"]) { - NSInteger totalOffsetMinutes = ((offsetHour * 60) + offsetMinute) * (isMinusOffset ? -1 : 1); - self.offsetMinutes = @(totalOffsetMinutes); - - // Minus offset means Universal time is that many hours and minutes ahead. - // - // This is the inverse of the adjustment done above in RFC3339String. - NSTimeInterval deltaOffsetSeconds = -totalOffsetMinutes * 60; - NSCalendar *calendar = [[self class] calendar]; - NSDate *scannedDate = [calendar dateFromComponents:dateComponents]; - NSDate *offsetDate = [scannedDate dateByAddingTimeInterval:deltaOffsetSeconds]; - - dateComponents = [calendar components:kGTLRDateComponentBits - fromDate:offsetDate]; - } - - self.dateComponents = dateComponents; - self.milliseconds = milliseconds; -} - -- (BOOL)hasTime { - NSDateComponents *dateComponents = self.dateComponents; - - BOOL hasTime = ([dateComponents hour] != NSDateComponentUndefined - && [dateComponents minute] != NSDateComponentUndefined); - - return hasTime; -} - -- (void)setHasTime:(BOOL)shouldHaveTime { - // We'll set time values to zero or kUndefinedDateComponent as appropriate. - BOOL hadTime = self.hasTime; - - if (shouldHaveTime && !hadTime) { - [_dateComponents setHour:0]; - [_dateComponents setMinute:0]; - [_dateComponents setSecond:0]; - _milliseconds = 0; - } else if (hadTime && !shouldHaveTime) { - [_dateComponents setHour:NSDateComponentUndefined]; - [_dateComponents setMinute:NSDateComponentUndefined]; - [_dateComponents setSecond:NSDateComponentUndefined]; - _milliseconds = 0; - } -} - -+ (NSCalendar *)calendar { - NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; - cal.timeZone = (NSTimeZone * _Nonnull)[NSTimeZone timeZoneWithName:@"Universal"]; - return cal; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDuration.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDuration.h @@ -1,83 +0,0 @@ -/* Copyright (c) 2016 Google Inc. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -#import <Foundation/Foundation.h> -#import "GTLRDefines.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * An immutable class representing a string data type 'google-duration'. - * It is based off the protocol buffers definition: - * https://github.com/google/protobuf/blob/master/src/google/protobuf/duration.proto - */ -@interface GTLRDuration : NSObject <NSCopying> - -/** - * Signed seconds of the span of time. Must be from -315,576,000,000 - * to +315,576,000,000 inclusive. - **/ -@property(nonatomic, readonly) int64_t seconds; - -/** - * Signed fractions of a second at nanosecond resolution of the span - * of time. Durations less than one second are represented with a 0 - * `seconds` field and a positive or negative `nanos` field. For durations - * of one second or more, a non-zero value for the `nanos` field must be - * of the same sign as the `seconds` field. Must be from -999,999,999 - * to +999,999,999 inclusive. - **/ -@property(nonatomic, readonly) int32_t nanos; - -/** - * This duration expressed as a NSTimeInterval. - * - * @note: Not all second/nanos combinations can be represented in a - * NSTimeInterval, so this could be a lossy transform. - **/ -@property(nonatomic, readonly) NSTimeInterval timeInterval; - -/** - * Returns the string form used to send this data type in a JSON payload. - */ -@property(nonatomic, readonly) NSString *jsonString; - -/** - * Constructor for a new duration with the given seconds and nanoseconds. - * - * Will fail if seconds/nanos differ in sign or if nanos is more than one - * second. - **/ -+ (nullable instancetype)durationWithSeconds:(int64_t)seconds - nanos:(int32_t)nanos; - -/** - * Constructor for a new duration from the given string form. - * - * Will return nil if jsonString is invalid. - **/ -+ (nullable instancetype)durationWithJSONString:(nullable NSString *)jsonString; - -/** - * Constructor for a new duration from the NSTimeInterval. - * - * @note NSTimeInterval doesn't always express things as exactly as one might - * expect, so coverting from to integer seconds & nanos can reveal this. - **/ -+ (instancetype)durationWithTimeInterval:(NSTimeInterval)timeInterval; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDuration.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRDuration.m @@ -1,222 +0,0 @@ -/* Copyright (c) 2016 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRDuration.h" - -static const int32_t kNanosPerMillisecond = 1000000; -static const int32_t kNanosPerMicrosecond = 1000; -static const int32_t kNanosPerSecond = 1000000000; - -static int32_t IntPow10(int x) { - int32_t result = 1; - for (int i = 0; i < x; ++i) { - result *= 10; - } - return result; -} - -@implementation GTLRDuration - -@dynamic timeInterval; - -@synthesize seconds = _seconds, - nanos = _nanos, - jsonString = _jsonString; - -+ (instancetype)durationWithSeconds:(int64_t)seconds nanos:(int32_t)nanos { - if (seconds < 0) { - if (nanos > 0) { - // secs was -, nanos was + - return nil; - } - } else if (seconds > 0) { - if (nanos < 0) { - // secs was +, nanos was - - return nil; - } - } - if ((nanos <= -kNanosPerSecond) || (nanos >= kNanosPerSecond)) { - // more than a seconds worth - return nil; - } - return [[self alloc] initWithSeconds:seconds nanos:nanos jsonString:NULL]; -} - -+ (instancetype)durationWithJSONString:(NSString *)jsonString { - // It has to end in "s", so it needs >1 character. - if (jsonString.length <= 1) { - return nil; - } - - static NSCharacterSet *gNumberSet; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - gNumberSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"]; - }); - - NSScanner* scanner = [NSScanner scannerWithString:jsonString]; - // There should be no whitespace, so no skip characters. - [scanner setCharactersToBeSkipped:nil]; - - // Can start with a '-'. - BOOL isNeg = [scanner scanString:@"-" intoString:NULL]; - - int64_t seconds; - if (![scanner scanLongLong:&seconds]) { - return nil; - } - - // Since the sign was manually scanned, seconds should be positive - // (i.e. no "--#" in the put). - if (seconds < 0) { - return nil; - } - - // See if it has a ".[nanos]". Spec seems to say it is required, but play - // it safe and make it optional. - int32_t nanos = 0; - if ([scanner scanString:@"." intoString:NULL]) { - NSString *nanosStr; - if (![scanner scanCharactersFromSet:gNumberSet intoString:&nanosStr]) { - return nil; - } - // Ensure not too many digits (also ensure it is within range). - if (nanosStr.length > 9) { - return nil; - } - // Can use NSString's intValue since the character set was controlled. - nanos = [nanosStr intValue]; - // Scale based on length. - nanos *= IntPow10(9 - (int)nanosStr.length); - } - - // And must have the final 's'. - if (![scanner scanString:@"s" intoString:NULL]) { - return nil; - } - - // Better be the end... - if (![scanner isAtEnd]) { - return nil; - } - - if (isNeg) { - seconds = -seconds; - nanos = -nanos; - } - - // Pass on the json string so it will be reflected back out as it came in - // (incase it had a different number of digits, etc). - return [[self alloc] initWithSeconds:seconds - nanos:nanos - jsonString:jsonString]; -} - -+ (instancetype)durationWithTimeInterval:(NSTimeInterval)timeInterval { - NSTimeInterval seconds; - NSTimeInterval nanos = modf(timeInterval, &seconds); - nanos *= (NSTimeInterval)kNanosPerSecond; - - return [[self alloc] initWithSeconds:(int64_t)seconds - nanos:(int32_t)nanos - jsonString:NULL]; -} - -- (instancetype)init { - return [self initWithSeconds:0 nanos:0 jsonString:NULL]; -} - -- (instancetype)initWithSeconds:(int64_t)seconds - nanos:(int32_t)nanos - jsonString:(NSString *)jsonString { - self = [super init]; - if (self) { - // Sanity asserts, the class methods should make sure this doesn't happen. - GTLR_DEBUG_ASSERT((((seconds <= 0) && (nanos <= 0)) || - ((seconds >= 0) && (nanos >= 0))), - @"Seconds and nanos must have the same sign (%lld & %d)", - seconds, nanos); - GTLR_DEBUG_ASSERT(((nanos < kNanosPerSecond) && - (nanos > -kNanosPerSecond)), - @"Nanos is a second or more (%d)", nanos); - - _seconds = seconds; - _nanos = nanos; - - if (jsonString.length) { - _jsonString = [jsonString copy]; - } else { - // Based off the JSON serialization code in protocol buffers - // ( https://github.com/google/protobuf/ ). - NSString *sign = @""; - if ((seconds < 0) || (nanos < 0)) { - sign = @"-"; - seconds = -seconds; - nanos = -nanos; - } - int nanoDigts; - int32_t nanoDivider; - if (nanos % kNanosPerMillisecond == 0) { - nanoDigts = 3; - nanoDivider = kNanosPerMillisecond; - } else if (nanos % kNanosPerMicrosecond == 0) { - nanoDigts = 6; - nanoDivider = kNanosPerMicrosecond; - } else { - nanoDigts = 9; - nanoDivider = 1; - } - _jsonString = [NSString stringWithFormat:@"%@%lld.%0*ds", - sign, seconds, nanoDigts, (nanos / nanoDivider)]; - } - } - return self; -} - -- (NSTimeInterval)timeInterval { - NSTimeInterval result = self.seconds; - result += (NSTimeInterval)self.nanos / (NSTimeInterval)kNanosPerSecond; - return result; -} - -- (id)copyWithZone:(NSZone *)zone { - // Object is immutable - return self; -} - -- (BOOL)isEqual:(GTLRDuration *)other { - if (self == other) return YES; - if (![other isKindOfClass:[GTLRDuration class]]) return NO; - - BOOL result = ((self.seconds == other.seconds) && - (self.nanos == other.nanos)); - return result; -} - -- (NSUInteger)hash { - NSUInteger result = (NSUInteger)((self.seconds * 13) + self.nanos); - return result; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p: {%@}", - [self class], self, self.jsonString]; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRErrorObject.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRErrorObject.h @@ -1,116 +0,0 @@ -/* 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 "GTLRObject.h" - -NS_ASSUME_NONNULL_BEGIN - -@class GTLRErrorObjectErrorItem; -@class GTLRErrorObjectDetail; - -/** - * This class wraps JSON responses (both V1 and V2 of Google JSON errors) and NSErrors. - * - * A GTLRErrorObject can be created using +objectWithJSON: or +objectWithFoundationError: - */ -@interface GTLRErrorObject : GTLRObject - -/** - * Convenience method for creating an error object from an NSError. - * - * @param error The @c NSError to be encapsulated by the @c GTLRErrorObject - * - * @return A @c GTLRErrorObject wrapping the NSError. - */ -+ (instancetype)objectWithFoundationError:(NSError *)error; - -/** - * Convenience utility for extracting the GTLRErrorObject that was used to create an NSError. - * - * @param foundationError The NSError that may have been obtained from a GTLRErrorObject. - * - * @return The GTLRErrorObject, nil if the error was not originally from a GTLRErrorObject. - */ -+ (nullable GTLRErrorObject *)underlyingObjectForError:(NSError *)foundationError; - -// -// V1 & V2 properties. -// - -/** - * The numeric error code. - */ -@property(nonatomic, strong, nullable) NSNumber *code; - -/** - * An error message string, typically provided by the API server. This is not localized, - * and its reliability depends on the API server. - */ -@property(nonatomic, strong, nullable) NSString *message; - -// -// V1 properties. -// - -/** - * Underlying errors that occurred on the server. - */ -@property(nonatomic, strong, nullable) NSArray<GTLRErrorObjectErrorItem *> *errors; - -// -// V2 properties -// - -/** - * A status error string, defined by the API server, such as "NOT_FOUND". - */ -@property(nonatomic, strong, nullable) NSString *status; - -/** - * Additional diagnostic error details provided by the API server. - */ -@property(nonatomic, strong, nullable) NSArray<GTLRErrorObjectDetail *> *details; - -/** - * An NSError, either underlying the error object or manufactured from the error object's - * properties. - */ -@property(nonatomic, readonly) NSError *foundationError; - -@end - -/** - * Class representing the items of the "errors" array inside the Google V1 error JSON. - * - * Client applications should not rely on the property values of these items. - */ -@interface GTLRErrorObjectErrorItem : GTLRObject -@property(nonatomic, strong, nullable) NSString *domain; -@property(nonatomic, strong, nullable) NSString *reason; -@property(nonatomic, strong, nullable) NSString *message; -@property(nonatomic, strong, nullable) NSString *location; -@end - -/** - * Class representing the items of the "details" array inside the Google V2 error JSON. - * - * Client applications should not rely on the property values of these items. - */ -@interface GTLRErrorObjectDetail : GTLRObject -@property(nonatomic, strong, nullable) NSString *type; -@property(nonatomic, strong, nullable) NSString *detail; -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRErrorObject.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRErrorObject.m @@ -1,140 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRErrorObject.h" - -#import "GTLRUtilities.h" -#import "GTLRService.h" - -static NSString *const kGTLRErrorObjectFoundationErrorKey = @"foundationError"; - -@implementation GTLRErrorObject { - NSError *_originalFoundationError; -} - -// V1 & V2 properties. -@dynamic code; -@dynamic message; - -// V1 properties. -@dynamic errors; - -// V2 properties. -@dynamic status; -@dynamic details; - -// Implemented below. -@dynamic foundationError; - -+ (instancetype)objectWithFoundationError:(NSError *)error { - GTLRErrorObject *object = [self object]; - object->_originalFoundationError = error; - object.code = @(error.code); - object.message = error.localizedDescription; - return object; -} - -+ (NSDictionary *)arrayPropertyToClassMap { - return @{ - @"errors" : [GTLRErrorObjectErrorItem class], - @"details" : [GTLRErrorObjectDetail class] - }; -} - -- (NSError *)foundationError { - // If there was an original foundation error, copy its userInfo as the basis for ours. - NSMutableDictionary *userInfo = - [NSMutableDictionary dictionaryWithDictionary:_originalFoundationError.userInfo]; - - // This structured GTLRErrorObject will be available in the error's userInfo - // dictionary. - userInfo[kGTLRStructuredErrorKey] = self; - - NSError *error; - if (_originalFoundationError) { - error = [NSError errorWithDomain:_originalFoundationError.domain - code:_originalFoundationError.code - userInfo:userInfo]; - } else { - NSString *reasonStr = self.message; - if (reasonStr) { - userInfo[NSLocalizedDescriptionKey] = reasonStr; - } - - error = [NSError errorWithDomain:kGTLRErrorObjectDomain - code:self.code.integerValue - userInfo:userInfo]; - } - return error; -} - -+ (GTLRErrorObject *)underlyingObjectForError:(NSError *)foundationError { - NSDictionary *userInfo = [foundationError userInfo]; - GTLRErrorObject *errorObj = [userInfo objectForKey:kGTLRStructuredErrorKey]; - return errorObj; -} - -- (BOOL)isEqual:(id)object { - // Include the underlying foundation error in equality checks. - if (self == object) return YES; - if (![super isEqual:object]) return NO; - if (![object isKindOfClass:[GTLRErrorObject class]]) return NO; - GTLRErrorObject *other = (GTLRErrorObject *)object; - return GTLR_AreEqualOrBothNil(_originalFoundationError, - other->_originalFoundationError); -} - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - self = [super initWithCoder:decoder]; - if (self) { - _originalFoundationError = - [decoder decodeObjectOfClass:[NSError class] - forKey:kGTLRErrorObjectFoundationErrorKey]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)encoder { - [super encodeWithCoder:encoder]; - [encoder encodeObject:_originalFoundationError - forKey:kGTLRErrorObjectFoundationErrorKey]; -} - -@end - -@implementation GTLRErrorObjectErrorItem -@dynamic domain; -@dynamic reason; -@dynamic message; -@dynamic location; -@end - -@implementation GTLRErrorObjectDetail -@dynamic type; -@dynamic detail; - -+ (NSDictionary *)propertyToJSONKeyMap { - return @{ @"type" : @"@type" }; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRObject.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRObject.h @@ -1,317 +0,0 @@ -/* 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. - */ - -// GTLRObject documentation: -// https://github.com/google/google-api-objectivec-client-for-rest/wiki#objects-and-queries - -#import <Foundation/Foundation.h> - -#import "GTLRDefines.h" -#import "GTLRDateTime.h" -#import "GTLRDuration.h" - -NS_ASSUME_NONNULL_BEGIN - -@class GTLRObject; - -/** - * Protocol that can be implemented to provide custom logic for what class - * should be created out of the given JSON. - */ -@protocol GTLRObjectClassResolver <NSObject> -- (Class)classForJSON:(NSDictionary *)json - defaultClass:(Class)defaultClass; -@end - -/** - * Standard GTLRObjectClassResolver used by the core library. - */ -@interface GTLRObjectClassResolver : NSObject<GTLRObjectClassResolver> - -/** - * Returns a resolver that will look up the 'kind' properties to find classes - * based on the JSON. - * - * The generated service classes provide a +kindStringToClassMap method for any - * mappings that were found from discovery when generating the service. - */ -+ (instancetype)resolverWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap; - -/** - * Returns a resolver that will look up the 'kind' properties to find classes - * based on the JSON and then applies mapping of surrogate classes to swap out - * specific classes. - * - * Surrogates are subclasses to be instantiated instead of standard classes - * when creating objects from the JSON. For example, this code will, for one query's - * execution, swap a service's default resolver for one that will then use - * MyCalendarEventSubclass instead of GTLRCalendarEvent and - * MyCalendarReminderSubclass instead of GTLRCalendarReminder. - * - * @code - * NSDictionary *surrogates = @{ - * [GTLRCalendarEvent class] : [MyCalendarEventSubclass class] - * [GTLRCalendarReminder class] : [MyCalendarReminderSubclass class], - * }; - * NSDictionary *serviceKindMap = [[calendarService class] kindStringToClassMap]; - * GTLRObjectClassResolver *updatedResolver = - * [GTLRObjectClassResolver resolverWithKindMap:serviceKindMap - * surrogates:surrogates]; - * query.executionParameters.objectClassResolver = updatedResolver; - * @endcode - * - * @note To install surrogates for all queries executed by the service, use - * the service's @c -setSurrogates method. - */ -+ (instancetype)resolverWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap - surrogates:(NSDictionary<Class, Class> *)surrogates; - -@end - -/** - * @c GTLRObject serves as the common superclass for classes wrapping JSON, errors, and other data - * passed in server requests and responses. - * - * @note This class is @em not safe for simultaneous use from multiple threads. Applications should - * serialize or protect access to a @c GTLRObject instance as they would for any standard - * Cocoa mutable container. - */ -@interface GTLRObject : NSObject <NSCopying, NSSecureCoding> - -/** - * The JSON underlying the property values for this object. - * - * The JSON should be accessed or set using the generated properties of a - * class derived from GTLRObject or with the methods @c setJSONValue:forKey: - * and @c JSONValueForKey: - * - * @note: Applications should use @c additionalPropertyForKey: when accessing - * API object properties that do not have generated @c \@property accessors. - */ -@property(nonatomic, strong, nullable) NSMutableDictionary *JSON; - -/** - * A dictionary retained by the object for the convenience of the client application. - * - * A client application may use this to retain any dictionary. - * - * The values of the user properties dictionary will not be sent to the server during - * query execution, and will not be copied by NSCopying or encoded by NSSecureCoding. - */ -@property(nonatomic, strong) NSDictionary *userProperties; - -///////////////////////////////////////////////////////////////////////////////////////////// -// -// Public methods -// -// These methods are intended for users of the library -// -///////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Constructor for an empty object. - */ -+ (instancetype)object; - -/** - * Constructor for an object including JSON. - */ -+ (instancetype)objectWithJSON:(nullable NSDictionary *)dict; - -/** - * Constructor for an object including JSON and providing a resolver to help - * select the correct classes for sub objects within the json. - * - * The generated services provide a default resolver (-objectClassResolver) - * that covers the kinds for that service. They also expose the kind mappings - * via the +kindStringToClassMap method. - */ -+ (instancetype)objectWithJSON:(nullable NSDictionary *)dict - objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver; - -/** - * The JSON for the object, or an empty string if there is no JSON or if the JSON - * dictionary cannot be represented as JSON. - */ -- (NSString *)JSONString; - -/** - * Generic access for setting entries in the JSON dictionary. This creates the JSON dictionary - * if necessary. - * - * @note: Applications should use @c setAdditionalProperty:forKey: when setting - * API object properties that do not have generated @c \@property accessors. - */ -- (void)setJSONValue:(nullable id)obj forKey:(nonnull NSString *)key; - -/** - * Generic access to the JSON dictionary. - * - * @note: Applications should use @c additionalPropertyForKey: when accessing - * API object properties that do not have generated @c \@property accessors. - */ -- (nullable id)JSONValueForKey:(NSString *)key; - -/** - * The list of keys in this object's JSON that are not listed as properties on the object. - */ -- (nullable NSArray<NSString *> *)additionalJSONKeys; - -/** - * Setter for any key in the JSON that is not listed as a @c \@property in the class declaration. - */ -- (void)setAdditionalProperty:(id)obj forName:(NSString *)name; - -/** - * Accessor for any key in the JSON that is not listed as a @c \@property in the class - * declaration. - */ -- (nullable id)additionalPropertyForName:(NSString *)name; - -/** - * A dictionary of all keys in the JSON that is not listed as a @c \@property in the class - * declaration. - */ -- (NSDictionary<NSString *, id> *)additionalProperties; - -/** - * A string for a partial query describing the fields present. - * - * @note Only the first element of any array is examined. - * - * @see https://developers.google.com/google-apps/tasks/performance?csw=1#partial - * - * @return A @c fields string describing the fields present in the object. - */ -- (NSString *)fieldsDescription; - -/** - * 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 - * @c GTLRSomeObject *patchObject = [newVersion patchObjectFromOriginal:oldVersion]; - * - * @note This method returns nil if there are no changes between the original and the receiver. - * - * @see https://developers.google.com/google-apps/tasks/performance?csw=1#patch - * - * @param original The original object from which to create the patch object. - * - * @return The object used for the patch body. - */ -- (nullable id)patchObjectFromOriginal:(GTLRObject *)original; - -/** - * 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. - * - * @return The null value object. - */ -+ (id)nullValue; - -#pragma mark Internal - -/////////////////////////////////////////////////////////////////////////////// -// -// Protected methods -// -// These methods are intended for subclasses of GTLRObject -// - -// Creation of objects from a JSON dictionary. The class created depends on -// the content of the JSON, not the class messaged. -+ (nullable GTLRObject *)objectForJSON:(NSMutableDictionary *)json - defaultClass:(nullable Class)defaultClass - objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver; - -// Property-to-key mapping (for JSON keys which are not used as method names) -+ (nullable NSDictionary<NSString *, NSString *> *)propertyToJSONKeyMap; - -// property-to-Class mapping for array properties (to say what is in the array) -+ (nullable NSDictionary<NSString *, Class> *)arrayPropertyToClassMap; - -// The default class for additional JSON keys -+ (nullable Class)classForAdditionalProperties; - -// Indicates if a "kind" property on this class can be used for the class -// registry or if it appears to be non standard. -+ (BOOL)isKindValidForClassRegistry; - -@end - -/** - * Collection results have a property containing an array of @c GTLRObject - * - * This provides support for @c NSFastEnumeration and for indexed subscripting to - * access the objects in the array. - */ -@interface GTLRCollectionObject : GTLRObject<NSFastEnumeration> - -/** - * The property name that holds the collection. - * - * @return The key for the property holding the array of @c GTLRObject items. - */ -+ (NSString *)collectionItemsKey; - -// objectAtIndexedSubscript: will throw if the index is out of bounds (like -// NSArray does). -- (nullable id)objectAtIndexedSubscript:(NSUInteger)idx; - -@end - -/** - * A GTLRDataObject holds media data and the MIME type of the data returned by a media - * download query. - * - * The JSON for the object may be nil. - */ -@interface GTLRDataObject : GTLRObject - -/** - * The downloaded media data. - */ -@property(atomic, strong) NSData *data; - -/** - * The MIME type of the downloaded media data. - */ -@property(atomic, copy) NSString *contentType; - -@end - -/** - * Base class used when a service method directly returns an array instead - * of a JSON object. This exists for the methods not up to spec. - */ -@interface GTLRResultArray : GTLRCollectionObject - -/** - * This method should only be called by subclasses. - */ -- (nullable NSArray *)itemsWithItemClass:(Class)itemClass; -@end - -/** - * Helper to call the resolver and find the class to use for the given JSON. - * Intended for internal library use only. - */ -Class GTLRObjectResolveClass( - id<GTLRObjectClassResolver> objectClassResolver, - NSDictionary *json, - Class defaultClass); - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRObject.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRObject.m @@ -1,760 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#include <objc/runtime.h> - -#import "GTLRObject.h" -#import "GTLRRuntimeCommon.h" -#import "GTLRUtilities.h" - -static NSString *const kUserDataPropertyKey = @"_userData"; - -static NSString *const kGTLRObjectJSONCoderKey = @"json"; - -static NSMutableDictionary *DeepMutableCopyOfJSONDictionary(NSDictionary *initialJSON); - -@interface GTLRObject () <GTLRRuntimeCommon> - -@property(nonatomic, strong) id<GTLRObjectClassResolver>objectClassResolver; - -@end - -@implementation GTLRObject { - // 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; -} - -@synthesize JSON = _json, - objectClassResolver = _objectClassResolver, - userProperties = _userProperties; - -+ (instancetype)object { - return [[self alloc] init]; -} - -+ (instancetype)objectWithJSON:(NSDictionary *)dict { - GTLRObject *obj = [self object]; - obj->_json = DeepMutableCopyOfJSONDictionary(dict); - return obj; -} - -+ (instancetype)objectWithJSON:(nullable NSDictionary *)dict - objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver { - GTLRObject *obj = [self objectWithJSON:dict]; - obj->_objectClassResolver = objectClassResolver; - return obj; -} - -+ (NSDictionary<NSString *, NSString *> *)propertyToJSONKeyMap { - return nil; -} - -+ (NSDictionary<NSString *, Class> *)arrayPropertyToClassMap { - return nil; -} - -+ (Class)classForAdditionalProperties { - return Nil; -} - -+ (BOOL)isKindValidForClassRegistry { - return YES; -} - -- (BOOL)isEqual:(GTLRObject *)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 GTLR_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 GTLRObjects. -- (NSUInteger)hash { - return (NSUInteger) (__bridge void *) [GTLRObject class]; -} - -- (id)copyWithZone:(NSZone *)zone { - GTLRObject *newObject = [[[self class] allocWithZone:zone] init]; - newObject.JSON = DeepMutableCopyOfJSONDictionary(self.JSON); - newObject.objectClassResolver = self.objectClassResolver; - - // What we're not copying: - // userProperties - return newObject; -} - -- (NSString *)descriptionWithLocale:(id)locale { - return self.description; -} - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - self = [super init]; - if (self) { - _json = [decoder decodeObjectOfClass:[NSMutableDictionary class] - forKey:kGTLRObjectJSONCoderKey]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:_json forKey:kGTLRObjectJSONCoderKey]; -} - -#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; - NSDictionary *json = self.JSON; - if (json) { - NSData *data = [NSJSONSerialization dataWithJSONObject:json - options:NSJSONWritingPrettyPrinted - error:&error]; - GTLR_DEBUG_ASSERT(data != nil, @"JSONString generate failed: %@\n JSON: %@", error, json); - if (data) { - NSString *jsonStr = [[NSString alloc] initWithData:data - encoding:NSUTF8StringEncoding]; - if (jsonStr) return jsonStr; - } - } - return @""; -} - -- (NSArray<NSString *> *)additionalJSONKeys { - NSArray *knownKeys = [[self class] allKnownKeys]; - NSMutableArray *result; - NSArray *allKeys = _json.allKeys; - if (allKeys) { - result = [NSMutableArray arrayWithArray: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 = [GTLRObject 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 (((NSArray *)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 { - GTLR_ASSERT(0, @"GTLRObject unknown field element for %@ (%@)", - key, NSStringFromClass([value class])); - } - } - return resultFields; -} - -#pragma mark Partial - Patch - -- (id)patchObjectFromOriginal:(GTLRObject *)original { - GTLRObject *resultObj; - NSMutableDictionary *resultJSON = [GTLRObject patchDictionaryForJSON:self.JSON - fromOriginalJSON:original.JSON]; - if (resultJSON.count > 0) { - // Avoid an extra copy by assigning the JSON directly rather than using +objectWithJSON: - resultObj = [[self class] object]; - resultObj.JSON = 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 (!GTLR_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) { - id<GTLRObjectClassResolver>objectClassResolver = self.objectClassResolver; - result = [GTLRRuntimeCommon objectFromJSON:jsonObj - defaultClass:defaultClass - objectClassResolver:objectClassResolver - 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 = [GTLRRuntimeCommon jsonFromAPIObject:obj - expectedClass:defaultClass - isCacheable:&shouldCache]; - [self setJSONValue:json forKey:name]; - [self setCacheChild:(shouldCache ? obj : nil) - forKey:name]; -} - -- (NSDictionary<NSString *, id> *)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 Support methods - -+ (NSMutableArray *)allDeclaredProperties { - NSMutableArray *array = [NSMutableArray array]; - - // walk from this class up the hierarchy to GTLRObject - Class topClass = class_getSuperclass([GTLRObject 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:(id _Nonnull)@(propName)]; - } - } - free(properties); - } - } - return array; -} - -+ (NSArray *)allKnownKeys { - NSArray *allProps = [self allDeclaredProperties]; - NSMutableArray *knownKeys = [NSMutableArray arrayWithArray:allProps]; - - NSDictionary *propMap = [GTLRObject 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 { - NSString *jsonDesc = [self JSONDescription]; - - NSString *str = [NSString stringWithFormat:@"%@ %p: %@", - [self class], self, jsonDesc]; - return str; -} - -// Internal utility for creating an appropriate description summary for the object's JSON. -- (NSString *)JSONDescription { - // Find the list of declared and otherwise known JSON keys for this class. - NSArray *knownKeys = [[self class] allKnownKeys]; - - NSMutableString *descStr = [NSMutableString stringWithString:@"{"]; - - NSString *spacer = @""; - for (NSString *key in [[_json allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]) { - 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} - NSArray *subKeys = [((NSDictionary *)rawValue).allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; - NSString *subkeyList = [subKeys 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:@"[%tu]", ((NSArray *)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 = @" "; - } - [descStr appendString:@"}"]; - return descStr; -} - -#pragma mark Object Instantiation - -+ (GTLRObject *)objectForJSON:(NSMutableDictionary *)json - defaultClass:(Class)defaultClass - objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver { - if (((id)json == [NSNull null]) || json.count == 0) { - if (json != nil && defaultClass != Nil) { - // The JSON included an empty dictionary, just create the object. - Class classToCreate = - GTLRObjectResolveClass(objectClassResolver, - [NSDictionary dictionary], - defaultClass); - return [classToCreate object]; - } - // No actual result, such as the response from a delete. - return nil; - } - - if (defaultClass == Nil) { - defaultClass = self; - } - - Class classToCreate = - GTLRObjectResolveClass(objectClassResolver, json, defaultClass); - - // now instantiate the GTLRObject - GTLRObject *parsedObject = [classToCreate object]; - parsedObject.objectClassResolver = objectClassResolver; - parsedObject.JSON = json; - 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 = [[NSMutableDictionary alloc] init]; - } - if (gArrayPropertyToClassMapCache == nil) { - gArrayPropertyToClassMapCache = [[NSMutableDictionary alloc] init]; - } -} - -+ (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRRuntimeCommon>)aClass { - NSDictionary *resultMap = - [GTLRRuntimeCommon mergedClassDictionaryForSelector:@selector(propertyToJSONKeyMap) - startClass:aClass - ancestorClass:[GTLRObject class] - cache:gJSONKeyMapCache]; - return resultMap; -} - -+ (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRRuntimeCommon>)aClass { - NSDictionary *resultMap = - [GTLRRuntimeCommon mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap) - startClass:aClass - ancestorClass:[GTLRObject class] - cache:gArrayPropertyToClassMapCache]; - return resultMap; -} - -#pragma mark Runtime Support - -+ (Class<GTLRRuntimeCommon>)ancestorClass { - return [GTLRObject class]; -} - -+ (BOOL)resolveInstanceMethod:(SEL)sel { - BOOL resolved = [GTLRRuntimeCommon resolveInstanceMethod:sel onClass:self]; - if (resolved) - return YES; - - return [super resolveInstanceMethod:sel]; -} - -@end - -@implementation GTLRCollectionObject - -+ (NSString *)collectionItemsKey { - // GTLRCollectionObject fast enumeration, indexed access, and automatic pagination - // (when shouldFetchNextPages is enabled) applies to the object array property "items". - // The array property's key may be different if subclasses override this method. - return @"items"; -} - -- (id)objectAtIndexedSubscript:(NSUInteger)idx { - NSString *key = [[self class] collectionItemsKey]; - NSArray *items = [self valueForKey:key]; - if (items == nil) { - [NSException raise:NSRangeException - format:@"index %tu beyond bounds (%@ property \"%@\" is nil)", - idx, [self class], key]; - } - id result = [items objectAtIndexedSubscript:idx]; - return result; -} - -// NSFastEnumeration protocol -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state - objects:(__unsafe_unretained id _Nonnull *)stackbuf - count:(NSUInteger)len { - NSString *key = [[self class] collectionItemsKey]; - NSArray *items = [self valueForKey:key]; - NSUInteger result = [items countByEnumeratingWithState:state - objects:stackbuf - count:len]; - return result; -} - -@end - -@implementation GTLRDataObject - -@synthesize data = _data, - contentType = _contentType; - -- (NSString *)description { - NSString *jsonDesc = @""; - if (self.JSON.count > 0) { - jsonDesc = [self JSONDescription]; - } - return [NSString stringWithFormat:@"%@ %p: %tu bytes, contentType:%@ %@", - [self class], self, self.data.length, self.contentType, jsonDesc]; -} - -- (id)copyWithZone:(NSZone *)zone { - GTLRDataObject *newObj = [super copyWithZone:zone]; - newObj.data = [self.data copy]; - newObj.contentType = self.contentType; - return newObj; -} - -@end - -@implementation GTLRResultArray - -- (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]]) { - id<GTLRObjectClassResolver>objectClassResolver = self.objectClassResolver; - result = [GTLRRuntimeCommon objectFromJSON:array - defaultClass:itemClass - objectClassResolver:objectClassResolver - isCacheable:NULL]; - } else { -#if DEBUG - if (![array isKindOfClass:[NSNull class]]) { - GTLR_DEBUG_LOG(@"GTLRObject: 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; -} - -- (NSString *)JSONDescription { - // Just like GTLRObject's handing of arrays, just return the count. - return [NSString stringWithFormat:@"[%tu]", self.JSON.count]; -} - -@end - -Class GTLRObjectResolveClass( - id<GTLRObjectClassResolver>objectClassResolver, - NSDictionary *json, - Class defaultClass) { - Class result = [objectClassResolver classForJSON:json - defaultClass:defaultClass]; - if (result == Nil) { - result = defaultClass; - } - return result; -} - -@implementation GTLRObjectClassResolver { - NSDictionary<NSString *, Class> *_kindToClassMap; - NSDictionary<Class, Class> *_surrogates; -} - -+ (instancetype)resolverWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap { - GTLRObjectClassResolver *result = [[self alloc] initWithKindMap:kindStringToClassMap - surrogates:nil]; - return result; -} - -+ (instancetype)resolverWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap - surrogates:(NSDictionary<Class, Class> *)surrogates { - GTLRObjectClassResolver *result = [[self alloc] initWithKindMap:kindStringToClassMap - surrogates:surrogates]; - return result; -} - -- (instancetype)initWithKindMap:(NSDictionary<NSString *, Class> *)kindStringToClassMap - surrogates:(NSDictionary<Class, Class> *)surrogates { - self = [super init]; - if (self) { - _kindToClassMap = [kindStringToClassMap copy]; - _surrogates = [surrogates copy]; - } - return self; -} - -- (Class)classForJSON:(NSDictionary *)json - defaultClass:(Class)defaultClass { - Class result = defaultClass; - - // Apply kind map. - BOOL shouldUseKind = (result == Nil) || [result isKindValidForClassRegistry]; - if (shouldUseKind && [json isKindOfClass:[NSDictionary class]]) { - NSString *kind = [json valueForKey:@"kind"]; - if ([kind isKindOfClass:[NSString class]] && kind.length > 0) { - Class dynamicClass = [_kindToClassMap objectForKey:kind]; - if (dynamicClass) { - result = dynamicClass; - } - } - } - - // Apply surrogate map. - Class surrogate = [_surrogates objectForKey:result]; - if (surrogate) { - result = surrogate; - } - - return result; -} - -@end - -static NSMutableDictionary *DeepMutableCopyOfJSONDictionary(NSDictionary *initialJSON) { - if (!initialJSON) return nil; - - NSMutableDictionary *result; - CFPropertyListRef ref = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, - (__bridge CFPropertyListRef)(initialJSON), - kCFPropertyListMutableContainers); - if (ref) { - result = CFBridgingRelease(ref); - } else { - // Failed to copy, probably due to a non-plist type such as NSNull. - // - // As a fallback, round-trip through NSJSONSerialization. - NSError *serializationError; - NSData *data = [NSJSONSerialization dataWithJSONObject:initialJSON - options:0 - error:&serializationError]; - if (!data) { - GTLR_DEBUG_ASSERT(0, @"Copy failed due to serialization: %@\nJSON: %@", - serializationError, initialJSON); - } else { - result = [NSJSONSerialization JSONObjectWithData:data - options:NSJSONReadingMutableContainers - error:&serializationError]; - GTLR_DEBUG_ASSERT(result != nil, @"Copy failed due to deserialization: %@\nJSON: %@", - serializationError, - [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); - } - } - return result; -} diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRQuery.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRQuery.h @@ -1,253 +0,0 @@ -/* 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. - */ - -// Query documentation: -// https://github.com/google/google-api-objectivec-client-for-rest/wiki#query-operations - -#import "GTLRObject.h" -#import "GTLRUploadParameters.h" - -NS_ASSUME_NONNULL_BEGIN - -@class GTLRServiceTicket; -@class GTLRServiceExecutionParameters; -@class GTLRQuery; - -/** - * This protocol is just to support passing of either a batch or a single query - * to a GTLRService instance. The library does not expect or support client app - * implementations of this protocol. - */ -@protocol GTLRQueryProtocol <NSObject, NSCopying> - -/** - * Service ticket values may be set in the execution parameters for an individual query - * prior to executing the query. - */ -@property(atomic, strong, null_resettable) GTLRServiceExecutionParameters *executionParameters; - -- (BOOL)isBatchQuery; -- (BOOL)hasExecutionParameters; -- (BOOL)shouldSkipAuthorization; -- (void)invalidateQuery; -- (nullable NSDictionary<NSString *, NSString *> *)additionalHTTPHeaders; -- (nullable NSDictionary<NSString *, NSString *> *)additionalURLQueryParameters; -- (nullable NSString *)loggingName; -- (nullable GTLRUploadParameters *)uploadParameters; - -@end - -@protocol GTLRQueryCollectionProtocol -@optional -@property(nonatomic, strong) NSString *pageToken; -@end - -/** - * A block called when a query completes executing. - * - * Errors passed to the completionBlock will have an "underlying" GTLRErrorObject - * when the server returned an error for this specific query: - * - * GTLRErrorObject *errorObj = [GTLRErrorObject underlyingObjectForError:callbackError]; - * if (errorObj) { - * // The server returned this error for this specific query. - * } else { - * // The query execution fetch failed. - * } - * - * @param callbackTicket The ticket that tracked query execution. - * @param object The result of query execution. This will be derived from - * GTLRObject. - * @param callbackError If non-nil, the query execution failed. - */ -typedef void (^GTLRQueryCompletionBlock)(GTLRServiceTicket *callbackTicket, - id _Nullable object, - NSError * _Nullable callbackError); - -/** - * Class for a single query. - */ -@interface GTLRQuery : NSObject <GTLRQueryProtocol, NSCopying> - -/** - * The object to be uploaded with the query. The JSON of this object becomes - * the body for PUT and POST requests. - */ -@property(atomic, strong, nullable) GTLRObject *bodyObject; - -/** - * Each query must have a request ID string. The client app 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(atomic, copy) NSString *requestID; - -/** - * For queries which support file upload, the MIME type and file URL - * or data must be provided. - */ -@property(atomic, copy, nullable) GTLRUploadParameters *uploadParameters; - -/** - * Any additional URL query parameters for this query. - * - * These query parameters override the same keys from the service object's - * additionalURLQueryParameters - */ -@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalURLQueryParameters; - -/** - * Any additional HTTP headers for this query. - * - * These headers override the same keys from the service object's additionalHTTPHeaders - */ -@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalHTTPHeaders; - -/** - * If set, when the query is executed, an @c "alt" query parameter is added - * with this value and the raw result of the query is returned in a - * GTLRDataObject. This is useful when the server documents result datatypes - * other than JSON ("csv", for example). - */ -@property(atomic, copy) NSString *downloadAsDataObjectType; - -/** - * If set, and the query also has a non-empty @c downloadAsDataObjectType, the - * URL to download from will be modified to include "download/". This extra path - * component avoids the need for a server redirect to the download URL. - */ -@property(atomic, assign) BOOL useMediaDownloadService; - -/** - * Clients may set this to YES to disallow authorization. Defaults to NO. - */ -@property(atomic, assign) BOOL shouldSkipAuthorization; - -/** - * An optional callback block to be called immediately before the executeQuery: completion handler. - * - * The completionBlock property is particularly useful for queries executed in a batch. - */ -@property(atomic, copy, nullable) GTLRQueryCompletionBlock completionBlock; - -/** - * The brief string to identify this query in GTMSessionFetcher http logs. - * - * A default logging name is set by the code generator, but may be overridden by the client app. - */ -@property(atomic, copy, nullable) NSString *loggingName; - -#pragma mark Internal -///////////////////////////////////////////////////////////////////////////////////////////// -// -// Properties below are used by the library and aren't typically needed by client apps. -// -///////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The URITemplate path segment. This is initialized in by the service generator. - */ -@property(atomic, readonly) NSString *pathURITemplate; - -/** - * The HTTP method to use for this query. This is initialized in by the service generator. - */ -@property(atomic, readonly, nullable) NSString *httpMethod; - -/** - * The parameters names that are in the URI Template. - * This is initialized in by the service generator. - * - * The service generator collects these via the discovery info instead of having to parse the - * template to figure out what is part of the path. - */ -@property(atomic, readonly, nullable) NSArray<NSString *> *pathParameterNames; - -/** - * The JSON dictionary of all the parameters set on this query. - * - * The JSON values are set by setting the query's properties. - */ -@property(nonatomic, strong, nullable) NSMutableDictionary<NSString *, id> *JSON; - -/** - * A custom URI template for resumable uploads. This is initialized by the service generator - * if needed. - */ -@property(atomic, copy, nullable) NSString *resumableUploadPathURITemplateOverride; - -/** - * A custom URI template for simple and multipart media uploads. This is initialized - * by the service generator. - */ -@property(atomic, copy, nullable) NSString *simpleUploadPathURITemplateOverride; - -/** - * The GTLRObject subclass expected for results. This is initialized by the service generator. - * - * This is needed if the object returned by the server lacks a known "kind" string. - */ -@property(atomic, assign, nullable) Class expectedObjectClass; - -/** - * Set when the query has been invalidated, meaning it was slated for execution so it's been copied - * and its callbacks were released, or it's a copy that has finished executing. - * - * Once a query has been invalidated, it cannot be executed, added to a batch, or copied. - */ -@property(atomic, assign, getter=isQueryInvalid) BOOL queryInvalid; - -/** - * Internal query init method. - * - * @param pathURITemplate URI template to be filled in with parameters. - * @param httpMethod The requests's http method. A nil method will execute as GET. - * @param pathParameterNames Names of parameters to be replaced in the template. - */ -- (instancetype)initWithPathURITemplate:(NSString *)pathURITemplate - HTTPMethod:(nullable NSString *)httpMethod - pathParameterNames:(nullable NSArray<NSString *> *)pathParameterNames NS_DESIGNATED_INITIALIZER; - -/** - * @return Auto-generated request ID string. - */ -+ (NSString *)nextRequestID; - -/** - * Overridden by subclasses. - * - * @return Substitute parameter names where needed for Objective-C or library compatibility. - */ -+ (nullable NSDictionary<NSString *, NSString *> *)parameterNameMap; - -/** - * Overridden by subclasses. - * - * @return Map of property keys to specifying the class of objects to be instantiated in arrays. - */ -+ (nullable NSDictionary<NSString *, Class> *)arrayPropertyToClassMap; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -/** - * The library doesn't use GTLRQueryCollectionImpl, but it provides a concrete implementation - * of the protocol so the methods do not cause private method errors in Xcode/AppStore review. - */ -@interface GTLRQueryCollectionImpl : GTLRQuery <GTLRQueryCollectionProtocol> -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRQuery.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRQuery.m @@ -1,313 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#include <objc/runtime.h> - -#import "GTLRQuery.h" -#import "GTLRRuntimeCommon.h" -#import "GTLRService.h" -#import "GTLRUtilities.h" - -@interface GTLRQuery () <GTLRRuntimeCommon> -@end - -@implementation GTLRQuery { - NSMutableDictionary *_childCache; - GTLRServiceExecutionParameters *_executionParameters; -} - -@synthesize additionalURLQueryParameters = _additionalURLQueryParameters, - additionalHTTPHeaders = _additionalHTTPHeaders, - bodyObject = _bodyObject, - completionBlock = _completionBlock, - downloadAsDataObjectType = _downloadAsDataObjectType, - expectedObjectClass = _expectedObjectClass, - httpMethod = _httpMethod, - JSON = _json, - loggingName = _loggingName, - pathParameterNames = _pathParameterNames, - pathURITemplate = _pathURITemplate, - queryInvalid = _queryInvalid, - requestID = _requestID, - resumableUploadPathURITemplateOverride = _resumableUploadPathURITemplateOverride, - shouldSkipAuthorization = _shouldSkipAuthorization, - simpleUploadPathURITemplateOverride = _simpleUploadPathURITemplateOverride, - uploadParameters = _uploadParameters, - useMediaDownloadService = _useMediaDownloadService; - -#if DEBUG -- (instancetype)init { - [self doesNotRecognizeSelector:_cmd]; - self = nil; - return self; -} -#endif - -- (instancetype)initWithPathURITemplate:(NSString *)pathURITemplate - HTTPMethod:(nullable NSString *)httpMethod - pathParameterNames:(nullable NSArray<NSString *> *)pathParameterNames { - self = [super init]; - if (self) { - _requestID = [[self class] nextRequestID]; - - _pathURITemplate = [pathURITemplate copy]; - _httpMethod = [httpMethod copy]; - _pathParameterNames = [pathParameterNames copy]; - - if (_pathURITemplate.length == 0) { - self = nil; - } - } - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - GTLR_DEBUG_ASSERT(!self.queryInvalid, @"Cannot copy an executed query: %@", self); - - GTLRQuery *query = - [[[self class] allocWithZone:zone] initWithPathURITemplate:self.pathURITemplate - HTTPMethod:self.httpMethod - pathParameterNames:self.pathParameterNames]; - - if (_json.count > 0) { - // Deep copy the parameters - CFPropertyListRef ref = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, - (__bridge CFPropertyListRef)(_json), - kCFPropertyListMutableContainers); - query.JSON = CFBridgingRelease(ref); - } - - // Using the executionParameters ivar avoids creating the object. - query.executionParameters = self.executionParameters; - - // Copied in the same order as synthesized above. - query.additionalHTTPHeaders = self.additionalHTTPHeaders; - query.additionalURLQueryParameters = self.additionalURLQueryParameters; - query.bodyObject = self.bodyObject; - query.completionBlock = self.completionBlock; - query.downloadAsDataObjectType = self.downloadAsDataObjectType; - query.expectedObjectClass = self.expectedObjectClass; - // http method passed to init above. - // JSON copied above. - query.loggingName = self.loggingName; - // pathParameterNames passed to init above. - // pathURITemplate passed to init above. - query.queryInvalid = self.queryInvalid; - query.requestID = self.requestID; - query.resumableUploadPathURITemplateOverride = self.resumableUploadPathURITemplateOverride; - query.shouldSkipAuthorization = self.shouldSkipAuthorization; - query.simpleUploadPathURITemplateOverride = self.simpleUploadPathURITemplateOverride; - query.uploadParameters = self.uploadParameters; - query.useMediaDownloadService = self.useMediaDownloadService; - - return query; -} - -#if DEBUG -- (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:@","]]; - } - - NSString *invalidStr = @""; - if (self.queryInvalid) { - invalidStr = @" [callbacks released]"; - } - - keys = self.additionalURLQueryParameters.allKeys; - NSArray *urlQParams = [keys sortedArrayUsingSelector:@selector(compare:)]; - NSString *urlQParamsSummary = @""; - if (urlQParams.count > 0) { - urlQParamsSummary = [NSString stringWithFormat:@" urlQParams:(%@)", - [urlQParams componentsJoinedByString:@","]]; - } - - GTLRObject *bodyObj = self.bodyObject; - NSString *bodyObjSummary = @""; - if (bodyObj != nil) { - bodyObjSummary = [NSString stringWithFormat:@" bodyObject:%@", [bodyObj class]]; - } - - NSString *uploadStr = @""; - GTLRUploadParameters *uploadParams = self.uploadParameters; - if (uploadParams) { - uploadStr = [NSString stringWithFormat:@" %@", uploadParams]; - } - - NSString *httpMethod = self.httpMethod; - if (httpMethod == nil) { - httpMethod = @"GET"; - } - - NSString *dataObjectType = self.downloadAsDataObjectType; - NSString *downloadStr = @""; - if (dataObjectType.length > 0) { - downloadStr = - [NSString stringWithFormat:@" downloadDataAs:%@", dataObjectType]; - } - - return [NSString stringWithFormat:@"%@ %p:%@%@ {%@ pathTemplate:%@%@%@%@%@}", - [self class], self, invalidStr, downloadStr, - httpMethod, self.pathURITemplate, - paramsSummary, urlQParamsSummary, bodyObjSummary, uploadStr]; -} -#endif // DEBUG - -- (BOOL)isBatchQuery { - return NO; -} - -- (void)invalidateQuery { - self.queryInvalid = YES; - self.completionBlock = nil; - self.executionParameters = nil; -} - -- (GTLRServiceExecutionParameters *)executionParameters { - @synchronized(self) { - if (!_executionParameters) { - _executionParameters = [[GTLRServiceExecutionParameters alloc] init]; - } - return _executionParameters; - } -} - -- (void)setExecutionParameters:(nullable GTLRServiceExecutionParameters *)executionParameters { - @synchronized(self) { - _executionParameters = executionParameters; - } -} - -- (BOOL)hasExecutionParameters { - return self.executionParameters.hasParameters; -} - -+ (NSString *)nextRequestID { - static NSUInteger lastRequestID = 0; - NSString *result; - - @synchronized([GTLRQuery class]) { - ++lastRequestID; - result = [NSString stringWithFormat:@"gtlr_%tu", lastRequestID]; - } - return result; -} - -#pragma mark GTLRRuntimeCommon 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, since 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<NSString *, NSString *> *)parameterNameMap { - return nil; -} - -+ (NSDictionary<NSString *, Class> *)arrayPropertyToClassMap { - return nil; -} - -#pragma mark Runtime Utilities - -static NSMutableDictionary *gQueryParameterNameMapCache = nil; -static NSMutableDictionary *gQueryArrayPropertyToClassMapCache = nil; - -+ (void)initialize { - // Note that +initialize is guaranteed by the runtime to be called in a thread-safe manner. - if (gQueryParameterNameMapCache == nil) { - gQueryParameterNameMapCache = [[NSMutableDictionary alloc] init]; - } - if (gQueryArrayPropertyToClassMapCache == nil) { - gQueryArrayPropertyToClassMapCache = [[NSMutableDictionary alloc] init]; - } -} - -+ (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRRuntimeCommon>)aClass { - NSDictionary *resultMap = - [GTLRRuntimeCommon mergedClassDictionaryForSelector:@selector(parameterNameMap) - startClass:aClass - ancestorClass:[GTLRQuery class] - cache:gQueryParameterNameMapCache]; - return resultMap; -} - -+ (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRRuntimeCommon>)aClass { - NSDictionary *resultMap = - [GTLRRuntimeCommon mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap) - startClass:aClass - ancestorClass:[GTLRQuery class] - cache:gQueryArrayPropertyToClassMapCache]; - return resultMap; -} - -#pragma mark Runtime Support - -- (id<GTLRObjectClassResolver>)objectClassResolver { - // Stub method just needed for RuntimeCommon. - return nil; -} - -+ (Class<GTLRRuntimeCommon>)ancestorClass { - return [GTLRQuery class]; -} - -+ (BOOL)resolveInstanceMethod:(SEL)sel { - BOOL resolved = [GTLRRuntimeCommon resolveInstanceMethod:sel onClass:self]; - if (resolved) return YES; - - return [super resolveInstanceMethod:sel]; -} - -@end - -@implementation GTLRQueryCollectionImpl -@dynamic pageToken; -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.h @@ -1,73 +0,0 @@ -/* Copyright (c) 2011 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import <Foundation/Foundation.h> - -#import "GTLRDefines.h" - -@protocol GTLRObjectClassResolver; - -NS_ASSUME_NONNULL_BEGIN - -// This protocol and support class are an internal implementation detail so -// GTLRObject and GTLRQuery can share some code. - -/** - * An internal protocol for the GTLR library. - * - * None of these methods should be used by client apps. - */ -@protocol GTLRRuntimeCommon <NSObject> -@required -// Get/Set properties -- (void)setJSONValue:(nullable id)obj forKey:(NSString *)key; -- (id)JSONValueForKey:(NSString *)key; -// Child cache -- (void)setCacheChild:(nullable id)obj forKey:(NSString *)key; -- (nullable id)cacheChildForKey:(NSString *)key; -// Object mapper. -- (nullable id<GTLRObjectClassResolver>)objectClassResolver; -// Key map -+ (nullable NSDictionary<NSString *, NSString *> *)propertyToJSONKeyMapForClass:(Class<GTLRRuntimeCommon>)aClass; -// Array item types -+ (nullable NSDictionary<NSString *, Class> *)arrayPropertyToClassMapForClass:(Class<GTLRRuntimeCommon>)aClass; -// The parent class for dynamic support -+ (nullable Class<GTLRRuntimeCommon>)ancestorClass; -@end - -/** - * An internal class for the GTLR library. - * - * None of these methods should be used by client apps. - */ -@interface GTLRRuntimeCommon : NSObject -// Wire things up. -+ (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class)onClass; -// Helpers -+ (nullable id)objectFromJSON:(id)json - defaultClass:(nullable Class)defaultClass - objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver - isCacheable:(nullable BOOL *)isCacheable; -+ (nullable id)jsonFromAPIObject:(id)obj - expectedClass:(nullable Class)expectedClass - isCacheable:(nullable BOOL *)isCacheable; -// Walk up the class tree merging dictionaries and return the result. -+ (NSDictionary *)mergedClassDictionaryForSelector:(SEL)selector - startClass:(Class)startClass - ancestorClass:(Class)ancestorClass - cache:(NSMutableDictionary *)cache; -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRRuntimeCommon.m @@ -1,1060 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#include <objc/runtime.h> -#include <TargetConditionals.h> - -#import "GTLRRuntimeCommon.h" - -#import "GTLRDateTime.h" -#import "GTLRDuration.h" -#import "GTLRObject.h" -#import "GTLRUtilities.h" - -// 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 GTLRRuntimeCommon - -// Helper to generically convert JSON to an api object type. -+ (id)objectFromJSON:(id)json - defaultClass:(Class)defaultClass - objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver - 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 = [GTLRObject class]; - } - result = [GTLRObject objectForJSON:json - defaultClass:defaultClass - objectClassResolver:objectClassResolver]; - } 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 - objectClassResolver:objectClassResolver - isCacheable:NULL]; - [resultArray addObject:item]; - } - result = resultArray; - } else if ([json isKindOfClass:[NSString class]]) { - // DateTimes and Durations live in JSON as strings, so convert. - if ([defaultClass isEqual:[GTLRDateTime class]]) { - result = [GTLRDateTime dateTimeWithRFC3339String:json]; - } else if ([defaultClass isEqual:[GTLRDuration class]]) { - result = [GTLRDuration durationWithJSONString:json]; - } else if ([defaultClass isEqual:[NSNumber class]]) { - result = GTLR_EnsureNSNumber(json); - canBeCached = NO; - } else { - result = json; - canBeCached = NO; - } - } else if ([json isKindOfClass:[NSNumber class]] || - [json isKindOfClass:[NSNull class]]) { - result = json; - canBeCached = NO; - } else { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: 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]; - canBeCached = NO; - } else if ([obj isKindOfClass:[NSNumber class]] || - [obj isKindOfClass:[NSNull class]]) { - result = obj; - canBeCached = NO; - } else if ([obj isKindOfClass:[GTLRObject class]]) { - result = [(GTLRObject *)obj JSON]; - if (result == nil) { - // adding an empty object; it should have a JSON dictionary so it can - // hold future assignments - [(GTLRObject *)obj setJSON:[NSMutableDictionary dictionary]]; - result = [(GTLRObject *)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:[GTLRDateTime class]]) { - // DateTimes live in JSON as strings, so convert. - GTLRDateTime *dateTime = obj; - result = dateTime.RFC3339String; - } else if ([obj isKindOfClass:[GTLRDuration class]]) { - // Durations live in JSON as strings, so convert. - GTLRDuration *duration = obj; - result = duration.jsonString; - } else { - checkExpected = NO; - if (obj) { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: 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]) { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: jsonFromAPIObject expected class '%s' instead got '%s'", - class_getName(expectedClass), class_getName([obj class])); - } - } - - if (isCacheable) { - *isCacheable = canBeCached; - } - return result; -} - -+ (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. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - NSDictionary *classDict = [startClass performSelector:selector]; -#pragma clang diagnostic pop - - // Collect the parent class's merged dictionary. - NSDictionary *parentClassMergedDict; - if ([startClass isEqual:ancestorClass]) { - parentClassMergedDict = nil; - } else { - Class parentClass = class_getSuperclass(startClass); - parentClassMergedDict = - [self mergedClassDictionaryForSelector:selector - startClass:parentClass - ancestorClass:ancestorClass - cache:cache]; - } - - // Merge this class's into the parent's so things properly override. - NSMutableDictionary *mergeDict; - if (parentClassMergedDict != nil) { - mergeDict = - [NSMutableDictionary dictionaryWithDictionary:parentClassMergedDict]; - } else { - mergeDict = [NSMutableDictionary dictionary]; - } - if (classDict != nil) { - [mergeDict addEntriesFromDictionary:classDict]; - } - - // Make an immutable version. - result = [NSDictionary dictionaryWithDictionary:mergeDict]; - - // Save it. - [cache setObject:result forKey:(id<NSCopying>)startClass]; - } - } - return result; -} - -#pragma mark Runtime lookup support - -static objc_property_t PropertyForSel(Class<GTLRRuntimeCommon> startClass, - SEL sel, BOOL isSetter, - Class<GTLRRuntimeCommon> *outFoundClass) { - const char *selName = sel_getName(sel); - const char *baseName = selName; - 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<GTLRRuntimeCommon> 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 *propAttrs = property_getAttributes(*prop); - const char *dynamicMarker = strstr(propAttrs, ",D"); - if (!dynamicMarker || - (dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) { - // It isn't dynamic, skip it. - continue; - } - - if (!isSetter) { - // See if this property has an explicit getter=. (the attributes always start with a T, - // so we can check for the leading ','. - const char *getterMarker = strstr(propAttrs, ",G"); - if (getterMarker) { - const char *getterStart = getterMarker + 2; - const char *getterEnd = getterStart; - while ((*getterEnd != 0) && (*getterEnd != ',')) { - ++getterEnd; - } - size_t getterLen = (size_t)(getterEnd - getterStart); - if ((strncmp(selName, getterStart, getterLen) == 0) - && (selName[getterLen] == 0)) { - // return the actual property - foundProp = *prop; - // if requested, return the class containing the property - if (outFoundClass) *outFoundClass = currClass; - break; - } - } // if (getterMarker) - } // if (!isSetter) - - // Search for an exact-name match (a getter), but case-insensitive on the - // first character (in case baseName comes from a setter) - const char *propName = property_getName(*prop); - size_t propNameLen = strlen(propName); - if (baseNameLen == propNameLen - && strncasecmp(baseName, propName, 1) == 0 - && (baseNameLen <= 1 - || strncmp(baseName + 1, propName + 1, baseNameLen - 1) == 0)) { - // return the actual property - foundProp = *prop; - - // if requested, return the class containing the property - if (outFoundClass) *outFoundClass = currClass; - break; - } - } // for (prop in properties) - 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 NS_ENUM(NSUInteger, GTLRPropertyType) { -#if !defined(__LP64__) || !__LP64__ - // These two only needed in 32bit builds since NSInteger in 64bit ends up in the LongLong paths. - GTLRPropertyTypeInt32 = 1, - GTLRPropertyTypeUInt32, -#endif - GTLRPropertyTypeLongLong = 3, - GTLRPropertyTypeULongLong, - GTLRPropertyTypeFloat, - GTLRPropertyTypeDouble, - GTLRPropertyTypeBool, - GTLRPropertyTypeNSString, - GTLRPropertyTypeNSNumber, - GTLRPropertyTypeGTLRDateTime, - GTLRPropertyTypeGTLRDuration, - GTLRPropertyTypeNSArray, - GTLRPropertyTypeNSObject, - GTLRPropertyTypeGTLRObject, -}; - -typedef struct { - const char *attributePrefix; - - GTLRPropertyType propertyType; - const char *setterEncoding; - const char *getterEncoding; - - // These are the "fixed" return classes, but some properties will require - // looking up the return class instead (because it is a subclass of - // GTLRObject). - const char *returnClassName; - Class returnClass; - BOOL extractReturnClass; - -} GTLRDynamicImpInfo; - -static const GTLRDynamicImpInfo *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 - // TB,D -- BOOL comes as bool on 64bit iOS - // Tc,D -- BOOL comes as char otherwise - // T@"NSString",D - // T@"GTLRLink",D - // T@"NSArray",D - - - static GTLRDynamicImpInfo kImplInfo[] = { -#if !defined(__LP64__) || !__LP64__ - { // NSInteger on 32bit - "Ti", - GTLRPropertyTypeInt32, - "v@:i", - "i@:", - nil, nil, - NO - }, - { // NSUInteger on 32bit - "TI", - GTLRPropertyTypeUInt32, - "v@:I", - "I@:", - nil, nil, - NO - }, -#endif - { // NSInteger on 64bit, long long on 32bit and 64bit. - "Tq", - GTLRPropertyTypeLongLong, - "v@:q", - "q@:", - nil, nil, - NO - }, - { // NSUInteger on 64bit, long long on 32bit and 64bit. - "TQ", - GTLRPropertyTypeULongLong, - "v@:Q", - "Q@:", - nil, nil, - NO - }, - { // float - "Tf", - GTLRPropertyTypeFloat, - "v@:f", - "f@:", - nil, nil, - NO - }, - { // double - "Td", - GTLRPropertyTypeDouble, - "v@:d", - "d@:", - nil, nil, - NO - }, -#if defined(OBJC_BOOL_IS_BOOL) && OBJC_BOOL_IS_BOOL - { // BOOL as bool - "TB", - GTLRPropertyTypeBool, - "v@:B", - "B@:", - nil, nil, - NO - }, -#elif defined(OBJC_BOOL_IS_CHAR) && OBJC_BOOL_IS_CHAR - { // BOOL as char - "Tc", - GTLRPropertyTypeBool, - "v@:c", - "c@:", - nil, nil, - NO - }, -#else - #error unknown definition for ObjC BOOL type -#endif - { // NSString - "T@\"NSString\"", - GTLRPropertyTypeNSString, - "v@:@", - "@@:", - "NSString", nil, - NO - }, - { // NSNumber - "T@\"NSNumber\"", - GTLRPropertyTypeNSNumber, - "v@:@", - "@@:", - "NSNumber", nil, - NO - }, - { // GTLRDateTime - "T@\"" GTLR_CLASSNAME_CSTR(GTLRDateTime) "\"", - GTLRPropertyTypeGTLRDateTime, - "v@:@", - "@@:", - GTLR_CLASSNAME_CSTR(GTLRDateTime), nil, - NO - }, - { // GTLRDuration - "T@\"" GTLR_CLASSNAME_CSTR(GTLRDuration) "\"", - GTLRPropertyTypeGTLRDuration, - "v@:@", - "@@:", - GTLR_CLASSNAME_CSTR(GTLRDuration), nil, - NO - }, - { // NSArray with type - "T@\"NSArray\"", - GTLRPropertyTypeNSArray, - "v@:@", - "@@:", - "NSArray", nil, - NO - }, - { // id (any of the objects above) - "T@,", - GTLRPropertyTypeNSObject, - "v@:@", - "@@:", - "NSObject", nil, - NO - }, - { // GTLRObject - Last, cause it's a special case and prefix is general - "T@\"", - GTLRPropertyTypeGTLRObject, - "v@:@", - "@@:", - 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, - @"GTLRRuntimeCommon: 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] != ',' )) { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: property %s isn't dynamic, attributes %s", - property_getName(prop), attr ? attr : "(nil)"); - return NULL; - } - - const GTLRDynamicImpInfo *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) { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: 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) { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: did not find class with name \"%s\" " - @"for property \"%s\" with attributes \"%s\"", - classNameStart, property_getName(prop), attr); - } - } else { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: Failed to find end of class name for " - @"property \"%s\" with attributes \"%s\"", - property_getName(prop), attr); - } - free(attrCopy); - } - - return result; -} - -// Helper to get the IMP for wiring up the getters. -// NOTE: Every argument passed in should be safe to capture in a block. Avoid -// passing something like selName instead of sel, because nothing says that -// pointer will be valid when it is finally used when the method IMP is invoked -// some time later. -static IMP GTLRRuntimeGetterIMP(SEL sel, - GTLRPropertyType propertyType, - NSString *jsonKey, - Class containedClass, - Class returnClass) { - // Only used in DEBUG logging. -#pragma unused(sel) - - IMP resultIMP; - switch (propertyType) { - -#if !defined(__LP64__) || !__LP64__ - case GTLRPropertyTypeInt32: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - num = GTLR_EnsureNSNumber(num); - NSInteger result = num.integerValue; - return result; - }); - break; - } - - case GTLRPropertyTypeUInt32: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - num = GTLR_EnsureNSNumber(num); - NSUInteger result = num.unsignedIntegerValue; - return result; - }); - break; - } -#endif // __LP64__ - - case GTLRPropertyTypeLongLong: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - num = GTLR_EnsureNSNumber(num); - long long result = num.longLongValue; - return result; - }); - break; - } - - case GTLRPropertyTypeULongLong: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - num = GTLR_EnsureNSNumber(num); - unsigned long long result = num.unsignedLongLongValue; - return result; - }); - break; - } - - case GTLRPropertyTypeFloat: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - num = GTLR_EnsureNSNumber(num); - float result = num.floatValue; - return result; - }); - break; - } - - case GTLRPropertyTypeDouble: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - num = GTLR_EnsureNSNumber(num); - double result = num.doubleValue; - return result; - }); - break; - } - - case GTLRPropertyTypeBool: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - BOOL flag = num.boolValue; - return flag; - }); - break; - } - - case GTLRPropertyTypeNSString: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSString *str = [obj JSONValueForKey:jsonKey]; - return str; - }); - break; - } - - case GTLRPropertyTypeGTLRDateTime: { - resultIMP = imp_implementationWithBlock(^GTLRDateTime *(GTLRObject<GTLRRuntimeCommon> *obj) { - // Return the cached object before creating on demand. - GTLRDateTime *cachedDateTime = [obj cacheChildForKey:jsonKey]; - if (cachedDateTime != nil) { - return cachedDateTime; - } - NSString *str = [obj JSONValueForKey:jsonKey]; - id cacheValue, resultValue; - if (![str isKindOfClass:[NSNull class]]) { - GTLRDateTime *dateTime = [GTLRDateTime dateTimeWithRFC3339String:str]; - - cacheValue = dateTime; - resultValue = dateTime; - } else { - cacheValue = nil; - resultValue = [NSNull null]; - } - [obj setCacheChild:cacheValue forKey:jsonKey]; - return resultValue; - }); - break; - } - - case GTLRPropertyTypeGTLRDuration: { - resultIMP = imp_implementationWithBlock(^GTLRDuration *(GTLRObject<GTLRRuntimeCommon> *obj) { - // Return the cached object before creating on demand. - GTLRDuration *cachedDuration = [obj cacheChildForKey:jsonKey]; - if (cachedDuration != nil) { - return cachedDuration; - } - NSString *str = [obj JSONValueForKey:jsonKey]; - id cacheValue, resultValue; - if (![str isKindOfClass:[NSNull class]]) { - GTLRDuration *duration = [GTLRDuration durationWithJSONString:str]; - - cacheValue = duration; - resultValue = duration; - } else { - cacheValue = nil; - resultValue = [NSNull null]; - } - [obj setCacheChild:cacheValue forKey:jsonKey]; - return resultValue; - }); - break; - } - - case GTLRPropertyTypeNSNumber: { - resultIMP = imp_implementationWithBlock(^(id obj) { - NSNumber *num = [obj JSONValueForKey:jsonKey]; - num = GTLR_EnsureNSNumber(num); - return num; - }); - break; - } - - case GTLRPropertyTypeGTLRObject: { - // Default return class to GTLRObject if it wasn't found. - if (returnClass == Nil) { - returnClass = [GTLRObject class]; - } - resultIMP = imp_implementationWithBlock(^GTLRObject *(GTLRObject<GTLRRuntimeCommon> *obj) { - // Return the cached object before creating on demand. - GTLRObject *cachedObj = [obj cacheChildForKey:jsonKey]; - if (cachedObj != nil) { - return cachedObj; - } - NSMutableDictionary *dict = [obj JSONValueForKey:jsonKey]; - if ([dict isKindOfClass:[NSMutableDictionary class]]) { - id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver]; - GTLRObject *subObj = [GTLRObject objectForJSON:dict - defaultClass:returnClass - objectClassResolver:objectClassResolver]; - [obj setCacheChild:subObj forKey:jsonKey]; - return subObj; - } else if ([dict isKindOfClass:[NSNull class]]) { - [obj setCacheChild:nil forKey:jsonKey]; - return (GTLRObject*)[NSNull null]; - } else if (dict != nil) { - // unexpected; probably got a string -- let the caller figure it out - GTLR_DEBUG_LOG(@"GTLRObject: unexpected JSON: %@.%@ should be a dictionary, actually is a %@:\n%@", - NSStringFromClass([obj class]), - NSStringFromSelector(sel), - NSStringFromClass([dict class]), dict); - return (GTLRObject *)dict; - } - return nil; - }); - break; - } - - case GTLRPropertyTypeNSArray: { - resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj) { - // Return the cached array before creating on demand. - NSMutableArray *cachedArray = [obj cacheChildForKey:jsonKey]; - if (cachedArray != nil) { - return cachedArray; - } - NSMutableArray *result = nil; - NSArray *array = [obj JSONValueForKey:jsonKey]; - if (array != nil) { - if ([array isKindOfClass:[NSArray class]]) { - id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver]; - result = [GTLRRuntimeCommon objectFromJSON:array - defaultClass:containedClass - objectClassResolver:objectClassResolver - isCacheable:NULL]; - } else { -#if DEBUG - if (![array isKindOfClass:[NSNull class]]) { - GTLR_DEBUG_LOG(@"GTLRObject: unexpected JSON: %@.%@ should be an array, actually is a %@:\n%@", - NSStringFromClass([obj class]), - NSStringFromSelector(sel), - NSStringFromClass([array class]), array); - } -#endif - result = (NSMutableArray *)array; - } - } - [obj setCacheChild:result forKey:jsonKey]; - return result; - }); - break; - } - - case GTLRPropertyTypeNSObject: { - resultIMP = imp_implementationWithBlock(^id(GTLRObject<GTLRRuntimeCommon> *obj) { - // Return the cached object before creating on demand. - id cachedObj = [obj cacheChildForKey:jsonKey]; - if (cachedObj != nil) { - return cachedObj; - } - id jsonObj = [obj JSONValueForKey:jsonKey]; - if (jsonObj != nil) { - BOOL shouldCache = NO; - id<GTLRObjectClassResolver>objectClassResolver = [obj objectClassResolver]; - id result = [GTLRRuntimeCommon objectFromJSON:jsonObj - defaultClass:nil - objectClassResolver:objectClassResolver - isCacheable:&shouldCache]; - - [obj setCacheChild:(shouldCache ? result : nil) - forKey:jsonKey]; - return result; - } - return nil; - }); - break; - } - } // switch(propertyType) - - return resultIMP; -} - -// Helper to get the IMP for wiring up the setters. -// NOTE: Every argument passed in should be safe to capture in a block. Avoid -// passing something like selName instead of sel, because nothing says that -// pointer will be valid when it is finally used when the method IMP is invoked -// some time later. -static IMP GTLRRuntimeSetterIMP(SEL sel, - GTLRPropertyType propertyType, - NSString *jsonKey, - Class containedClass, - Class returnClass) { -#pragma unused(sel, returnClass) - IMP resultIMP; - switch (propertyType) { - -#if !defined(__LP64__) || !__LP64__ - case GTLRPropertyTypeInt32: { - resultIMP = imp_implementationWithBlock(^(id obj, NSInteger val) { - [obj setJSONValue:@(val) forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeUInt32: { - resultIMP = imp_implementationWithBlock(^(id obj, NSUInteger val) { - [obj setJSONValue:@(val) forKey:jsonKey]; - }); - break; - } -#endif // __LP64__ - - case GTLRPropertyTypeLongLong: { - resultIMP = imp_implementationWithBlock(^(id obj, long long val) { - [obj setJSONValue:@(val) forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeULongLong: { - resultIMP = imp_implementationWithBlock(^(id obj, - unsigned long long val) { - [obj setJSONValue:@(val) forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeFloat: { - resultIMP = imp_implementationWithBlock(^(id obj, float val) { - [obj setJSONValue:@(val) forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeDouble: { - resultIMP = imp_implementationWithBlock(^(id obj, double val) { - [obj setJSONValue:@(val) forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeBool: { - resultIMP = imp_implementationWithBlock(^(id obj, BOOL val) { - NSNumber *numValue = (NSNumber *)(val ? kCFBooleanTrue : kCFBooleanFalse); - [obj setJSONValue:numValue forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeNSString: { - resultIMP = imp_implementationWithBlock(^(id obj, NSString *val) { - NSString *copiedStr = [val copy]; - [obj setJSONValue:copiedStr forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeGTLRDateTime: { - resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj, - GTLRDateTime *val) { - id cacheValue, jsonValue; - if (![val isKindOfClass:[NSNull class]]) { - jsonValue = val.RFC3339String; - cacheValue = val; - } else { - jsonValue = [NSNull null]; - cacheValue = nil; - } - - [obj setJSONValue:jsonValue forKey:jsonKey]; - [obj setCacheChild:cacheValue forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeGTLRDuration: { - resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj, - GTLRDuration *val) { - id cacheValue, jsonValue; - if (![val isKindOfClass:[NSNull class]]) { - jsonValue = val.jsonString; - cacheValue = val; - } else { - jsonValue = [NSNull null]; - cacheValue = nil; - } - - [obj setJSONValue:jsonValue forKey:jsonKey]; - [obj setCacheChild:cacheValue forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeNSNumber: { - resultIMP = imp_implementationWithBlock(^(id obj, NSNumber *val) { - [obj setJSONValue:val forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeGTLRObject: { - resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj, - GTLRObject *val) { - id cacheValue, jsonValue; - if (![val isKindOfClass:[NSNull class]]) { - NSMutableDictionary *dict = [val JSON]; - if (dict == nil && val != nil) { - // adding an empty object; it should have a JSON dictionary so it - // can hold future assignments - val.JSON = [NSMutableDictionary dictionary]; - jsonValue = val.JSON; - } else { - jsonValue = dict; - } - cacheValue = val; - } else { - jsonValue = [NSNull null]; - cacheValue = nil; - } - [obj setJSONValue:jsonValue forKey:jsonKey]; - [obj setCacheChild:cacheValue forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeNSArray: { - resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj, - NSMutableArray *val) { - id json = [GTLRRuntimeCommon jsonFromAPIObject:val - expectedClass:containedClass - isCacheable:NULL]; - [obj setJSONValue:json forKey:jsonKey]; - [obj setCacheChild:val forKey:jsonKey]; - }); - break; - } - - case GTLRPropertyTypeNSObject: { - resultIMP = imp_implementationWithBlock(^(GTLRObject<GTLRRuntimeCommon> *obj, - id val) { - BOOL shouldCache = NO; - id json = [GTLRRuntimeCommon jsonFromAPIObject:val - expectedClass:Nil - isCacheable:&shouldCache]; - [obj setJSONValue:json forKey:jsonKey]; - [obj setCacheChild:(shouldCache ? val : nil) - forKey:jsonKey]; - }); - break; - } - } // switch(propertyType) - - return resultIMP; -} - -#pragma mark Runtime - wiring point - -+ (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class<GTLRRuntimeCommon>)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<GTLRRuntimeCommon> foundClass = nil; - - objc_property_t prop = PropertyForSel(onClass, sel, isSetter, &foundClass); - if (prop == NULL || foundClass == nil) { - return NO; // No luck, out of here. - } - - Class returnClass = nil; - const GTLRDynamicImpInfo *implInfo = DynamicImpInfoForProperty(prop, - &returnClass); - if (implInfo == NULL) { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: unexpected return type class %s for " - @"property \"%s\" of class \"%s\"", - returnClass ? class_getName(returnClass) : "<nil>", - property_getName(prop), - class_getName(onClass)); - return NO; // Failed to find our impl info, out of here. - } - - const char *propName = property_getName(prop); - NSString *propStr = @(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 - // NOTE: These caches that are built up could likely be dropped and do this - // lookup on demand from the class tree. Most are checked once when a method - // is first resolved, so eventually become wasted memory. - NSDictionary *keyMap = - [[foundClass ancestorClass] propertyToJSONKeyMapForClass:foundClass]; - NSString *jsonKey = [keyMap objectForKey:propStr]; - if (jsonKey == nil) { - jsonKey = propStr; - } - - // For arrays we need to look up what the contained class is. - Class containedClass = nil; - if (implInfo->propertyType == GTLRPropertyTypeNSArray) { - NSDictionary *classMap = - [[foundClass ancestorClass] arrayPropertyToClassMapForClass:foundClass]; - containedClass = [classMap objectForKey:jsonKey]; - if (containedClass == Nil) { - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: expected array item class for " - @"property \"%s\" of class \"%s\"", - property_getName(prop), class_getName(foundClass)); - } - } - - // Wire in the method. - IMP imp; - const char *encoding; - if (isSetter) { - imp = GTLRRuntimeSetterIMP(sel, implInfo->propertyType, - jsonKey, containedClass, returnClass); - encoding = implInfo->setterEncoding; - } else { - imp = GTLRRuntimeGetterIMP(sel, implInfo->propertyType, - jsonKey, containedClass, returnClass); - encoding = implInfo->getterEncoding; - } - if (class_addMethod(foundClass, sel, imp, encoding)) { - return YES; - } - // Not much we can do if this fails, but leave a breadcumb in the log. - GTLR_DEBUG_LOG(@"GTLRRuntimeCommon: Failed to wire %@ on %@ (encoding: %s).", - NSStringFromSelector(sel), - NSStringFromClass(foundClass), - encoding); - return NO; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRService.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRService.h @@ -1,879 +0,0 @@ -/* Copyright (c) 2016 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. - */ - -// Service object documentation: -// https://github.com/google/google-api-objectivec-client-for-rest/wiki#services-and-tickets - -#import <Foundation/Foundation.h> - -#import "GTLRDefines.h" -#import "GTLRBatchQuery.h" -#import "GTLRBatchResult.h" -#import "GTLRDateTime.h" -#import "GTLRDuration.h" -#import "GTLRErrorObject.h" -#import "GTLRObject.h" -#import "GTLRQuery.h" - -@class GTMSessionFetcher; -@class GTMSessionFetcherService; -@protocol GTMFetcherAuthorizationProtocol; - -NS_ASSUME_NONNULL_BEGIN - -/** - * The domain used used for NSErrors created by GTLRService query execution. - */ -extern NSString *const kGTLRServiceErrorDomain; - -typedef NS_ENUM(NSInteger, GTLRServiceError) { - GTLRServiceErrorQueryResultMissing = -3000, - GTLRServiceErrorBatchResponseUnexpected = -3001, - GTLRServiceErrorBatchResponseStatusCode = -3002 -}; - -/** - * The kGTLRServiceErrorDomain userInfo key for the server response body. - */ -extern NSString *const kGTLRServiceErrorBodyDataKey; - -/** - * The kGTLRServiceErrorDomain userInfo key for the response content ID, if appropriate. - */ -extern NSString *const kGTLRServiceErrorContentIDKey; - -/** - * The domain used for foundation errors created from GTLRErrorObjects that - * were not originally foundation errors. - */ -extern NSString *const kGTLRErrorObjectDomain; - -/** - * The userInfo key for a GTLRErrorObject for errors with domain kGTLRErrorObjectDomain - * when the error was created from a structured JSON error response body. - */ -extern NSString *const kGTLRStructuredErrorKey; - -/** - * A constant ETag for when updating or deleting a single entry, telling - * the server to replace the current value unconditionally. - * - * Do not use this in entries in a batch feed. - */ -extern NSString *const kGTLRETagWildcard; - -/** - * Notification of a ticket starting. The notification object is the ticket. - * This is posted on the main thread. - * - * Use the stopped notification to log all requests made by the library. - */ -extern NSString *const kGTLRServiceTicketStartedNotification; - -/** - * Notification of a ticket stopping. The notification object is the ticket. - * This is posted on the main thread. - */ -extern NSString *const kGTLRServiceTicketStoppedNotification; - -/** - * Notifications when parsing of a server response or entry begins. - * This is posted on the main thread. - */ -extern NSString *const kGTLRServiceTicketParsingStartedNotification; - -/** - * Notifications when parsing of a server response or entry ends. - * This is posted on the main thread. - */ -extern NSString *const kGTLRServiceTicketParsingStoppedNotification; - -/** - * The header name used to send an Application's Bundle Identifier. - * For more information on adding API restrictions see the docs: - * https://cloud.google.com/docs/authentication/api-keys#api_key_restrictions - */ -extern NSString *const kXIosBundleIdHeader; - -@class GTLRServiceTicket; - -/** - * Callback block for query execution. - * - * @param callbackTicket The ticket that tracked query execution. - * @param object The result of query execution. This will be derived from - * GTLRObject. The object may be nil for operations such as DELETE which - * do not return an object. The object will be a GTLRBatchResult for - * batch operations, and GTLRDataObject for media downloads. - * @param callbackError If non-nil, the query execution failed. For batch requests, - * this may be nil even if individual queries in the batch have failed. - */ -typedef void (^GTLRServiceCompletionHandler)(GTLRServiceTicket *callbackTicket, - id _Nullable object, - NSError * _Nullable callbackError); - -/** - * Callback block for upload of query data. - * - * @param progressTicket The ticket that tracks query execution. - * @param totalBytesUploaded Number of bytes uploaded so far. - * @param totalBytesExpectedToUpload Number of bytes expected to be uploaded. - */ -typedef void (^GTLRServiceUploadProgressBlock)(GTLRServiceTicket *progressTicket, - unsigned long long totalBytesUploaded, - unsigned long long totalBytesExpectedToUpload); - -/** - * Callback block invoked when an eror occurs during query execution. - * - * @param retryTicket The ticket that tracks query execution. - * @param suggestedWillRetry Flag indicating if the library would retry this without a retry block. - * @param fetchError The error that occurred. If the domain is - * kGTMSessionFetcherStatusDomain then the error's code is the server - * response status. Details on the error from the server are available - * in the userInfo via the keys kGTLRStructuredErrorKey and - * NSLocalizedDescriptionKey. - * - * @return YES if the request should be retried. - */ -typedef BOOL (^GTLRServiceRetryBlock)(GTLRServiceTicket *retryTicket, - BOOL suggestedWillRetry, - NSError * _Nullable fetchError); - -/** - * Block to be invoked by a test block. - * - * @param object The faked object, if any, to be passed to the test code's completion handler. - * @param error The faked error if any, to be passed to the test code's completion handler. - */ -typedef void (^GTLRServiceTestResponse)(id _Nullable object, NSError *_Nullable error); - -/** - * A test block enables testing of query execution without any network activity. - * - * The test block must finish by calling the response block, passing either an object - * (GTLRObject or GTLRBatchResult) or an NSError. - * - * The query is available to the test block code as testTicket.originalQuery. - * - * Because query execution is asynchronous, the test code must wait for a callback, - * either with GTLRService's waitForTicket:timeout:fetchedObject:error: or with - * XCTestCase's waitForExpectationsWithTimeout: - * - * Example usage is available in GTLRServiceTest. - * - * @param testTicket The ticket that tracks query execution. - * @param testResponse A block that must be invoked by the test block. This may be invoked - * synchronously or asynchornously. - */ -typedef void (^GTLRServiceTestBlock)(GTLRServiceTicket *testTicket, - GTLRServiceTestResponse testResponse); - -#pragma mark - - -/** - * Base class for the service that executes queries and manages tickets. - * - * Client apps will typically use a generated subclass of GTLRService. - */ -@interface GTLRService : NSObject - -#pragma mark Query Execution - -/** - * Executes the supplied query - * - * Success is indicated in the completion handler by a nil error parameter, not by a non-nil - * object parameter. - * - * The callback block is invoked exactly once unless the ticket is cancelled. - * The callback will be called on the service's callback queue. - * - * Various execution parameters will be taken from the service's properties, unless overridden - * in the query's @c executionParameters property. - * - * A query may only be executed a single time. To reuse a query, make a copy before executing - * it. - * - * To get a NSURLRequest that represents the query, use @c -[GTLRService requestForQuery:] - * - * @param query The API query, either a subclass of GTLRQuery, or a GTLRBatchQuery. - * @param handler The execution callback block. - * - * @return A ticket for tracking or canceling query execution. - */ -- (GTLRServiceTicket *)executeQuery:(id<GTLRQueryProtocol>)query - completionHandler:(nullable GTLRServiceCompletionHandler)handler; - -/** - * Executes the supplied query - * - * The callback is invoked exactly once unless the ticket is cancelled. - * The callback will be called on the service's callbackQueue. - * Various execution parameters will be taken from the service's properties, unless overridden - * in the query's @c executionParameters property. - * - * The selector should have a signature matching: - * @code - * - (void)serviceTicket:(GTLRServiceTicket *)callbackTicket - * finishedWithObject:(GTLRObject *)object - * error:(NSError *)callbackError - * @endcode - * - * @param query The API query, either a subclass of GTLRQuery, or a GTLRBatchQuery. - * @param delegate The object to be with the selector to be invoked upon completion. - * @param finishedSelector The selector to be invoked upon completion. - * - * @return A ticket for tracking or canceling query execution. - */ -- (GTLRServiceTicket *)executeQuery:(id<GTLRQueryProtocol>)query - delegate:(nullable id)delegate - didFinishSelector:(nullable SEL)finishedSelector; - - -/** - * Enable automatic pagination. - * - * A ticket can optionally do a sequence of fetches for queries where repeated requests - * with a @c nextPageToken query parameter is required to retrieve all pages of - * the response collection. The client's callback is invoked only when all items have - * been retrieved, or an error has occurred. - * - * 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 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 @c maxResults parameter - * should probably be increased to specify more items returned per page. - * - * Automatic page accumulation is available for query result objects that are derived - * from GTLRCollectionObject. - * - * This may also be specified for a single query in the query's @c executionParameters property. - * - * Default value is NO. - */ -@property(nonatomic, assign) BOOL shouldFetchNextPages; - -/** - * Some services require a developer key for quotas and limits. - * - * If you have enabled the iOS API Key Restriction, you will want - * to manually set the @c APIKeyRestrictionBundleID property, or - * use -setMainBundleIDRestrictionWithAPIKey: to set your API key - * and set the restriction to the main bundle's bundle id. - */ -@property(nonatomic, copy, nullable) NSString *APIKey; - -/** - * The Bundle Identifier to use for the API key restriction. This will be - * sent in an X-Ios-Bundle-Identifier header; for more information see - * the API key documentation - * https://cloud.google.com/docs/authentication/api-keys#api_key_restrictions - */ -@property(nonatomic, copy, nullable) NSString *APIKeyRestrictionBundleID; - -/** - * Helper method to set the @c APIKey to the given value and set the - * @c APIKeyRestrictionBundleID to the main bundle's bundle identifier. - */ -- (void)setMainBundleIDRestrictionWithAPIKey:(NSString *)apiKey; - -/** - * An authorizer adds user authentication headers to the request as needed. - * - * This may be overridden on individual queries with the @c shouldSkipAuthorization property. - */ -@property(nonatomic, retain, nullable) id <GTMFetcherAuthorizationProtocol> authorizer; - -/** - * Enable fetcher retry support. See the explanation of retry support in @c GTMSessionFetcher.h - * - * Default value is NO, but retry is also enabled if the retryBlock is not nil. - * - * This may also be specified for a single query in the query's @c executionParameters property. - */ -@property(nonatomic, assign, getter=isRetryEnabled) BOOL retryEnabled; - -/** - * A retry block may be provided to inspect and change retry criteria. - * - * This may also be specified for a single query in the query's @c executionParameters property. - */ -@property(atomic, copy, nullable) GTLRServiceRetryBlock retryBlock; - -/** - * The maximum retry interval. Retries occur at increasing intervals, up to the specified maximum. - * - * This may also be specified for a single query in the query's @c executionParameters property. - */ -@property(nonatomic, assign) NSTimeInterval maxRetryInterval; - -#pragma mark Fetch Object by Resource URL - -/** - * Fetch an object given the resource URL. This is appropriate when the object's - * full link is known, such as from a selfLink response property. - * - * @param resourceURL The URL of the object to be fetched. - * @param objectClass The GTLRObject subclass to be instantiated. If nil, the library - * will try to infer the class from the object's "kind" string property. - * @param executionParameters Values to override the service's properties when executing the - * ticket. - * @param handler The execution callback block. - * - * @return A ticket for tracking or canceling query execution. - */ -- (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)resourceURL - objectClass:(nullable Class)objectClass - executionParameters:(nullable GTLRServiceExecutionParameters *)executionParameters - completionHandler:(nullable GTLRServiceCompletionHandler)handler; - -#pragma mark Support for Client Tests - -/** - * A test block can be provided to test service calls without any network activity. - * - * See the description of @c GTLRServiceTestBlock for additional details. - * - * This may also be specified for a single query in the query's @c executionParameters property. - * - * A service instance for testing can also be created with @c +mockServiceWithFakedObject - */ -@property(nonatomic, copy, nullable) GTLRServiceTestBlock testBlock; - -#pragma mark Converting a Query to an NSURLRequest - -/** - * Creates a NSURLRequest from the query object and from properties on this service - * (additionalHTTPHeaders, additionalURLQueryParameters, APIKey) without executing - * it. This can be useful for using @c GTMSessionFetcher or @c NSURLSession to - * perform the fetch. - * - * For requests to non-public resources, the request will not yet be authorized; - * that can be done using the GTLR service's authorizer. Creating a @c GTMSessionFetcher - * from the GTLRService's @c fetcherService will take care of authorization as well. - * - * This works only for GET queries, and only for an individual query, not a batch query. - * - * @note @c Unlike executeQuery:, requestForQuery: does not release the query's callback blocks. - * - * @param query The query used to create the request. - * - * @return A request suitable for use with @c GTMSessionFetcher or @c NSURLSession - */ -- (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query; - -#pragma mark User Properties - -/** - * The service properties dictionary is copied to become the initial property dictionary - * for each ticket, augmented by a query's execution parameter's properties. - */ -@property(nonatomic, copy, nullable) NSDictionary<NSString *, id> *serviceProperties; - -#pragma mark JSON to GTLRObject Mapping - -/** - * Specifies subclasses to be created instead of standard library objects, allowing - * an app to add properties and methods to GTLR objects. - * - * This is just a helper method that sets the service's objectClassResolver:. - * - * Example: - * @code - * NSDictionary *surrogates = @{ - * [MyDriveFile class] : [GTLRDrive_File_Surrogate class], - * [MyDriveFileList class] : [GTLRDrive_FileList_Surrogate class] - * }; - * [service setSurrogates:surrogates]; - * @endcode - */ -- (void)setSurrogates:(NSDictionary <Class, Class>*)surrogates; - -/** - * Used to decide what GTLRObject subclass to make from the received JSON. - * - * This defaults to a resolver that will use any kindStringToClassMap the service - * provides. - * - * To use a standard resolver with a surrogates dictionary, invoke setSurrogates: instead - * of setting this property. - */ -@property(nonatomic, strong) id<GTLRObjectClassResolver> objectClassResolver; - -/** - * A dictionary mapping "kind" strings to the GTLObject subclasses that should - * be created for JSON with the given kind. - */ -+ (NSDictionary<NSString *, Class> *)kindStringToClassMap; - -#pragma mark Request Settings - -/** - * The queue used to invoked callbacks. By default, the main queue is used for callbacks. - */ -@property(nonatomic, retain) dispatch_queue_t callbackQueue; - -/** - * Allows the application to make non-SSL and localhost requests for testing. - * - * Default value is NO. - */ -@property(nonatomic, assign) BOOL allowInsecureQueries; - -/** - * The fetcher service creates the fetcher instances for this API service. - * - * Applications may set this to an authorized fetcher service created elsewhere - * in the app, or may take the fetcher service created by this GTLRService and use it - * to create fetchers independent of this service. - */ -@property(nonatomic, retain) GTMSessionFetcherService *fetcherService; - -#pragma mark Custom User Agents - -/** - * Applications needing an additional identifier in the server logs may specify one - * through this property and it will be added to the existing UserAgent. It should - * already be a valid identifier as no cleaning/validation is done. - */ -@property(nonatomic, copy, nullable) NSString *userAgentAddition; - -/** - * A base user-agent based on the application signature in the Info.plist settings. - * - * Most applications should not explicitly set this property. Any string provided will - * be cleaned of inappropriate characters. - */ -@property(nonatomic, copy, nullable) 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, nullable) NSString *requestUserAgent; - -/** - * A precise base userAgent string identifying the application. No cleaning of characters - * is done. Library-specific details will be appended. - * - * @param userAgent A wire-ready user agent string. - */ -- (void)setExactUserAgent:(nullable NSString *)userAgent; - -/** - * A precise userAgent string to send on requests; no cleaning is done. When - * set, requestUserAgent will be exactly this, no library or system information - * will be auto added. - * - * @param requestUserAgent A wire-ready user agent string. - */ -- (void)overrideRequestUserAgent:(nullable NSString *)requestUserAgent; - -/** - * Any additional URL query parameters for the queries executed by this service. - * - * Individual queries may have additionalURLQueryParameters specified as well. - */ -@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalURLQueryParameters; - -/** - * Any additional HTTP headers for this queries executed by this service. - * - * Individual queries may have additionalHTTPHeaders specified as well. - */ -@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalHTTPHeaders; - -#pragma mark Request URL Construction - -/* - * The URL for where to send a Query is built out of these parts - * ( https://developers.google.com/discovery/v1/using#build-compose ) : - * - * service.rootURLString + service.servicePath + query.pathURITemplate - * - * Note: odds are these both should end in a '/', so make sure any value you - * provide will combine correctly with the above rules. - */ - -/** - * The scheme and host for the API server. This may be modified to point at a test server. - */ -@property(nonatomic, copy) NSString *rootURLString; - -/** - * The path for the specific API service instance, relative to the rootURLString. - */ -@property(nonatomic, copy) NSString *servicePath; - -/** - * A path fragment added in to URLs before "servicePath" to build - * the full URL used for resumable media uploads. - */ -@property(nonatomic, copy) NSString *resumableUploadPath; - -/** - * A path fragment added in to URLs before "servicePath" to build - * the full URL used for simple and multipart media uploads. - */ -@property(nonatomic, copy) NSString *simpleUploadPath; - -/** - * A path fragment added in to URLs before "servicePath" to build - * the full URL used for batch requests. - */ -@property(nonatomic, copy) NSString *batchPath; - -#pragma mark Resumable Uploads - -/** - * A block called to track upload progress. - * - * A query's service execution parameters may be used to override this. - */ -@property(nonatomic, copy, nullable) GTLRServiceUploadProgressBlock uploadProgressBlock; - -/** - * The default chunk size for resumable uploads. This defaults to kGTLRStandardUploadChunkSize - * for service subclasses that support chunked uploads. - */ -@property(nonatomic, assign) NSUInteger serviceUploadChunkSize; - -/** - * Service subclasses may override this to specify their own default chunk size for - * resumable uploads. - */ -+ (NSUInteger)defaultServiceUploadChunkSize; - -#pragma mark Internal -///////////////////////////////////////////////////////////////////////////////////////////// -// -// Properties below are used by the library and should not typically be set by client apps. -// -///////////////////////////////////////////////////////////////////////////////////////////// - -/** - * The queue used for parsing JSON responses. - * - * Applications should typically not change this. - */ -@property(nonatomic, retain) dispatch_queue_t parseQueue; - -/** - * If this service supports pretty printing the JSON on the wire, these are - * the names of the query params that enable it. If there are any values, - * the library disables pretty printing to save on bandwidth. - * - * Applications should typically not need change this; the ServiceGenerator - * will set this up when generating the custom subclass. - */ -@property(nonatomic, strong, nullable) NSArray<NSString *> *prettyPrintQueryParameterNames; - -/** - * This indicates if the API requires a "data" JSON element to wrap the payload - * on requests and responses. - * - * Applications should typically not change this. - */ -@property(nonatomic, assign, getter=isDataWrapperRequired) BOOL dataWrapperRequired; - -@end - -@interface GTLRService (TestingSupport) - -/** - * Convenience method to create a mock GTLR service just for testing. - * - * Queries executed by this mock service will not perform any network operation, - * but will invoke callbacks and provide the supplied object or error to the - * completion handler. - * - * You can make more customized mocks by setting the test block property of a service - * or a query's execution parameters. The test block can inspect the query as ticket.originalQuery - * to customize test behavior. - * - * See the description of @c GTLRServiceTestBlock for more details on customized testing. - * - * Example usage is in the unit test method @c testService_MockService_Succeeding - * - * @param object An object derived from GTLRObject to be passed to query completion handlers. - * @param error An error to be passed to query completion handlers. - * - * @return A mock instance of the service, suitable for unit testing. - */ -+ (instancetype)mockServiceWithFakedObject:(nullable id)object - fakedError:(nullable NSError *)error; - -/** - * Wait synchronously for fetch to complete (strongly discouraged) - * - * This method is intended for use only in unit tests and command-line tools. - * Unit tests may also use XCTest's waitForExpectationsWithTimeout: instead of - * or after this method. - * - * This method 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 YES if an object was successfully fetched. If the wait - * timed out, returns NO and the returned error is nil. - * - * @param ticket The ticket being executed. - * @param timeoutInSeconds Maximum duration to wait. - * - * @return YES if the ticket completed or was cancelled; NO if the wait timed out. - */ -- (BOOL)waitForTicket:(GTLRServiceTicket *)ticket - timeout:(NSTimeInterval)timeoutInSeconds; - -@end - -#pragma mark - - -/** - * Service execution parameters may be set on an individual query - * to alter the service's settings. - */ -@interface GTLRServiceExecutionParameters : NSObject<NSCopying> - -/** - * Override the service's property @c shouldFetchNextPages for automatic pagination. - * - * A BOOL value should be specified. - */ -@property(atomic, strong, nullable) NSNumber *shouldFetchNextPages; - -/** - * Override the service's property @c shouldFetchNextPages for enabling automatic retries. - * - * A BOOL value should be specified. - * - * Retry is also enabled if the retryBlock is not nil - */ -@property(atomic, strong, nullable, getter=isRetryEnabled) NSNumber *retryEnabled; - -/** - * Override the service's property @c retryBlock for customizing automatic retries. - */ -@property(atomic, copy, nullable) GTLRServiceRetryBlock retryBlock; - -/** - * Override the service's property @c maxRetryInterval for customizing automatic retries. - * - * A NSTimeInterval (double) value should be specified. - */ -@property(atomic, strong, nullable) NSNumber *maxRetryInterval; - -/** - * Override the service's property @c uploadProgressBlock for monitoring upload progress. - */ -@property(atomic, copy, nullable) GTLRServiceUploadProgressBlock uploadProgressBlock; - -/** - * Override the service's property @c callbackQueue for invoking callbacks. - */ -@property(atomic, retain, nullable) dispatch_queue_t callbackQueue; - -/** - * Override the service's property @c testBlock for simulating query execution. - * - * See the description of @c GTLRServiceTestBlock for additional details. - */ -@property(atomic, copy, nullable) GTLRServiceTestBlock testBlock; - -/** - * Override the service's property @c objectClassResolver for controlling object class selection. - */ -@property(atomic, strong, nullable) id<GTLRObjectClassResolver> objectClassResolver; - -/** - * The ticket's properties are the service properties, with the execution parameter's - * ticketProperties added (replacing any keys already present from the service.) - */ -@property(atomic, copy, nullable) NSDictionary<NSString *, id> *ticketProperties; - -/** - * Indicates if any of the execution parameters properties are set. - */ -@property(nonatomic, readonly) BOOL hasParameters; - -@end - -/** - * A ticket tracks the progress of a query being executed. - */ -@interface GTLRServiceTicket : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -/** - * The service that issued this ticket. - * - * This method may be invoked from any thread. - */ -@property(atomic, readonly) GTLRService *service; - -#pragma mark Execution Control - -/** - * Invoking cancelTicket stops the fetch if it is in progress. The query callbacks - * will not be invoked. - * - * This method may be invoked from any thread. - */ -- (void)cancelTicket; - -/** - * The time the ticket was created. - */ -@property(atomic, readonly) NSDate *creationDate; - -/** - * Pause the ticket execution. This is valid only for chunked, resumable upload queries. - */ -- (void)pauseUpload; - -/** - * Resume the ticket execution. This is valid only for chunked, resumable upload queries. - */ -- (void)resumeUpload; - -/** - * Checks if the ticket execution is paused. - */ -@property(nonatomic, readonly, getter=isUploadPaused) BOOL uploadPaused; - -/** - * The request being fetched for the query. - */ -@property(nonatomic, readonly, nullable) NSURLRequest *fetchRequest; - -/** - * The fetcher being used for the query request. - */ -@property(atomic, readonly, nullable) GTMSessionFetcher *objectFetcher; - -/** - * The queue used for query callbacks. - */ -@property(atomic, readonly) dispatch_queue_t callbackQueue; - -/** - * The API key used for the query requeat. - */ -@property(atomic, readonly, nullable) NSString *APIKey; - -/** - * The Bundle Identifier to use for the API key restriciton. - */ -@property(atomic, readonly, nullable) NSString *APIKeyRestrictionBundleID; - -#pragma mark Status - -/** - * The server's response status for the query's fetch, if available. - */ -@property(nonatomic, readonly) NSInteger statusCode; - -/** - * The error resulting from the query's fetch, if available. - */ -@property(nonatomic, readonly, nullable) NSError *fetchError; - -/** - * A flag indicating if the query's callbacks have been invoked. - */ -@property(nonatomic, readonly) BOOL hasCalledCallback; - -/** - * A flag indicating if the query execution was cancelled by the client app. - */ -@property(atomic, readonly, getter=isCancelled) BOOL cancelled; - -#pragma mark Pagination - -/** - * A flag indicating if automatic pagination is enabled for the query. - */ -@property(nonatomic, readonly) BOOL shouldFetchNextPages; - -/** - * The number of pages fetched, if automatic pagination is enabled for the query and multiple - * pages have been fetched. - */ -@property(nonatomic, readonly) NSUInteger pagesFetchedCounter; - -#pragma mark User Properties - -/** - * Ticket properties a way to pass values via the ticket for the convenience of the client app. - * - * Ticket properties are initialized from serviceProperties and augmented by the ticketProperties - * of the query's execution parameters. - */ -@property(nonatomic, readonly, nullable) NSDictionary<NSString *, id> *ticketProperties; - -#pragma mark Payload - -/** - * The object being uploaded via POST, PUT, or PATCH. - */ -@property(nonatomic, readonly, nullable) GTLRObject *postedObject; - -/** - * The object downloaded for the query, after parsing. - */ -@property(nonatomic, readonly, nullable) GTLRObject *fetchedObject; - -/** - * The query currently being fetched by this ticket. This may not be the original query when - * fetching a second or later pages. - */ -@property(atomic, readonly, nullable) id<GTLRQueryProtocol> executingQuery; - -/** - * The query used to create this ticket - */ -@property(atomic, readonly, nullable) id<GTLRQueryProtocol> originalQuery; - -/** - * The @c GTLRObjectClassResolver for controlling object class selection. - */ -@property(atomic, readonly, strong) id<GTLRObjectClassResolver> objectClassResolver; - -/** - * The query from within the ticket's batch request with the given ID. - * - * @param requestID The desired ticket's request ID. - * - * @return The query with the specified ID, if found. - */ -- (nullable GTLRQuery *)queryForRequestID:(NSString *)requestID; - -@end - -/** - * The library doesn't use GTLRObjectCollectionImpl, but it provides a concrete implementation - * so the methods do not cause private method errors in Xcode/AppStore review. - */ -@interface GTLRObjectCollectionImpl : GTLRObject -@property(nonatomic, copy) NSString *nextPageToken; -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRService.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRService.m @@ -1,2883 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import <TargetConditionals.h> - -#if TARGET_OS_IPHONE -#import <UIKit/UIKit.h> -#endif - -#if !defined(GTLR_USE_FRAMEWORK_IMPORTS) - #if defined(COCOAPODS) && COCOAPODS - #define GTLR_USE_FRAMEWORK_IMPORTS 1 - #else - #define GTLR_USE_FRAMEWORK_IMPORTS 0 - #endif -#endif - -#import "GTLRService.h" - -#import "GTLRFramework.h" -#import "GTLRURITemplate.h" -#import "GTLRUtilities.h" - -#if GTLR_USE_FRAMEWORK_IMPORTS - #import <GTMSessionFetcher/GTMSessionFetcher.h> - #import <GTMSessionFetcher/GTMSessionFetcherService.h> - #import <GTMSessionFetcher/GTMMIMEDocument.h> -#else - #import "GTMSessionFetcher.h" - #import "GTMSessionFetcherService.h" - #import "GTMMIMEDocument.h" -#endif // GTLR_USE_FRAMEWORK_IMPORTS - - -#ifndef STRIP_GTM_FETCH_LOGGING - #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. -#endif - -NSString *const kGTLRServiceErrorDomain = @"com.google.GTLRServiceDomain"; -NSString *const kGTLRErrorObjectDomain = @"com.google.GTLRErrorObjectDomain"; -NSString *const kGTLRServiceErrorBodyDataKey = @"body"; -NSString *const kGTLRServiceErrorContentIDKey = @"contentID"; -NSString *const kGTLRStructuredErrorKey = @"GTLRStructuredError"; -NSString *const kGTLRETagWildcard = @"*"; - -NSString *const kGTLRServiceTicketStartedNotification = @"kGTLRServiceTicketStartedNotification"; -NSString *const kGTLRServiceTicketStoppedNotification = @"kGTLRServiceTicketStoppedNotification"; -NSString *const kGTLRServiceTicketParsingStartedNotification = @"kGTLRServiceTicketParsingStartedNotification"; -NSString *const kGTLRServiceTicketParsingStoppedNotification = @"kGTLRServiceTicketParsingStoppedNotification"; - -NSString *const kXIosBundleIdHeader = @"X-Ios-Bundle-Identifier"; - -static NSString *const kDeveloperAPIQueryParamKey = @"key"; - -static const NSUInteger kMaxNumberOfNextPagesFetched = 25; - -static const NSUInteger kMaxGETURLLength = 2048; - -// we'll enforce 50K chunks minimum just to avoid the server getting hit -// with too many small upload chunks -static const NSUInteger kMinimumUploadChunkSize = 50000; - -// Helper to get the ETag if it is defined on an object. -static NSString *ETagIfPresent(GTLRObject *obj) { - NSString *result = [obj.JSON objectForKey:@"etag"]; - return result; -} - -// Merge two dictionaries. Either may be nil. -// If both are nil, return nil. -// In case of a key collision, values of the second dictionary prevail. -static NSDictionary *MergeDictionaries(NSDictionary *recessiveDict, NSDictionary *dominantDict) { - if (!dominantDict) return recessiveDict; - if (!recessiveDict) return dominantDict; - - NSMutableDictionary *worker = [recessiveDict mutableCopy]; - [worker addEntriesFromDictionary:dominantDict]; - return worker; -} - -@interface GTLRServiceTicket () - -- (instancetype)initWithService:(GTLRService *)service - executionParameters:(GTLRServiceExecutionParameters *)params NS_DESIGNATED_INITIALIZER; - -// Thread safety: ticket properties are all publicly exposed as read-only. -// -// Service execution of a ticket is serial (started by the app, then executing on the fetcher -// callback queue and then the parse queue), so we don't need to worry about synchronization. -// -// One important exception is when the user invoked cancelTicket. During cancellation, ticket -// properties are released. This should be harmless even during the fetch start-parse-callback -// phase because nothing released in cancelTicket is used to begin a fetch, and the cancellation -// flag will prevent any application callbacks from being invoked. -// -// The cancel and objectFetcher properties are synchronized on the ticket. - -// Ticket properties exposed publicly as readonly. -@property(atomic, readwrite, nullable) id<GTLRQueryProtocol> originalQuery; -@property(atomic, readwrite, nullable) id<GTLRQueryProtocol> executingQuery; -@property(atomic, readwrite, nullable) GTMSessionFetcher *objectFetcher; -@property(nonatomic, readwrite, nullable) NSURLRequest *fetchRequest; -@property(nonatomic, readwrite, nullable) GTLRObject *postedObject; -@property(nonatomic, readwrite, nullable) GTLRObject *fetchedObject; -@property(nonatomic, readwrite, nullable) NSError *fetchError; -@property(nonatomic, readwrite) BOOL hasCalledCallback; -@property(nonatomic, readwrite) NSUInteger pagesFetchedCounter; -@property(readwrite, atomic, strong) id<GTLRObjectClassResolver> objectClassResolver; - -// Internal properties copied from the service. -@property(nonatomic, assign) BOOL allowInsecureQueries; -@property(nonatomic, strong) GTMSessionFetcherService *fetcherService; -@property(nonatomic, strong, nullable) id<GTMFetcherAuthorizationProtocol> authorizer; - -// Internal properties copied from serviceExecutionParameters. -@property(nonatomic, getter=isRetryEnabled) BOOL retryEnabled; -@property(nonatomic, readwrite) NSTimeInterval maxRetryInterval; -@property(nonatomic, strong, nullable) GTLRServiceRetryBlock retryBlock; -@property(nonatomic, strong, nullable) GTLRServiceUploadProgressBlock uploadProgressBlock; -@property(nonatomic, strong, nullable) GTLRServiceTestBlock testBlock; -@property(nonatomic, readwrite) BOOL shouldFetchNextPages; - -// Internal properties used by the service. -#if GTM_BACKGROUND_TASK_FETCHING -// Access to backgroundTaskIdentifier should be protected by @synchronized(self). -@property(nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskIdentifier; -#endif // GTM_BACKGROUND_TASK_FETCHING - -// Dispatch group enabling waitForTicket: to delay until async callbacks and notifications -// related to the ticket have completed. -@property(nonatomic, readonly) dispatch_group_t callbackGroup; - -// startBackgroundTask and endBackgroundTask do nothing if !GTM_BACKGROUND_TASK_FETCHING -- (void)startBackgroundTask; -- (void)endBackgroundTask; - -- (void)notifyStarting:(BOOL)isStarting; -- (void)releaseTicketCallbacks; - -// Posts a notification on the main queue using the ticket's dispatch group. -- (void)postNotificationOnMainThreadWithName:(NSString *)name - object:(id)object - userInfo:(NSDictionary *)userInfo; -@end - -#if !defined(GTLR_HAS_SESSION_UPLOAD_FETCHER_IMPORT) - #if defined(COCOAPODS) && COCOAPODS - #define GTLR_HAS_SESSION_UPLOAD_FETCHER_IMPORT 1 - #else - #define GTLR_HAS_SESSION_UPLOAD_FETCHER_IMPORT 0 - #endif -#endif - -#if GTLR_HAS_SESSION_UPLOAD_FETCHER_IMPORT - #if GTLR_USE_FRAMEWORK_IMPORTS - #import <GTMSessionFetcher/GTMSessionUploadFetcher.h> - #else - #import "GTMSessionUploadFetcher.h" - #endif // GTLR_USE_FRAMEWORK_IMPORTS -#else -// If the upload fetcher class is available, it can be used for chunked uploads -// -// We locally declare some methods of the upload fetcher so we -// do not need to import the header, as some projects may not have it available -@interface GTMSessionUploadFetcher : GTMSessionFetcher - -+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; - -+ (instancetype)uploadFetcherWithLocation:(NSURL *)uploadLocationURL - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; - -@property(strong) NSURL *uploadLocationURL; -@property(strong) NSData *uploadData; -@property(strong) NSURL *uploadFileURL; -@property(strong) NSFileHandle *uploadFileHandle; - -- (void)pauseFetching; -- (void)resumeFetching; -- (BOOL)isPaused; -@end -#endif // GTLR_HAS_SESSION_UPLOAD_FETCHER_IMPORT - - -@interface GTLRObject (StandardProperties) -// Common properties on GTLRObject that are invoked below. -@property(nonatomic, copy) NSString *nextPageToken; -@end - -// This class encapsulates the pieces of a single batch response, including -// inner http response code and message, inner headers, JSON body (parsed as a dictionary), -// or parsing NSError. -// -// See responsePartsWithMIMEParts: for an example of the wire format data used -// to populate this object. -@interface GTLRBatchResponsePart : NSObject -@property(nonatomic, copy) NSString *contentID; -@property(nonatomic, assign) NSInteger statusCode; -@property(nonatomic, copy) NSString *statusString; -@property(nonatomic, strong) NSDictionary *headers; -@property(nonatomic, strong) NSDictionary *JSON; -@property(nonatomic, strong) NSError *parseError; -@end - -@implementation GTLRBatchResponsePart -@synthesize contentID = _contentID, - headers = _headers, - JSON = _JSON, - parseError = _parseError, - statusCode = _statusCode, - statusString = _statusString; -#if DEBUG -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p: %@\n%ld %@\nheaders:%@\nJSON:%@\nerror:%@", - [self class], self, self.contentID, (long)self.statusCode, self.statusString, - self.headers, self.JSON, self.parseError]; -} -#endif -@end - -// GTLRResourceURLQuery is an internal class used as a query object placeholder -// when fetchObjectWithURL: is invoked by the client app. This lets the service's -// plumbing treat the request like other queries, without allowing users to -// set arbitrary query properties that may not work as anticipated. -@interface GTLRResourceURLQuery : GTLRQuery - -@property(nonatomic, strong, nullable) NSURL *resourceURL; - -+ (instancetype)queryWithResourceURL:(NSURL *)resourceURL - objectClass:(nullable Class)objectClass; - -@end - -@implementation GTLRService { - NSString *_userAgent; - NSString *_overrideUserAgent; - NSDictionary *_serviceProperties; // Properties retained for the convenience of the client app. - NSUInteger _uploadChunkSize; // Only applies to resumable chunked uploads. -} - -@synthesize additionalHTTPHeaders = _additionalHTTPHeaders, - additionalURLQueryParameters = _additionalURLQueryParameters, - allowInsecureQueries = _allowInsecureQueries, - callbackQueue = _callbackQueue, - APIKey = _apiKey, - APIKeyRestrictionBundleID = _apiKeyRestrictionBundleID, - batchPath = _batchPath, - dataWrapperRequired = _dataWrapperRequired, - fetcherService = _fetcherService, - maxRetryInterval = _maxRetryInterval, - parseQueue = _parseQueue, - prettyPrintQueryParameterNames = _prettyPrintQueryParameterNames, - resumableUploadPath = _resumableUploadPath, - retryBlock = _retryBlock, - retryEnabled = _retryEnabled, - rootURLString = _rootURLString, - servicePath = _servicePath, - shouldFetchNextPages = _shouldFetchNextPages, - simpleUploadPath = _simpleUploadPath, - objectClassResolver = _objectClassResolver, - testBlock = _testBlock, - uploadProgressBlock = _uploadProgressBlock, - userAgentAddition = _userAgentAddition; - -+ (Class)ticketClass { - return [GTLRServiceTicket class]; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _parseQueue = dispatch_queue_create("com.google.GTLRServiceParse", DISPATCH_QUEUE_SERIAL); - _callbackQueue = dispatch_get_main_queue(); - _fetcherService = [[GTMSessionFetcherService alloc] init]; - - // Make the session fetcher use a background delegate queue instead of bouncing - // through the main queue for its callbacks from NSURLSession. This should improve - // performance, and eventually be the default behavior for the fetcher. - NSOperationQueue *delegateQueue = [[NSOperationQueue alloc] init]; - delegateQueue.maxConcurrentOperationCount = 1; - delegateQueue.name = @"com.google.GTLRServiceFetcherDelegate"; - _fetcherService.sessionDelegateQueue = delegateQueue; - - NSDictionary<NSString *, Class> *kindMap = [[self class] kindStringToClassMap]; - _objectClassResolver = [GTLRObjectClassResolver resolverWithKindMap:kindMap]; - } - return self; -} - -- (NSString *)requestUserAgent { - if (_overrideUserAgent != nil) { - return _overrideUserAgent; - } - - NSString *userAgent = self.userAgent; - if (userAgent.length == 0) { - // The service instance is missing an explicit user-agent; use the bundle ID - // or process name. Don't use the bundle ID of the library's framework. - NSBundle *owningBundle = [NSBundle bundleForClass:[self class]]; - if (owningBundle == nil - || [owningBundle.bundleIdentifier isEqual:@"com.google.GTLR"]) { - owningBundle = [NSBundle mainBundle]; - } - userAgent = GTMFetcherApplicationIdentifier(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 = GTLRFrameworkVersionString(); - - NSString *systemString = GTMFetcherSystemVersionString(); - - // 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; -} - -- (void)setMainBundleIDRestrictionWithAPIKey:(NSString *)apiKey { - self.APIKey = apiKey; - self.APIKeyRestrictionBundleID = [[NSBundle mainBundle] bundleIdentifier]; -} - -- (NSMutableURLRequest *)requestForURL:(NSURL *)url - ETag:(NSString *)etag - httpMethod:(NSString *)httpMethod - ticket:(GTLRServiceTicket *)ticket { - - // subclasses may add headers to this - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url - cachePolicy:NSURLRequestReloadIgnoringCacheData - timeoutInterval:60]; - 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; -} - -// objectRequestForURL returns an NSMutableURLRequest for a GTLRObject -// -// 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:(GTLRObject *)object - contentType:(NSString *)contentType - contentLength:(NSString *)contentLength - ETag:(NSString *)etag - httpMethod:(NSString *)httpMethod - additionalHeaders:(NSDictionary *)additionalHeaders - ticket:(GTLRServiceTicket *)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) { - etag = ETagIfPresent(object); - } - } - - NSMutableURLRequest *request = [self requestForURL:url - ETag:etag - httpMethod:httpMethod - ticket:ticket]; - [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; - - [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"]; - - if (contentLength) { - [request setValue:contentLength forHTTPHeaderField:@"Content-Length"]; - } - - // 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 objectForKey:key]; - [request setValue:value forHTTPHeaderField:key]; - } - - headers = additionalHeaders; - for (NSString *key in headers) { - NSString *value = [headers objectForKey:key]; - [request setValue:value forHTTPHeaderField:key]; - } - - return request; -} - -#pragma mark - - -- (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query { - GTLR_DEBUG_ASSERT(query.bodyObject == nil, - @"requestForQuery: supports only GET methods, but was passed: %@", query); - GTLR_DEBUG_ASSERT(query.uploadParameters == nil, - @"requestForQuery: does not support uploads, but was passed: %@", query); - - NSURL *url = [self URLFromQueryObject:query - usePartialPaths:NO - includeServiceURLQueryParams:YES]; - - // If there is a developer key, add it onto the url. - NSString *apiKey = self.APIKey; - if (apiKey.length > 0) { - NSDictionary *queryParameters; - queryParameters = @{ kDeveloperAPIQueryParamKey : apiKey }; - url = [GTLRService URLWithString:url.absoluteString - queryParameters:queryParameters]; - } - - NSMutableURLRequest *request = [self requestForURL:url - ETag:nil - httpMethod:query.httpMethod - ticket:nil]; - NSString *apiRestriction = self.APIKeyRestrictionBundleID; - if ([apiRestriction length] > 0) { - [request setValue:apiRestriction forHTTPHeaderField:kXIosBundleIdHeader]; - } - - NSDictionary *headers = self.additionalHTTPHeaders; - for (NSString *key in headers) { - NSString *value = [headers objectForKey:key]; - [request setValue:value forHTTPHeaderField:key]; - } - - headers = query.additionalHTTPHeaders; - for (NSString *key in headers) { - NSString *value = [headers objectForKey:key]; - [request setValue:value forHTTPHeaderField:key]; - } - - return request; -} - -// common fetch starting method - -- (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL - objectClass:(Class)objectClass - bodyObject:(GTLRObject *)bodyObject - dataToPost:(NSData *)dataToPost - ETag:(NSString *)etag - httpMethod:(NSString *)httpMethod - mayAuthorize:(BOOL)mayAuthorize - completionHandler:(GTLRServiceCompletionHandler)completionHandler - executingQuery:(id<GTLRQueryProtocol>)executingQuery - ticket:(GTLRServiceTicket *)ticket { - // Once inside this method, we should not access any service properties that may reasonably - // be changed by the app, as this method may execute multiple times during query execution - // and we want consistent behavior. Service properties should be copied to the ticket. - - GTLR_DEBUG_ASSERT(executingQuery != nil, - @"no query? service additionalURLQueryParameters needs to be added to targetURL"); - - GTLR_DEBUG_ASSERT(targetURL != nil, @"no url?"); - if (targetURL == nil) return nil; - - BOOL hasExecutionParams = [executingQuery hasExecutionParameters]; - GTLRServiceExecutionParameters *executionParams = (hasExecutionParams ? - executingQuery.executionParameters : nil); - - // We need to create a ticket unless one was created earlier (like during authentication.) - if (!ticket) { - ticket = [[[[self class] ticketClass] alloc] initWithService:self - executionParameters:executionParams]; - [ticket notifyStarting:YES]; - } - - // If there is a developer key, add it onto the URL. - NSString *apiKey = ticket.APIKey; - if (apiKey.length > 0) { - NSDictionary *queryParameters; - queryParameters = @{ kDeveloperAPIQueryParamKey : apiKey }; - targetURL = [GTLRService URLWithString:targetURL.absoluteString - queryParameters:queryParameters]; - } - - NSString *contentType = @"application/json; charset=utf-8"; - NSString *contentLength; // nil except for single-request uploads. - - if ([executingQuery isBatchQuery]) { - contentType = [NSString stringWithFormat:@"multipart/mixed; boundary=%@", - ((GTLRBatchQuery *)executingQuery).boundary]; - } - - - GTLRUploadParameters *uploadParams = executingQuery.uploadParameters; - - if (uploadParams.shouldUploadWithSingleRequest) { - NSData *uploadData = uploadParams.data; - NSString *uploadMIMEType = uploadParams.MIMEType; - if (!uploadData) { - GTLR_DEBUG_ASSERT(0, @"Uploading with a single request requires bytes to upload as NSData"); - } else { - if (uploadParams.shouldSendUploadOnly) { - contentType = uploadMIMEType; - dataToPost = uploadData; - contentLength = @(dataToPost.length).stringValue; - } else { - GTMMIMEDocument *mimeDoc = [GTMMIMEDocument MIMEDocument]; - if (dataToPost) { - // Include the object as metadata with the upload. - [mimeDoc addPartWithHeaders:@{ @"Content-Type" : contentType } - body:dataToPost]; - } - [mimeDoc addPartWithHeaders:@{ @"Content-Type" : uploadMIMEType } - body:uploadData]; - - dispatch_data_t mimeDispatchData; - unsigned long long mimeLength; - NSString *mimeBoundary; - [mimeDoc generateDispatchData:&mimeDispatchData - length:&mimeLength - boundary:&mimeBoundary]; - - contentType = [NSString stringWithFormat:@"multipart/related; boundary=%@", mimeBoundary]; - dataToPost = (NSData *)mimeDispatchData; - contentLength = @(mimeLength).stringValue; - } - } - } - - NSDictionary *additionalHeaders = nil; - NSString *restriction = self.APIKeyRestrictionBundleID; - if ([restriction length] > 0) { - additionalHeaders = @{ kXIosBundleIdHeader : restriction }; - } - - NSDictionary *queryAdditionalHeaders = executingQuery.additionalHTTPHeaders; - if (queryAdditionalHeaders) { - if (additionalHeaders) { - NSMutableDictionary *builder = [additionalHeaders mutableCopy]; - [builder addEntriesFromDictionary:queryAdditionalHeaders]; - additionalHeaders = builder; - } else { - additionalHeaders = queryAdditionalHeaders; - } - } - - NSURLRequest *request = [self objectRequestForURL:targetURL - object:bodyObject - contentType:contentType - contentLength:contentLength - ETag:etag - httpMethod:httpMethod - additionalHeaders:additionalHeaders - ticket:ticket]; - ticket.postedObject = bodyObject; - ticket.executingQuery = executingQuery; - - GTLRQuery *originalQuery = (GTLRQuery *)ticket.originalQuery; - if (originalQuery == nil) { - originalQuery = (GTLRQuery *)executingQuery; - ticket.originalQuery = originalQuery; - } - - // Some proxy servers (and some web servers) have issues with GET URLs being - // too long, trap that and move the query parameters into the body. The - // uploadParams and dataToPost should be nil for a GET, but playing it safe - // and confirming. - NSString *requestHTTPMethod = request.HTTPMethod; - BOOL isDoingHTTPGet = - (requestHTTPMethod == nil - || [requestHTTPMethod caseInsensitiveCompare:@"GET"] == NSOrderedSame); - if (isDoingHTTPGet && - (request.URL.absoluteString.length >= kMaxGETURLLength) && - (uploadParams == nil) && - (dataToPost == nil)) { - NSString *urlString = request.URL.absoluteString; - NSRange range = [urlString rangeOfString:@"?"]; - if (range.location != NSNotFound) { - NSURL *trimmedURL = [NSURL URLWithString:[urlString substringToIndex:range.location]]; - NSString *urlArgsString = [urlString substringFromIndex:(range.location + 1)]; - if (trimmedURL && (urlArgsString.length > 0)) { - dataToPost = [urlArgsString dataUsingEncoding:NSUTF8StringEncoding]; - NSMutableURLRequest *mutableRequest = [request mutableCopy]; - mutableRequest.URL = trimmedURL; - mutableRequest.HTTPMethod = @"POST"; - [mutableRequest setValue:@"GET" forHTTPHeaderField:@"X-HTTP-Method-Override"]; - [mutableRequest setValue:@"application/x-www-form-urlencoded" - forHTTPHeaderField:@"Content-Type"]; - [mutableRequest setValue:@(dataToPost.length).stringValue - forHTTPHeaderField:@"Content-Length"]; - request = mutableRequest; - } - } - } - ticket.fetchRequest = request; - - GTLRServiceTestBlock testBlock = ticket.testBlock; - if (testBlock) { - [self simulateFetchWithTicket:ticket - testBlock:testBlock - dataToPost:dataToPost - completionHandler:completionHandler]; - return ticket; - } - - GTMSessionFetcherService *fetcherService = ticket.fetcherService; - GTMSessionFetcher *fetcher; - - if (uploadParams == nil || uploadParams.shouldUploadWithSingleRequest) { - // Create a single-request fetcher. - fetcher = [fetcherService fetcherWithRequest:request]; - } else { - fetcher = [self uploadFetcherWithRequest:request - fetcherService:fetcherService - params:uploadParams]; - } - - if (ticket.allowInsecureQueries) { - fetcher.allowLocalhostRequest = YES; - fetcher.allowedInsecureSchemes = @[ @"http" ]; - } - - NSString *loggingName = executingQuery.loggingName; - if (loggingName.length > 0) { - NSUInteger pageNumber = ticket.pagesFetchedCounter + 1; - if (pageNumber > 1) { - loggingName = [loggingName stringByAppendingFormat:@", page %tu", pageNumber]; - } - fetcher.comment = loggingName; - } - - if (!mayAuthorize) { - fetcher.authorizer = nil; - } else { - fetcher.authorizer = ticket.authorizer; - } - - // copy the ticket's retry settings into the fetcher - fetcher.retryEnabled = ticket.retryEnabled; - fetcher.maxRetryInterval = ticket.maxRetryInterval; - - BOOL shouldExamineRetries = (ticket.retryBlock != nil); - if (shouldExamineRetries) { - GTLR_DEBUG_ASSERT(ticket.retryEnabled, @"Setting retry block without retry enabled."); - - fetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *error, - GTMSessionFetcherRetryResponse response) { - // The object fetcher may call into this retry block; this one invokes the - // selector provided by the user. - GTLRServiceRetryBlock retryBlock = ticket.retryBlock; - if (!retryBlock) { - response(suggestedWillRetry); - } else { - dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{ - if (ticket.cancelled) { - response(NO); - return; - } - BOOL willRetry = retryBlock(ticket, suggestedWillRetry, error); - response(willRetry); - }); - } - }; - } - - // Remember the object fetcher in the ticket. - ticket.objectFetcher = fetcher; - - // Set the upload data. - fetcher.bodyData = dataToPost; - - // Have the fetcher call back on the parse queue. - fetcher.callbackQueue = self.parseQueue; - - // If this ticket is paging, end any ongoing background task immediately, and - // rely on the fetcher's background task now instead. - [ticket endBackgroundTask]; - - [fetcher beginFetchWithCompletionHandler:^(NSData * _Nullable data, NSError * _Nullable error) { - // We now have the JSON data for an object, or an error. - GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue); - - // Until now, the only async operation has been the fetch, and we rely on the fetcher's - // background task on iOS to get us here if the app was backgrounded. - // - // Now we'll let the ticket create a background task so that the async parsing and call back to - // the app will happen if the app is sent to the background. The ticket is responsible for - // ending the background task. - [ticket startBackgroundTask]; - - if (ticket.cancelled) { - // If the user cancels the ticket, then cancelTicket will stop the fetcher so this - // callback probably won't occur. - // - // But just for safety, if we get here, skip any parsing steps by fabricating an error. - data = nil; - error = [NSError errorWithDomain:NSURLErrorDomain - code:NSURLErrorCancelled - userInfo:nil]; - } - - if (error == nil) { - // Successful fetch. - if (data.length > 0) { - [self prepareToParseObjectForFetcher:fetcher - executingQuery:executingQuery - ticket:ticket - error:error - defaultClass:objectClass - completionHandler:completionHandler]; - } else { - // no data (such as when deleting) - [self handleParsedObjectForFetcher:fetcher - executingQuery:executingQuery - ticket:ticket - error:nil - parsedObject:nil - hasSentParsingStartNotification:NO - completionHandler:completionHandler]; - } - return; - } - - // Failed 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 *responseContentType = [responseHeaders objectForKey:@"Content-Type"]; - - if (data.length > 0) { - if ([responseContentType hasPrefix:@"application/json"]) { - NSError *parseError = nil; - NSMutableDictionary *jsonWrapper = - [NSJSONSerialization JSONObjectWithData:(NSData * _Nonnull)data - options:NSJSONReadingMutableContainers - error:&parseError]; - if (parseError) { - // We could not parse the JSON payload - error = parseError; - } else { - // HTTP Streaming defined by Google services is is an array - // of requests and replies. This code never makes one of - // these requests; but, some GET apis can actually be to - // a Streaming result (for media?), so the errors can still - // come back in an array. - if ([jsonWrapper isKindOfClass:[NSArray class]]) { - NSArray *jsonWrapperAsArray = (NSArray *)jsonWrapper; -#if DEBUG - if (jsonWrapperAsArray.count > 1) { - GTLR_DEBUG_LOG(@"Got error array with >1 item, only using first. Full list: %@", - jsonWrapperAsArray); - } -#endif - // Use the first. - jsonWrapper = [jsonWrapperAsArray firstObject]; - } - - // Convert the JSON error payload into a structured error - NSMutableDictionary *errorJSON = [jsonWrapper valueForKey:@"error"]; - if (errorJSON) { - GTLRErrorObject *errorObject = [GTLRErrorObject 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:(NSData * _Nonnull)data - encoding:NSUTF8StringEncoding]; - NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : reasonStr }; - error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain - code:status - userInfo:userInfo]; - } - } else { - // Response data length is zero; we'll settle for returning the - // fetcher's error. - } - } - } - - [self handleParsedObjectForFetcher:fetcher - executingQuery:executingQuery - ticket:ticket - error:error - parsedObject:nil - hasSentParsingStartNotification:NO - completionHandler:completionHandler]; - }]; // fetcher completion handler - - // 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 (ticket.hasCalledCallback) { - return nil; - } - - return ticket; -} - -- (GTMSessionUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request - fetcherService:(GTMSessionFetcherService *)fetcherService - params:(GTLRUploadParameters *)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; - } - - NSString *uploadClassName = GTLR_CLASSNAME_STR(GTMSessionUploadFetcher); - Class uploadClass = NSClassFromString(uploadClassName); - GTLR_ASSERT(uploadClass != nil, @"GTMSessionUploadFetcher needed"); - - NSString *uploadMIMEType = uploadParams.MIMEType; - NSData *uploadData = uploadParams.data; - NSURL *uploadFileURL = uploadParams.fileURL; - NSFileHandle *uploadFileHandle = uploadParams.fileHandle; - NSURL *uploadLocationURL = uploadParams.uploadLocationURL; - - // Create the upload fetcher. - GTMSessionUploadFetcher *fetcher; - if (uploadLocationURL) { - // Resuming with the session fetcher and a file URL. - GTLR_DEBUG_ASSERT(uploadFileURL != nil, @"Resume requires a file URL"); - fetcher = [uploadClass uploadFetcherWithLocation:uploadLocationURL - uploadMIMEType:uploadMIMEType - chunkSize:(int64_t)uploadChunkSize - fetcherService:fetcherService]; - fetcher.uploadFileURL = uploadFileURL; - } else { - fetcher = [uploadClass uploadFetcherWithRequest:request - uploadMIMEType:uploadMIMEType - chunkSize:(int64_t)uploadChunkSize - fetcherService:fetcherService]; - if (uploadFileURL) { - fetcher.uploadFileURL = uploadFileURL; - } else if (uploadData) { - fetcher.uploadData = uploadData; - } else if (uploadFileHandle) { -#if DEBUG - if (uploadParams.useBackgroundSession) { - GTLR_DEBUG_LOG(@"Warning: GTLRUploadParameters should be supplied an uploadFileURL rather" - @" than a file handle to support background uploads.\n %@", uploadParams); - } -#endif - fetcher.uploadFileHandle = uploadFileHandle; - } - } - fetcher.useBackgroundSession = uploadParams.useBackgroundSession; - return fetcher; -} - -#pragma mark - - -- (GTLRServiceTicket *)executeBatchQuery:(GTLRBatchQuery *)batchObj - completionHandler:(GTLRServiceCompletionHandler)completionHandler - ticket:(GTLRServiceTicket *)ticket { - // Copy the original batch object and each query inside so our working queries cannot be modified - // by the caller, and release the callback blocks from the supplied query objects. - GTLRBatchQuery *batchCopy = [batchObj copy]; - [batchObj invalidateQuery]; - - NSArray *queries = batchCopy.queries; - NSUInteger numberOfQueries = queries.count; - if (numberOfQueries == 0) return nil; - - // Create the batch of REST calls. - NSMutableSet *requestIDs = [NSMutableSet setWithCapacity:numberOfQueries]; - NSMutableSet *loggingNames = [NSMutableSet set]; - GTMMIMEDocument *mimeDoc = [GTMMIMEDocument MIMEDocument]; - - // Each batch part has two "header" sections, an outer and inner. - // The inner headers are preceded by a line specifying the http request. - // So a part looks like this: - // - // --END_OF_PART - // Content-ID: gtlr_3 - // Content-Transfer-Encoding: binary - // Content-Type: application/http - // - // POST https://www.googleapis.com/drive/v3/files/ - // Content-Length: 0 - // Content-Type: application/json - // - // { - // "id": "04109509152946699072k" - // } - - for (GTLRQuery *query in queries) { - GTLRObject *bodyObject = query.bodyObject; - NSDictionary *bodyJSON = bodyObject.JSON; - NSString *requestID = query.requestID; - - if (requestID.length == 0) { - GTLR_DEBUG_ASSERT(0, @"Invalid query ID: %@", [query class]); - return nil; - } - - if ([requestIDs containsObject:requestID]) { - GTLR_DEBUG_ASSERT(0, @"Duplicate request ID in batch: %@", requestID); - return nil; - } - [requestIDs addObject:requestID]; - - // Create the inner request, body, and headers. - - NSURL *requestURL = [self URLFromQueryObject:query - usePartialPaths:YES - includeServiceURLQueryParams:NO]; - NSString *requestURLString = requestURL.absoluteString; - - NSError *error = nil; - NSData *bodyData; - if (bodyJSON) { - bodyData = [NSJSONSerialization dataWithJSONObject:bodyJSON - options:0 - error:&error]; - if (bodyData == nil) { - GTLR_DEBUG_ASSERT(0, @"JSON generation error: %@\n JSON: %@", error, bodyJSON); - return nil; - } - } - - NSString *httpRequestString = [NSString stringWithFormat:@"%@ %@\r\n", - query.httpMethod ?: @"GET", requestURLString]; - NSDictionary *innerPartHeaders = @{ @"Content-Type" : @"application/json", - @"Content-Length" : @(bodyData.length).stringValue }; - - innerPartHeaders = MergeDictionaries(query.additionalHTTPHeaders, innerPartHeaders); - - NSData *innerPartHeadersData = [GTMMIMEDocument dataWithHeaders:innerPartHeaders]; - - NSMutableData *innerData = - [[httpRequestString dataUsingEncoding:NSUTF8StringEncoding] mutableCopy]; - [innerData appendData:innerPartHeadersData]; - if (bodyData) { - [innerData appendData:bodyData]; - } - - // Combine the outer headers with the inner headers and body data. - NSDictionary *outerPartHeaders = @{ @"Content-Type" : @"application/http", - @"Content-ID" : requestID, - @"Content-Transfer-Encoding" : @"binary" }; - - [mimeDoc addPartWithHeaders:outerPartHeaders - body:innerData]; - - NSString *loggingName = query.loggingName ?: [[query class] description]; - [loggingNames addObject:loggingName]; - } - -#if !STRIP_GTM_FETCH_LOGGING - // Set the fetcher log comment. - if (!batchCopy.loggingName) { - NSUInteger pageNumber = ticket.pagesFetchedCounter; - NSString *pageStr = @""; - if (pageNumber > 0) { - pageStr = [NSString stringWithFormat:@"page %tu, ", pageNumber + 1]; - } - batchCopy.loggingName = [NSString stringWithFormat:@"batch: %@ (%@%tu queries)", - [loggingNames.allObjects componentsJoinedByString:@", "], - pageStr, numberOfQueries]; - } -#endif - - dispatch_data_t mimeDispatchData; - unsigned long long mimeLength; - NSString *mimeBoundary; - [mimeDoc generateDispatchData:&mimeDispatchData - length:&mimeLength - boundary:&mimeBoundary]; - - batchCopy.boundary = mimeBoundary; - - BOOL mayAuthorize = (batchCopy ? !batchCopy.shouldSkipAuthorization : YES); - - NSString *rootURLString = self.rootURLString; - NSString *batchPath = self.batchPath ?: @""; - NSString *batchURLString = [rootURLString stringByAppendingString:batchPath]; - - GTLR_DEBUG_ASSERT(![batchPath hasPrefix:@"/"], - @"batchPath shouldn't start with a slash: %@", - batchPath); - - // Query parameters override service parameters. - NSDictionary *mergedQueryParams = MergeDictionaries(self.additionalURLQueryParameters, - batchObj.additionalURLQueryParameters); - NSURL *batchURL; - if (mergedQueryParams.count > 0) { - batchURL = [GTLRService URLWithString:batchURLString - queryParameters:mergedQueryParams]; - } else { - batchURL = [NSURL URLWithString:batchURLString]; - } - - GTLRServiceTicket *resultTicket = [self fetchObjectWithURL:batchURL - objectClass:[GTLRBatchResult class] - bodyObject:nil - dataToPost:(NSData *)mimeDispatchData - ETag:nil - httpMethod:@"POST" - mayAuthorize:mayAuthorize - completionHandler:completionHandler - executingQuery:batchCopy - ticket:ticket]; - return resultTicket; -} - -#pragma mark - - -// Raw REST fetch method. - -- (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL - objectClass:(Class)objectClass - bodyObject:(GTLRObject *)bodyObject - ETag:(NSString *)etag - httpMethod:(NSString *)httpMethod - mayAuthorize:(BOOL)mayAuthorize - completionHandler:(GTLRServiceCompletionHandler)completionHandler - executingQuery:(id<GTLRQueryProtocol>)executingQuery - ticket:(GTLRServiceTicket *)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 && !executingQuery.uploadParameters.shouldSendUploadOnly) { - NSError *error = nil; - - NSDictionary *whatToSend; - NSDictionary *json = bodyObject.JSON; - if (json == nil) { - // Since a body object was provided, we'll ensure there's at least an empty dictionary. - json = [NSDictionary dictionary]; - } - if (_dataWrapperRequired) { - // create the top-level "data" object - whatToSend = @{ @"data" : json }; - } else { - whatToSend = json; - } - dataToPost = [NSJSONSerialization dataWithJSONObject:whatToSend - options:0 - error:&error]; - if (dataToPost == nil) { - GTLR_DEBUG_LOG(@"JSON generation error: %@", error); - } - } - - return [self fetchObjectWithURL:targetURL - objectClass:objectClass - bodyObject:bodyObject - dataToPost:dataToPost - ETag:etag - httpMethod:httpMethod - mayAuthorize:mayAuthorize - completionHandler:completionHandler - executingQuery:executingQuery - ticket:ticket]; -} - -- (void)invokeProgressCallbackForTicket:(GTLRServiceTicket *)ticket - deliveredBytes:(unsigned long long)numReadSoFar - totalBytes:(unsigned long long)total { - - GTLRServiceUploadProgressBlock block = ticket.uploadProgressBlock; - if (block) { - dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{ - if (ticket.cancelled) return; - - block(ticket, numReadSoFar, total); - }); - } -} - -// 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:(GTMSessionFetcher *)fetcher - executingQuery:(id<GTLRQueryProtocol>)executingQuery - ticket:(GTLRServiceTicket *)ticket - error:(NSError *)error - defaultClass:(Class)defaultClass - completionHandler:(GTLRServiceCompletionHandler)completionHandler { - GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue); - - [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification - object:ticket - userInfo:nil]; - - // For unit tests to cancel during parsing, we need a synchronous notification posted. - // Because this notification is intended only for unit tests, there is no public symbol - // for the notification name. - NSNotificationCenter *nc =[NSNotificationCenter defaultCenter]; - [nc postNotificationName:@"kGTLRServiceTicketParsingStartedForTestNotification" - object:ticket - userInfo:nil]; - - NSDictionary *batchClassMap; - if ([executingQuery isBatchQuery]) { - // build a dictionary of expected classes for the batch responses - GTLRBatchQuery *batchQuery = (GTLRBatchQuery *)executingQuery; - NSArray *queries = batchQuery.queries; - batchClassMap = [NSMutableDictionary dictionaryWithCapacity:queries.count]; - for (GTLRQuery *singleQuery in queries) { - [batchClassMap setValue:singleQuery.expectedObjectClass - forKey:singleQuery.requestID]; - } - } - - [self parseObjectFromDataOfFetcher:fetcher - executingQuery:executingQuery - ticket:ticket - error:error - defaultClass:defaultClass - batchClassMap:batchClassMap - hasSentParsingStartNotification:YES - completionHandler:completionHandler]; -} - -- (void)parseObjectFromDataOfFetcher:(GTMSessionFetcher *)fetcher - executingQuery:(id<GTLRQueryProtocol>)executingQuery - ticket:(GTLRServiceTicket *)ticket - error:(NSError *)error - defaultClass:(Class)defaultClass - batchClassMap:(NSDictionary *)batchClassMap - hasSentParsingStartNotification:(BOOL)hasSentParsingStartNotification - completionHandler:(GTLRServiceCompletionHandler)completionHandler { - GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue); - - NSError *fetchError = error; - - NSString *downloadAsDataObjectType = nil; - if (![executingQuery isBatchQuery]) { - GTLRQuery *singleQuery = (GTLRQuery *)executingQuery; - downloadAsDataObjectType = singleQuery.downloadAsDataObjectType; - } - - NSDictionary *responseHeaders = fetcher.responseHeaders; - NSString *contentType = [responseHeaders objectForKey:@"Content-Type"]; - NSData *data = fetcher.downloadedData; - BOOL hasData = data.length > 0; - BOOL isJSON = [contentType hasPrefix:@"application/json"]; - GTLRObject *parsedObject; - - if (hasData) { -#if GTLR_LOG_PERFORMANCE - NSTimeInterval secs1, secs2; - secs1 = [NSDate timeIntervalSinceReferenceDate]; -#endif - id<GTLRObjectClassResolver> objectClassResolver = ticket.objectClassResolver; - - if ((downloadAsDataObjectType.length != 0) && fetchError == nil) { - GTLRDataObject *dataObject = [GTLRDataObject object]; - dataObject.data = data; - dataObject.contentType = contentType; - parsedObject = dataObject; - } else if (isJSON) { - NSError *parseError = nil; - NSMutableDictionary *jsonWrapper = - [NSJSONSerialization JSONObjectWithData:data - options:NSJSONReadingMutableContainers - error:&parseError]; - if (jsonWrapper == nil) { - fetchError = parseError; - } else { - NSMutableDictionary *json; - - if (_dataWrapperRequired) { - json = [jsonWrapper valueForKey:@"data"]; - } else { - json = jsonWrapper; - } - - if (json != nil) { - parsedObject = [GTLRObject objectForJSON:json - defaultClass:defaultClass - objectClassResolver:objectClassResolver]; - } - } - } else { - // Has non-JSON data; it may be batch data. - NSString *boundary; - BOOL isBatchResponse = [self isContentTypeMultipart:contentType - boundary:&boundary]; - if (isBatchResponse) { - NSArray *mimeParts = [GTMMIMEDocument MIMEPartsWithBoundary:boundary - data:data]; - NSArray *responseParts = [self responsePartsWithMIMEParts:mimeParts]; - GTLRBatchResult *batchResult = [self batchResultWithResponseParts:responseParts - batchClassMap:batchClassMap - objectClassResolver:objectClassResolver]; - parsedObject = batchResult; - } else { - GTLR_DEBUG_ASSERT(0, @"Got unexpected content type '%@'", contentType); - } - } // isJSON - -#if GTLR_LOG_PERFORMANCE - secs2 = [NSDate timeIntervalSinceReferenceDate]; - NSLog(@"allocation of %@ took %f seconds", objectClass, secs2 - secs1); -#endif - } - - [self handleParsedObjectForFetcher:fetcher - executingQuery:executingQuery - ticket:ticket - error:fetchError - parsedObject:parsedObject - hasSentParsingStartNotification:hasSentParsingStartNotification - completionHandler:completionHandler]; -} - -- (void)handleParsedObjectForFetcher:(GTMSessionFetcher *)fetcher - executingQuery:(id<GTLRQueryProtocol>)executingQuery - ticket:(GTLRServiceTicket *)ticket - error:(NSError *)error - parsedObject:(GTLRObject *)object - hasSentParsingStartNotification:(BOOL)hasSentParsingStartNotification - completionHandler:(GTLRServiceCompletionHandler)completionHandler { - GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue); - - BOOL isResourceURLQuery = [executingQuery isKindOfClass:[GTLRResourceURLQuery class]]; - - // There may not be an object due to a fetch or parsing error - BOOL shouldFetchNextPages = ticket.shouldFetchNextPages && !isResourceURLQuery; - GTLRObject *previousObject = ticket.fetchedObject; - BOOL isFirstPage = (previousObject == nil); - - if (shouldFetchNextPages && !isFirstPage && (object != nil)) { - // Accumulate new results - object = [self mergedNewResultObject:object - oldResultObject:previousObject - forQuery:executingQuery - ticket:ticket]; - } - - ticket.fetchedObject = object; - ticket.fetchError = error; - - if (hasSentParsingStartNotification) { - // we want to always balance the start and stop notifications - [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification - object:ticket - userInfo:nil]; - } - - BOOL shouldCallCallbacks = YES; - - if (error == nil) { - ++ticket.pagesFetchedCounter; - - // Use the nextPageToken to fetch any later pages for non-batch queries - // - // This assumes a pagination model where objects have entries in a known "items" - // field and a "nextPageToken" field, and queries support a "pageToken" - // parameter. - - if (shouldFetchNextPages) { - // Determine if we should fetch more pages of results - - GTLRQuery *nextPageQuery = - (GTLRQuery *)[self nextPageQueryForQuery:executingQuery - result:object - ticket:ticket]; - if (nextPageQuery) { - BOOL isFetchingMore = [self fetchNextPageWithQuery:nextPageQuery - completionHandler:completionHandler - ticket:ticket]; - if (isFetchingMore) { - shouldCallCallbacks = NO; - } - } else { - // nextPageQuery == nil; no more page tokens are present - #if DEBUG && !GTLR_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; - if ([executingQuery isBatchQuery]) { - queryLabel = @"batch query"; - } else { - queryLabel = [[executingQuery class] description]; - } - GTLR_DEBUG_LOG(@"Executing %@ query required fetching %tu pages; use a query with" - @" a larger maxResults for faster results", queryLabel, pageCount); - } - #endif - } // nextPageQuery - } else { - // !ticket.shouldFetchNextPages - #if DEBUG && !GTLR_SKIP_PAGES_WARNING - // Let the developer know that there were additional pages that would have been - // fetched if shouldFetchNextPages was enabled. - // - // The client may specify a larger page size with the query's maxResults property, - // or enable automatic pagination by turning on shouldFetchNextPages on the service - // or on the query's executionParameters. - if ([executingQuery respondsToSelector:@selector(pageToken)] - && [object isKindOfClass:[GTLRCollectionObject class]] - && [object respondsToSelector:@selector(nextPageToken)] - && object.nextPageToken.length > 0) { - GTLR_DEBUG_LOG(@"Executing %@ has additional pages of results not fetched because" - @" shouldFetchNextPages is not enabled", [executingQuery class]); - } - #endif - } // ticket.shouldFetchNextPages - } // error == nil - - if (!isFirstPage) { - // Release callbacks from this completed page's query. - [executingQuery invalidateQuery]; - } - - // 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) { - // More fetches are happening. - } else { - dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{ - // 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. - GTLRQuery *originalQuery = (GTLRQuery *)ticket.originalQuery; - - if (!ticket.cancelled) { - if (![originalQuery isBatchQuery]) { - // Single query - GTLRServiceCompletionHandler completionBlock = originalQuery.completionBlock; - if (completionBlock) { - completionBlock(ticket, object, error); - } - } else { - [self invokeBatchCompletionsWithTicket:ticket - batchQuery:(GTLRBatchQuery *)originalQuery - batchResult:(GTLRBatchResult *)object - error:error]; - } - - if (completionHandler) { - completionHandler(ticket, object, error); - } - ticket.hasCalledCallback = YES; - } // !ticket.cancelled - - [ticket releaseTicketCallbacks]; - [ticket endBackgroundTask]; - - // Even if the ticket has been cancelled, it should notify that it's stopped. - [ticket notifyStarting:NO]; - - // Release query callback blocks. - [originalQuery invalidateQuery]; - }); - } -} - -- (BOOL)isContentTypeMultipart:(NSString *)contentType - boundary:(NSString **)outBoundary { - NSScanner *scanner = [NSScanner scannerWithString:contentType]; - // By default, the scanner skips leading whitespace. - if ([scanner scanString:@"multipart/mixed; boundary=" intoString:NULL] - && [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] - intoString:outBoundary]) { - return YES; - } - return NO; -} - -- (NSArray <GTLRBatchResponsePart *>*)responsePartsWithMIMEParts:(NSArray <GTMMIMEDocumentPart *>*)mimeParts { - NSMutableArray *resultParts = [NSMutableArray arrayWithCapacity:mimeParts.count]; - - for (GTMMIMEDocumentPart *mimePart in mimeParts) { - GTLRBatchResponsePart *responsePart = [self responsePartWithMIMEPart:mimePart]; - [resultParts addObject:responsePart]; - } - return resultParts; -} - -- (GTLRBatchResponsePart *)responsePartWithMIMEPart:(GTMMIMEDocumentPart *)mimePart { - // The MIME part body looks like - // - // Headers (from the MIME part): - // Content-Type: application/http - // Content-ID: response-gtlr_5 - // - // Body (including inner headers): - // HTTP/1.1 200 OK - // Content-Type: application/json; charset=UTF-8 - // Date: Sat, 16 Jan 2016 18:57:05 GMT - // Expires: Sat, 16 Jan 2016 18:57:05 GMT - // Cache-Control: private, max-age=0 - // Content-Length: 13459 - // - // {"kind":"drive#fileList", ...} - - GTLRBatchResponsePart *responsePart = [[GTLRBatchResponsePart alloc] init]; - - // The only header in the actual (outer) MIME multipart headers we want is Content-ID. - // - // The content ID in the response looks like - // - // Content-ID: response-gtlr_5 - // - // but we will strip the "response-" prefix. - NSDictionary *mimeHeaders = mimePart.headers; - NSString *responseContentID = mimeHeaders[@"Content-ID"]; - if ([responseContentID hasPrefix:@"response-"]) { - responseContentID = [responseContentID substringFromIndex:@"response-".length]; - } - responsePart.contentID = responseContentID; - - // Split the body from the inner headers at the first CRLFCRLF. - NSArray <NSNumber *>*offsets; - NSData *mimePartBody = mimePart.body; - [GTMMIMEDocument searchData:mimePartBody - targetBytes:"\r\n\r\n" - targetLength:4 - foundOffsets:&offsets]; - if (offsets.count == 0) { - // Parse error. - NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setValue:mimePartBody forKey:kGTLRServiceErrorBodyDataKey]; - [userInfo setValue:responseContentID forKey:kGTLRServiceErrorContentIDKey]; - responsePart.parseError = [NSError errorWithDomain:kGTLRServiceErrorDomain - code:GTLRServiceErrorBatchResponseUnexpected - userInfo:userInfo]; - } else { - // Separate the status/inner headers and the actual body. - NSUInteger partBodyLength = mimePartBody.length; - NSUInteger separatorOffset = offsets[0].unsignedIntegerValue; - NSData *innerHeaderData = - [mimePartBody subdataWithRange:NSMakeRange(0, (NSUInteger)separatorOffset)]; - - NSData *partBodyData; - if (separatorOffset + 4 < partBodyLength) { - NSUInteger offsetToBodyData = separatorOffset + 4; - NSUInteger bodyLength = mimePartBody.length - offsetToBodyData; - partBodyData = [mimePartBody subdataWithRange:NSMakeRange(offsetToBodyData, bodyLength)]; - } - - // Parse to separate the status line and the inner headers (though we don't - // really do much with either.) - [GTMMIMEDocument searchData:innerHeaderData - targetBytes:"\r\n" - targetLength:2 - foundOffsets:&offsets]; - if (offsets.count < 2) { - // Lack of status line and inner headers is strange, but not fatal since - // if the JSON was delivered. - GTLR_DEBUG_LOG(@"GTLRService: Batch result cannot parse headers for request %@:\n%@", - responseContentID, - [[NSString alloc] initWithData:innerHeaderData - encoding:NSUTF8StringEncoding]); - } else { - NSString *statusString; - NSInteger statusCode; - [self getResponseLineFromData:innerHeaderData - statusCode:&statusCode - statusString:&statusString]; - responsePart.statusCode = statusCode; - responsePart.statusString = statusString; - - NSUInteger actualInnerHeaderOffset = offsets[0].unsignedIntegerValue + 2; - NSData *actualInnerHeaderData; - if (innerHeaderData.length - actualInnerHeaderOffset > 0) { - NSRange actualInnerHeaderRange = - NSMakeRange(actualInnerHeaderOffset, - innerHeaderData.length - actualInnerHeaderOffset); - actualInnerHeaderData = [innerHeaderData subdataWithRange:actualInnerHeaderRange]; - } - responsePart.headers = [GTMMIMEDocument headersWithData:actualInnerHeaderData]; - } - - // Create JSON from the body. - NSError *parseError = nil; - NSMutableDictionary *json; - if (partBodyData) { - json = [NSJSONSerialization JSONObjectWithData:partBodyData - options:NSJSONReadingMutableContainers - error:&parseError]; - } else { - parseError = [NSError errorWithDomain:kGTLRServiceErrorDomain - code:GTLRServiceErrorBatchResponseUnexpected - userInfo:nil]; - } - responsePart.JSON = json; - - if (!json) { - // Add our content ID and part body data to the parse error. - NSMutableDictionary *userInfo = - [NSMutableDictionary dictionaryWithDictionary:parseError.userInfo]; - [userInfo setValue:mimePartBody forKey:kGTLRServiceErrorBodyDataKey]; - [userInfo setValue:responseContentID forKey:kGTLRServiceErrorContentIDKey]; - responsePart.parseError = [NSError errorWithDomain:parseError.domain - code:parseError.code - userInfo:userInfo]; - } - } - return responsePart; -} - -- (void)getResponseLineFromData:(NSData *)data - statusCode:(NSInteger *)outStatusCode - statusString:(NSString **)outStatusString { - // Sample response line: - // HTTP/1.1 200 OK - - *outStatusCode = -1; - *outStatusString = @"???"; - NSString *responseLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (!responseLine) return; - - NSScanner *scanner = [NSScanner scannerWithString:responseLine]; - // Scanner by default skips whitespace when locating the start of the next characters to - // scan. - NSCharacterSet *wsSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - NSCharacterSet *newlineSet = [NSCharacterSet newlineCharacterSet]; - NSString *httpVersion; - if ([scanner scanUpToCharactersFromSet:wsSet intoString:&httpVersion] - && [scanner scanInteger:outStatusCode] - && [scanner scanUpToCharactersFromSet:newlineSet intoString:outStatusString]) { - // Got it all. - } -} - -- (GTLRBatchResult *)batchResultWithResponseParts:(NSArray <GTLRBatchResponsePart *>*)parts - batchClassMap:(NSDictionary *)batchClassMap - objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver { - // Allow the resolver to override the batch rules class also. - Class resultClass = - GTLRObjectResolveClass(objectClassResolver, - [NSDictionary dictionary], - [GTLRBatchResult class]); - GTLRBatchResult *batchResult = [resultClass object]; - - NSMutableDictionary *successes = [NSMutableDictionary dictionary]; - NSMutableDictionary *failures = [NSMutableDictionary dictionary]; - NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionary]; - - for (GTLRBatchResponsePart *responsePart in parts) { - NSString *contentID = responsePart.contentID; - NSDictionary *json = responsePart.JSON; - NSError *parseError = responsePart.parseError; - NSInteger statusCode = responsePart.statusCode; - [responseHeaders setValue:responsePart.headers forKey:contentID]; - - if (parseError) { - GTLRErrorObject *parseErrorObject = [GTLRErrorObject objectWithFoundationError:parseError]; - [failures setValue:parseErrorObject forKey:contentID]; - } else { - // There is JSON. - NSMutableDictionary *errorJSON = [json objectForKey:@"error"]; - if (errorJSON) { - // A JSON error body should be the most informative error. - GTLRErrorObject *errorObject = [GTLRErrorObject objectWithJSON:errorJSON]; - [failures setValue:errorObject forKey:contentID]; - } else if (statusCode < 200 || statusCode > 399) { - // Report a fetch failure for this part that lacks a JSON error. - NSString *errorStr = responsePart.statusString; - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey : (errorStr ?: @"<unknown>"), - }; - NSError *httpError = [NSError errorWithDomain:kGTLRServiceErrorDomain - code:GTLRServiceErrorBatchResponseStatusCode - userInfo:userInfo]; - GTLRErrorObject *httpErrorObject = [GTLRErrorObject objectWithFoundationError:httpError]; - [failures setValue:httpErrorObject forKey:contentID]; - } else { - // The JSON represents a successful response. - Class defaultClass = batchClassMap[contentID]; - id resultObject = [GTLRObject objectForJSON:[json mutableCopy] - defaultClass:defaultClass - objectClassResolver:objectClassResolver]; - if (resultObject == nil) { - // Methods like delete return no object. - resultObject = [NSNull null]; - } - [successes setValue:resultObject forKey:contentID]; - } // errorJSON - } // parseError - } // for - batchResult.successes = successes; - batchResult.failures = failures; - batchResult.responseHeaders = responseHeaders; - return batchResult; -} - -- (void)invokeBatchCompletionsWithTicket:(GTLRServiceTicket *)ticket - batchQuery:(GTLRBatchQuery *)batchQuery - batchResult:(GTLRBatchResult *)batchResult - error:(NSError *)error { - // Batch query - // - // We'll step through the queries of the original batch, not of the - // batch result - GTLR_ASSERT_CURRENT_QUEUE_DEBUG(ticket.callbackQueue); - - NSDictionary *successes = batchResult.successes; - NSDictionary *failures = batchResult.failures; - - for (GTLRQuery *oneQuery in batchQuery.queries) { - GTLRServiceCompletionHandler completionBlock = oneQuery.completionBlock; - if (completionBlock) { - // If there was no networking error, look for a query-specific - // error or result - GTLRObject *oneResult = nil; - NSError *oneError = error; - if (oneError == nil) { - NSString *requestID = [oneQuery requestID]; - GTLRErrorObject *gtlrError = [failures objectForKey:requestID]; - if (gtlrError) { - oneError = [gtlrError foundationError]; - } else { - oneResult = [successes objectForKey:requestID]; - if (oneResult == nil) { - // We found neither a success nor a failure for this query, unexpectedly. - GTLR_DEBUG_LOG(@"GTLRService: Batch result missing for request %@", - requestID); - oneError = [NSError errorWithDomain:kGTLRServiceErrorDomain - code:GTLRServiceErrorQueryResultMissing - userInfo:nil]; - } - } - } - completionBlock(ticket, oneResult, oneError); - } - } -} - -- (void)simulateFetchWithTicket:(GTLRServiceTicket *)ticket - testBlock:(GTLRServiceTestBlock)testBlock - dataToPost:(NSData *)dataToPost - completionHandler:(GTLRServiceCompletionHandler)completionHandler { - - GTLRQuery *originalQuery = (GTLRQuery *)ticket.originalQuery; - ticket.executingQuery = originalQuery; - - testBlock(ticket, ^(id testObject, NSError *testError) { - dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{ - if (!ticket.cancelled) { - if (testError) { - // During simulation, we invoke any retry block, but ignore the result. - const BOOL willRetry = NO; - GTLRServiceRetryBlock retryBlock = ticket.retryBlock; - if (retryBlock) { - (void)retryBlock(ticket, willRetry, testError); - } - } else { - // Simulate upload progress, calling back up to three times. - if (ticket.uploadProgressBlock) { - GTLRQuery *query = (GTLRQuery *)ticket.originalQuery; - unsigned long long uploadLength = [self simulatedUploadLengthForQuery:query - dataToPost:dataToPost]; - unsigned long long sendReportSize = uploadLength / 3 + 1; - unsigned long long totalSentSoFar = 0; - while (totalSentSoFar < uploadLength) { - unsigned long long bytesRemaining = uploadLength - totalSentSoFar; - sendReportSize = MIN(sendReportSize, bytesRemaining); - totalSentSoFar += sendReportSize; - - [self invokeProgressCallbackForTicket:ticket - deliveredBytes:(unsigned long long)totalSentSoFar - totalBytes:(unsigned long long)uploadLength]; - } - [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification - object:ticket - userInfo:nil]; - [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification - object:ticket - userInfo:nil]; - } - } - - if (![originalQuery isBatchQuery]) { - // Single query - GTLRServiceCompletionHandler completionBlock = originalQuery.completionBlock; - if (completionBlock) { - completionBlock(ticket, testObject, testError); - } - } else { - // Batch query - GTLR_DEBUG_ASSERT(!testObject || [testObject isKindOfClass:[GTLRBatchResult class]], - @"Batch queries should have result objects of type GTLRBatchResult (not %@)", - [testObject class]); - - [self invokeBatchCompletionsWithTicket:ticket - batchQuery:(GTLRBatchQuery *)originalQuery - batchResult:(GTLRBatchResult *)testObject - error:testError]; - } // isBatchQuery - - if (completionHandler) { - completionHandler(ticket, testObject, testError); - } - ticket.hasCalledCallback = YES; - } // !ticket.cancelled - - // Even if the ticket has been cancelled, it should notify that it's stopped. - [ticket notifyStarting:NO]; - - // Release query callback blocks. - [originalQuery invalidateQuery]; - }); // dispatch_group_async - }); // testBlock -} - -- (unsigned long long)simulatedUploadLengthForQuery:(GTLRQuery *)query - dataToPost:(NSData *)dataToPost { - // We're uploading the body object and other posted metadata, plus optionally the - // data or file specified in the upload parameters. - unsigned long long uploadLength = dataToPost.length; - - GTLRUploadParameters *uploadParameters = query.uploadParameters; - if (uploadParameters) { - NSData *uploadData = uploadParameters.data; - if (uploadData) { - uploadLength += uploadData.length; - } else { - NSURL *fileURL = uploadParameters.fileURL; - if (fileURL) { - NSError *fileError = nil; - NSNumber *fileSizeNum = nil; - if ([fileURL getResourceValue:&fileSizeNum - forKey:NSURLFileSizeKey - error:&fileError]) { - uploadLength += fileSizeNum.unsignedLongLongValue; - } - } else { - NSFileHandle *fileHandle = uploadParameters.fileHandle; - unsigned long long fileLength = [fileHandle seekToEndOfFile]; - uploadLength += fileLength; - } - } - } - return uploadLength; -} - -#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 <GTLRQueryProtocol>)nextPageQueryForQuery:(id<GTLRQueryProtocol>)query - result:(GTLRObject *)object - ticket:(GTLRServiceTicket *)ticket { - if (![query isBatchQuery]) { - // This is a single query - GTLRQuery *currentPageQuery = (GTLRQuery *)query; - - // Determine if we should fetch more pages of results - GTLRQuery *nextPageQuery = nil; - NSString *nextPageToken = nil; - - if ([object respondsToSelector:@selector(nextPageToken)] - && [currentPageQuery respondsToSelector:@selector(pageToken)]) { - nextPageToken = [object performSelector:@selector(nextPageToken)]; - } - - if (nextPageToken && [object isKindOfClass:[GTLRCollectionObject class]]) { - NSString *itemsKey = [[object class] collectionItemsKey]; - GTLR_DEBUG_ASSERT(itemsKey != nil, @"Missing accumulation items key for %@", [object class]); - - SEL itemsSel = NSSelectorFromString(itemsKey); - if ([object respondsToSelector:itemsSel]) { - // Make a query for the next page, preserving the request ID - nextPageQuery = [currentPageQuery copy]; - nextPageQuery.requestID = currentPageQuery.requestID; - - [nextPageQuery performSelector:@selector(setPageToken:) - withObject:nextPageToken]; - } else { - GTLR_DEBUG_ASSERT(0, @"%@ does not implement its collection items property \"%@\"", - [object class], itemsKey); - } - } - 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 - GTLRBatchResult *batchResult = (GTLRBatchResult *)object; - GTLRBatchQuery *nextPageBatchQuery = nil; - NSDictionary *successes = batchResult.successes; - - for (NSString *requestID in successes) { - GTLRObject *singleObject = [successes objectForKey:requestID]; - GTLRQuery *singleQuery = [ticket queryForRequestID:requestID]; - - GTLRQuery *newQuery = - (GTLRQuery *)[self nextPageQueryForQuery:singleQuery - result:singleObject - ticket:ticket]; - if (newQuery) { - // There is another query to fetch - if (nextPageBatchQuery == nil) { - nextPageBatchQuery = [GTLRBatchQuery 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 -// -// Returns YES if fetching of the next page has started. -- (BOOL)fetchNextPageWithQuery:(GTLRQuery *)query - completionHandler:(GTLRServiceCompletionHandler)handler - ticket:(GTLRServiceTicket *)ticket { - // Sanity check the number of pages fetched already - if (ticket.pagesFetchedCounter > kMaxNumberOfNextPagesFetched) { - // Sanity check failed: way too many pages were fetched, so the query's - // page size should be bigger to avoid driving up networking and server - // overhead. - // - // The client should be querying with a higher max results per page - // to avoid this. - GTLR_DEBUG_ASSERT(0, @"Fetched too many next pages executing %@;" - @" increase maxResults page size to avoid this.", - [query class]); - return NO; - } - - GTLRServiceTicket *newTicket; - if ([query isBatchQuery]) { - newTicket = [self executeBatchQuery:(GTLRBatchQuery *)query - completionHandler:handler - ticket:ticket]; - } else { - BOOL mayAuthorize = !query.shouldSkipAuthorization; - NSURL *url = [self URLFromQueryObject:query - usePartialPaths:NO - includeServiceURLQueryParams:YES]; - newTicket = [self fetchObjectWithURL:url - objectClass:query.expectedObjectClass - bodyObject:query.bodyObject - ETag:nil - httpMethod:query.httpMethod - mayAuthorize:mayAuthorize - completionHandler:handler - 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. - GTLR_ASSERT(newTicket == ticket || newTicket == nil, - @"Pagination should not create an additional ticket: %@", newTicket); - - BOOL isFetchingNextPageWithCurrentTicket = (newTicket == ticket); - return isFetchingNextPageWithCurrentTicket; -} - -// Given a new single or batch result (meaning additional pages for a previous -// query result), merge it into the old result, and return the updated object. -// -// For a single result, this inserts the old result items into the new result. -// For batch results, this replaces some of the old items with new items. -// -// This method changes the objects passed in (the old result for batches, the new result -// for individual objects.) -- (GTLRObject *)mergedNewResultObject:(GTLRObject *)newResult - oldResultObject:(GTLRObject *)oldResult - forQuery:(id<GTLRQueryProtocol>)query - ticket:(GTLRServiceTicket *)ticket { - GTLR_DEBUG_ASSERT([oldResult isMemberOfClass:[newResult class]], - @"Trying to merge %@ and %@", [oldResult class], [newResult class]); - - 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. - GTLRBatchResult *newBatchResult = (GTLRBatchResult *)newResult; - GTLRBatchResult *oldBatchResult = (GTLRBatchResult *)oldResult; - - NSDictionary *newSuccesses = newBatchResult.successes; - if (newSuccesses.count > 0) { - NSDictionary *oldSuccesses = oldBatchResult.successes; - NSMutableDictionary *mutableOldSuccesses = [oldSuccesses mutableCopy]; - - for (NSString *requestID in newSuccesses) { - GTLRObject *newObj = [newSuccesses objectForKey:requestID]; - GTLRObject *oldObj = [oldSuccesses objectForKey:requestID]; - - GTLRQuery *thisQuery = [ticket queryForRequestID:requestID]; - - // Recursively merge the single query's result object, appending new items to the old items. - GTLRObject *updatedObj = [self mergedNewResultObject:newObj - oldResultObject:oldObj - forQuery:thisQuery - ticket:ticket]; - - // In the old batch, replace the old result object with the new one. - [mutableOldSuccesses setObject:updatedObj forKey:requestID]; - } // for requestID - oldBatchResult.successes = mutableOldSuccesses; - } // newSuccesses.count > 0 - - NSDictionary *newFailures = newBatchResult.failures; - if (newFailures.count > 0) { - NSMutableDictionary *mutableOldSuccesses = [oldBatchResult.successes mutableCopy]; - NSMutableDictionary *mutableOldFailures = [oldBatchResult.failures mutableCopy]; - for (NSString *requestID in newFailures) { - // In the old batch, replace old successes or failures with the new failure. - GTLRErrorObject *newError = [newFailures objectForKey:requestID]; - [mutableOldFailures setObject:newError forKey:requestID]; - - [mutableOldSuccesses removeObjectForKey:requestID]; - } - oldBatchResult.failures = mutableOldFailures; - oldBatchResult.successes = mutableOldSuccesses; - } // newFailures.count > 0 - return oldBatchResult; - } else { - // Single query result - // - // Merge the items into the new object, and return the new object. - NSString *itemsKey = [[oldResult class] collectionItemsKey]; - - GTLR_DEBUG_ASSERT([oldResult respondsToSelector:NSSelectorFromString(itemsKey)], - @"Collection items key \"%@\" not implemented by %@", itemsKey, oldResult); - if (itemsKey) { - // Append the new items to the old items. - NSArray *oldItems = [oldResult valueForKey:itemsKey]; - NSArray *newItems = [newResult valueForKey:itemsKey]; - NSMutableArray *items = [NSMutableArray arrayWithArray:oldItems]; - [items addObjectsFromArray:newItems]; - [newResult setValue:items forKey:itemsKey]; - } else { - // This shouldn't happen. - newResult = oldResult; - } - return newResult; - } -} - -#pragma mark - - -// GTLRQuery methods. - -// Helper to create the URL from the parts. -- (NSURL *)URLFromQueryObject:(GTLRQuery *)query - usePartialPaths:(BOOL)usePartialPaths - includeServiceURLQueryParams:(BOOL)includeServiceURLQueryParams { - NSString *rootURLString = self.rootURLString; - - // Skip URI template expansion if the resource URL was provided. - if ([query isKindOfClass:[GTLRResourceURLQuery class]]) { - // Because the query is created by the service rather than by the user, - // query.additionalURLQueryParameters must be nil, and usePartialPaths - // is irrelevant as the query is not in a batch. - GTLR_DEBUG_ASSERT(!usePartialPaths, - @"Batch not supported with resource URL fetch"); - GTLR_DEBUG_ASSERT(!query.uploadParameters && !query.useMediaDownloadService - && !query.downloadAsDataObjectType && !query.additionalURLQueryParameters, - @"Unsupported query properties"); - NSURL *result = ((GTLRResourceURLQuery *)query).resourceURL; - if (includeServiceURLQueryParams) { - NSDictionary *additionalParams = self.additionalURLQueryParameters; - if (additionalParams.count) { - result = [GTLRService URLWithString:result.absoluteString - queryParameters:additionalParams]; - } - } - return result; - } - - // This is all the dance needed due to having query and path parameters for - // REST based queries. - NSDictionary *params = query.JSON; - NSString *queryFilledPathURI = [GTLRURITemplate expandTemplate:query.pathURITemplate - values:params]; - - // Per https://developers.google.com/discovery/v1/using#build-compose and - // https://developers.google.com/discovery/v1/using#discovery-doc-methods-mediadownload - // glue together the parts. - NSString *servicePath = self.servicePath ?: @""; - NSString *uploadPath = @""; - NSString *downloadPath = @""; - - GTLR_DEBUG_ASSERT([rootURLString hasSuffix:@"/"], - @"rootURLString should end in a slash: %@", rootURLString); - GTLR_DEBUG_ASSERT(((servicePath.length == 0) || - (![servicePath hasPrefix:@"/"] && [servicePath hasSuffix:@"/"])), - @"servicePath shouldn't start with a slash but should end with one: %@", - servicePath); - GTLR_DEBUG_ASSERT(![query.pathURITemplate hasPrefix:@"/"], - @"the queries's pathURITemplate should not start with a slash: %@", - query.pathURITemplate); - - GTLRUploadParameters *uploadParameters = query.uploadParameters; - if (uploadParameters != nil) { - // If there is an override, clear all the parts and just use it with the - // the rootURLString. - NSString *override = (uploadParameters.shouldUploadWithSingleRequest - ? query.simpleUploadPathURITemplateOverride - : query.resumableUploadPathURITemplateOverride); - if (override.length > 0) { - GTLR_DEBUG_ASSERT(![override hasPrefix:@"/"], - @"The query's %@UploadPathURITemplateOverride should not start with a slash: %@", - (uploadParameters.shouldUploadWithSingleRequest ? @"simple" : @"resumable"), - override); - queryFilledPathURI = [GTLRURITemplate expandTemplate:override - values:params]; - servicePath = @""; - } else { - if (uploadParameters.shouldUploadWithSingleRequest) { - uploadPath = self.simpleUploadPath ?: @""; - } else { - uploadPath = self.resumableUploadPath ?: @""; - } - GTLR_DEBUG_ASSERT(((uploadPath.length == 0) || - (![uploadPath hasPrefix:@"/"] && - [uploadPath hasSuffix:@"/"])), - @"%@UploadPath shouldn't start with a slash but should end with one: %@", - (uploadParameters.shouldUploadWithSingleRequest ? @"simple" : @"resumable"), - uploadPath); - } - } - - if (query.useMediaDownloadService && - (query.downloadAsDataObjectType.length > 0)) { - downloadPath = @"download/"; - GTLR_DEBUG_ASSERT(uploadPath.length == 0, - @"Uploading while also downloading via mediaDownService" - @" is not well defined."); - } - - if (usePartialPaths) rootURLString = @"/"; - - NSString *urlString = - [NSString stringWithFormat:@"%@%@%@%@%@", - rootURLString, downloadPath, uploadPath, servicePath, queryFilledPathURI]; - - // Remove the path parameters from the dictionary. - NSMutableDictionary *workingQueryParams = [NSMutableDictionary dictionaryWithDictionary:params]; - - NSArray *pathParameterNames = query.pathParameterNames; - if (pathParameterNames.count > 0) { - [workingQueryParams removeObjectsForKeys:pathParameterNames]; - } - - // Note: A developer can override the uploadType and alt query parameters via - // query.additionalURLQueryParameters since those are added afterwards. - if (uploadParameters.shouldUploadWithSingleRequest) { - NSString *uploadType = uploadParameters.shouldSendUploadOnly ? @"media" : @"multipart"; - [workingQueryParams setObject:uploadType forKey:@"uploadType"]; - } - NSString *downloadAsDataObjectType = query.downloadAsDataObjectType; - if (downloadAsDataObjectType.length > 0) { - [workingQueryParams setObject:downloadAsDataObjectType - forKey:@"alt"]; - } - - // Add any parameters the user added directly to the query. - NSDictionary *mergedParams = MergeDictionaries(workingQueryParams, - query.additionalURLQueryParameters); - if (includeServiceURLQueryParams) { - // Query parameters override service parameters. - mergedParams = MergeDictionaries(self.additionalURLQueryParameters, mergedParams); - } - - NSURL *result = [GTLRService URLWithString:urlString - queryParameters:mergedParams]; - return result; -} - -- (GTLRServiceTicket *)executeQuery:(id<GTLRQueryProtocol>)queryObj - delegate:(id)delegate - didFinishSelector:(SEL)finishedSelector { - GTMSessionFetcherAssertValidSelector(delegate, finishedSelector, - @encode(GTLRServiceTicket *), @encode(GTLRObject *), @encode(NSError *), 0); - GTLRServiceCompletionHandler completionHandler = ^(GTLRServiceTicket *ticket, - id object, - NSError *error) { - if (delegate && finishedSelector) { - NSMethodSignature *sig = [delegate methodSignatureForSelector:finishedSelector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; - [invocation setSelector:(SEL)finishedSelector]; - [invocation setTarget:delegate]; - [invocation setArgument:&ticket atIndex:2]; - [invocation setArgument:&object atIndex:3]; - [invocation setArgument:&error atIndex:4]; - [invocation invoke]; - } - }; - return [self executeQuery:queryObj completionHandler:completionHandler]; -} - -- (GTLRServiceTicket *)executeQuery:(id<GTLRQueryProtocol>)queryObj - completionHandler:(void (^)(GTLRServiceTicket *ticket, id object, - NSError *error))handler { - if ([queryObj isBatchQuery]) { - GTLR_DEBUG_ASSERT([queryObj isKindOfClass:[GTLRBatchQuery class]], - @"GTLRBatchQuery required for batches (passed %@)", - [queryObj class]); - return [self executeBatchQuery:(GTLRBatchQuery *)queryObj - completionHandler:handler - ticket:nil]; - } - GTLR_DEBUG_ASSERT([queryObj isKindOfClass:[GTLRQuery class]], - @"GTLRQuery required for single queries (passed %@)", - [queryObj class]); - - // Copy the original query so our working query cannot be modified by the caller, - // and release the callback blocks from the supplied query object. - GTLRQuery *query = [(GTLRQuery *)queryObj copy]; - - GTLR_DEBUG_ASSERT(!query.queryInvalid, @"Query has already been executed: %@", query); - [queryObj invalidateQuery]; - - // For individual queries, we rely on the fetcher's log formatting so pretty-printing - // is not needed. Developers may override this in the query's additionalURLQueryParameters. - NSArray *prettyPrintNames = self.prettyPrintQueryParameterNames; - NSString *firstPrettyPrintName = prettyPrintNames.firstObject; - if (firstPrettyPrintName && (query.downloadAsDataObjectType.length == 0) - && ![query isKindOfClass:[GTLRResourceURLQuery class]]) { - NSDictionary *queryParams = query.additionalURLQueryParameters; - BOOL foundOne = NO; - for (NSString *name in prettyPrintNames) { - if ([queryParams objectForKey:name] != nil) { - foundOne = YES; - break; - } - } - if (!foundOne) { - NSMutableDictionary *worker = - [NSMutableDictionary dictionaryWithDictionary:queryParams]; - [worker setObject:@"false" forKey:firstPrettyPrintName]; - query.additionalURLQueryParameters = worker; - } - } - - BOOL mayAuthorize = !query.shouldSkipAuthorization; - NSURL *url = [self URLFromQueryObject:query - usePartialPaths:NO - includeServiceURLQueryParams:YES]; - - return [self fetchObjectWithURL:url - objectClass:query.expectedObjectClass - bodyObject:query.bodyObject - ETag:nil - httpMethod:query.httpMethod - mayAuthorize:mayAuthorize - completionHandler:handler - executingQuery:query - ticket:nil]; -} - -- (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)resourceURL - objectClass:(nullable Class)objectClass - executionParameters:(nullable GTLRServiceExecutionParameters *)executionParameters - completionHandler:(nullable GTLRServiceCompletionHandler)handler { - GTLRResourceURLQuery *query = [GTLRResourceURLQuery queryWithResourceURL:resourceURL - objectClass:objectClass]; - query.executionParameters = executionParameters; - - return [self executeQuery:query - completionHandler:handler]; -} - -#pragma mark - - -- (NSString *)userAgent { - return _userAgent; -} - -- (void)setExactUserAgent:(NSString *)userAgent { - _userAgent = [userAgent copy]; -} - -- (void)setUserAgent:(NSString *)userAgent { - // remove whitespace and unfriendly characters - NSString *str = GTMFetcherCleanedUserAgentString(userAgent); - [self setExactUserAgent:str]; -} - -- (void)overrideRequestUserAgent:(nullable NSString *)requestUserAgent { - _overrideUserAgent = [requestUserAgent copy]; -} - -#pragma mark - - -+ (NSDictionary<NSString *, Class> *)kindStringToClassMap { - // Generated services will provide custom ones. - return [NSDictionary dictionary]; -} - -#pragma mark - - -// The service properties becomes the initial value for each future ticket's -// properties -- (void)setServiceProperties:(NSDictionary *)dict { - _serviceProperties = [dict copy]; -} - -- (NSDictionary *)serviceProperties { - // be sure the returned pointer has the life of the autorelease pool, - // in case self is released immediately - __autoreleasing id props = _serviceProperties; - return props; -} - -- (void)setAuthorizer:(id <GTMFetcherAuthorizationProtocol>)authorizer { - self.fetcherService.authorizer = authorizer; -} - -- (id <GTMFetcherAuthorizationProtocol>)authorizer { - return self.fetcherService.authorizer; -} - -+ (NSUInteger)defaultServiceUploadChunkSize { - // Subclasses may override this method. - - // The upload server prefers multiples of 256K. - const NSUInteger kMegabyte = 4 * 256 * 1024; - -#if TARGET_OS_IPHONE - // For iOS, we're balancing a large upload size with limiting the memory - // used for the upload data buffer. - return 4 * kMegabyte; -#else - // A large upload chunk size minimizes http overhead and server effort. - return 25 * kMegabyte; -#endif -} - -- (NSUInteger)serviceUploadChunkSize { - if (_uploadChunkSize > 0) { - return _uploadChunkSize; - } - return [[self class] defaultServiceUploadChunkSize]; -} - -- (void)setServiceUploadChunkSize:(NSUInteger)val { - _uploadChunkSize = val; -} - -- (void)setSurrogates:(NSDictionary <Class, Class>*)surrogates { - NSDictionary *kindMap = [[self class] kindStringToClassMap]; - - self.objectClassResolver = [GTLRObjectClassResolver resolverWithKindMap:kindMap - surrogates:surrogates]; -} - -#pragma mark - Internal helper - -// If there are already query parameters on urlString, the new ones are simply -// appended after them. -+ (NSURL *)URLWithString:(NSString *)urlString - queryParameters:(NSDictionary *)queryParameters { - if (urlString.length == 0) return nil; - - NSString *fullURLString; - if (queryParameters.count > 0) { - // Use GTLRURITemplate by building up a template and then feeding in the - // values. The template is query expansion ('?'), and any key that is - // an array or dictionary gets tagged to explode them ('+'). - NSArray *sortedQueryParamKeys = - [queryParameters.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; - - NSMutableString *template = [@"{" mutableCopy]; - char joiner = '?'; - for (NSString *key in sortedQueryParamKeys) { - [template appendFormat:@"%c%@", joiner, key]; - id value = [queryParameters objectForKey:key]; - if ([value isKindOfClass:[NSArray class]] || - [value isKindOfClass:[NSDictionary class]]) { - [template appendString:@"+"]; - } - joiner = ','; - } - [template appendString:@"}"]; - NSString *urlArgs = - [GTLRURITemplate expandTemplate:template - values:queryParameters]; - urlArgs = [urlArgs substringFromIndex:1]; // Drop the '?' and use the joiner. - - BOOL missingQMark = ([urlString rangeOfString:@"?"].location == NSNotFound); - joiner = missingQMark ? '?' : '&'; - fullURLString = - [NSString stringWithFormat:@"%@%c%@", urlString, joiner, urlArgs]; - } else { - fullURLString = urlString; - } - NSURL *result = [NSURL URLWithString:fullURLString]; - return result; -} - -@end - -@implementation GTLRService (TestingSupport) - -+ (instancetype)mockServiceWithFakedObject:(id)objectOrNil - fakedError:(NSError *)errorOrNil { - GTLRService *service = [[GTLRService alloc] init]; - service.rootURLString = @"https://example.invalid/"; - service.testBlock = ^(GTLRServiceTicket *ticket, GTLRServiceTestResponse testResponse) { - testResponse(objectOrNil, errorOrNil); - }; - return service; -} - -- (BOOL)waitForTicket:(GTLRServiceTicket *)ticket - timeout:(NSTimeInterval)timeoutInSeconds { - // Loop until the fetch completes or is cancelled, or until the timeout has expired. - NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; - - BOOL hasTimedOut = NO; - while (1) { - int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms - BOOL areCallbacksPending = - (dispatch_group_wait(ticket.callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta)) != 0); - - if (!areCallbacksPending && (ticket.hasCalledCallback || ticket.cancelled)) break; - - hasTimedOut = (giveUpDate.timeIntervalSinceNow <= 0); - if (hasTimedOut) { - if (areCallbacksPending) { - // A timeout while waiting for the dispatch group to finish is seriously unexpected. - GTLR_DEBUG_LOG(@"%s timed out while waiting for the dispatch group", __PRETTY_FUNCTION__); - } else { - GTLR_DEBUG_LOG(@"%s timed out without callbacks pending", __PRETTY_FUNCTION__); - } - break; - } - - // 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]; - } - return !hasTimedOut; -} - -@end - -@implementation GTLRServiceTicket { - GTLRService *_service; - NSDictionary *_ticketProperties; - GTLRServiceUploadProgressBlock _uploadProgressBlock; - BOOL _needsStopNotification; -} - -@synthesize APIKey = _apiKey, - APIKeyRestrictionBundleID = _apiKeyRestrictionBundleID, - allowInsecureQueries = _allowInsecureQueries, - authorizer = _authorizer, - cancelled = _cancelled, - callbackGroup = _callbackGroup, - callbackQueue = _callbackQueue, - creationDate = _creationDate, - executingQuery = _executingQuery, - fetchedObject = _fetchedObject, - fetchError = _fetchError, - fetchRequest = _fetchRequest, - fetcherService = _fetcherService, - hasCalledCallback = _hasCalledCallback, - maxRetryInterval = _maxRetryInterval, - objectFetcher = _objectFetcher, - originalQuery = _originalQuery, - pagesFetchedCounter = _pagesFetchedCounter, - postedObject = _postedObject, - retryBlock = _retryBlock, - retryEnabled = _retryEnabled, - shouldFetchNextPages = _shouldFetchNextPages, - objectClassResolver = _objectClassResolver, - testBlock = _testBlock; - -#if GTM_BACKGROUND_TASK_FETCHING -@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier; -#endif - -#if DEBUG -- (instancetype)init { - [self doesNotRecognizeSelector:_cmd]; - self = nil; - return self; -} -#endif - -#if GTM_BACKGROUND_TASK_FETCHING && DEBUG -- (void)dealloc { - GTLR_DEBUG_ASSERT(_backgroundTaskIdentifier == UIBackgroundTaskInvalid, - @"Background task not ended"); -} -#endif // GTM_BACKGROUND_TASK_FETCHING && DEBUG - - -- (instancetype)initWithService:(GTLRService *)service - executionParameters:(GTLRServiceExecutionParameters *)params { - self = [super init]; - if (self) { - // ivars set at init time and never changed are exposed as atomic readonly properties. - _service = service; - _fetcherService = service.fetcherService; - _authorizer = service.authorizer; - - _ticketProperties = MergeDictionaries(service.serviceProperties, params.ticketProperties); - - _objectClassResolver = params.objectClassResolver ?: service.objectClassResolver; - - _retryEnabled = ((params.retryEnabled != nil) ? params.retryEnabled.boolValue : service.retryEnabled); - _maxRetryInterval = ((params.maxRetryInterval != nil) ? - params.maxRetryInterval.doubleValue : service.maxRetryInterval); - _shouldFetchNextPages = ((params.shouldFetchNextPages != nil)? - params.shouldFetchNextPages.boolValue : service.shouldFetchNextPages); - - GTLRServiceUploadProgressBlock uploadProgressBlock = - params.uploadProgressBlock ?: service.uploadProgressBlock; - _uploadProgressBlock = [uploadProgressBlock copy]; - - GTLRServiceRetryBlock retryBlock = params.retryBlock ?: service.retryBlock; - _retryBlock = [retryBlock copy]; - if (_retryBlock) { - _retryEnabled = YES; - } - - _testBlock = params.testBlock ?: service.testBlock; - - _callbackQueue = ((_Nonnull dispatch_queue_t)params.callbackQueue) ?: service.callbackQueue; - _callbackGroup = dispatch_group_create(); - - _apiKey = [service.APIKey copy]; - _apiKeyRestrictionBundleID = [service.APIKeyRestrictionBundleID copy]; - _allowInsecureQueries = service.allowInsecureQueries; - -#if GTM_BACKGROUND_TASK_FETCHING - _backgroundTaskIdentifier = UIBackgroundTaskInvalid; -#endif - - _creationDate = [NSDate date]; - } - return self; -} - -- (NSString *)description { - NSString *devKeyInfo = @""; - if (_apiKey != nil) { - devKeyInfo = [NSString stringWithFormat:@" devKey:%@", _apiKey]; - } - NSString *keyRestrictionInfo = @""; - if (_apiKeyRestrictionBundleID != nil) { - keyRestrictionInfo = [NSString stringWithFormat:@" restriction:%@", - _apiKeyRestrictionBundleID]; - } - - NSString *authorizerInfo = @""; - id <GTMFetcherAuthorizationProtocol> authorizer = self.objectFetcher.authorizer; - if (authorizer != nil) { - authorizerInfo = [NSString stringWithFormat:@" authorizer:%@", authorizer]; - } - - return [NSString stringWithFormat:@"%@ %p: {service:%@%@%@%@ fetcher:%@ }", - [self class], self, - _service, devKeyInfo, keyRestrictionInfo, authorizerInfo, _objectFetcher]; -} - -- (void)postNotificationOnMainThreadWithName:(NSString *)name - object:(id)object - userInfo:(NSDictionary *)userInfo { - // We always post these async to ensure they remain in order. - dispatch_group_async(self.callbackGroup, dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:name - object:object - userInfo:userInfo]; - }); -} - -- (void)pauseUpload { - GTMSessionFetcher *fetcher = self.objectFetcher; - BOOL canPause = [fetcher respondsToSelector:@selector(pauseFetching)]; - GTLR_DEBUG_ASSERT(canPause, @"tickets can be paused only for chunked resumable uploads"); - - if (canPause) { - [(GTMSessionUploadFetcher *)fetcher pauseFetching]; - } -} - -- (void)resumeUpload { - GTMSessionFetcher *fetcher = self.objectFetcher; - BOOL canResume = [fetcher respondsToSelector:@selector(resumeFetching)]; - GTLR_DEBUG_ASSERT(canResume, @"tickets can be resumed only for chunked resumable uploads"); - - if (canResume) { - [(GTMSessionUploadFetcher *)fetcher resumeFetching]; - } -} - -- (BOOL)isUploadPaused { - BOOL isPausable = [_objectFetcher respondsToSelector:@selector(isPaused)]; - GTLR_DEBUG_ASSERT(isPausable, @"tickets can be paused only for chunked resumable uploads"); - - if (isPausable) { - return [(GTMSessionUploadFetcher *)_objectFetcher isPaused]; - } - return NO; -} - -- (BOOL)isCancelled { - @synchronized(self) { - return _cancelled; - } -} - -- (void)cancelTicket { - @synchronized(self) { - _cancelled = YES; - } - - [_objectFetcher stopFetching]; - - self.objectFetcher = nil; - self.fetchRequest = nil; - _ticketProperties = nil; - - [self releaseTicketCallbacks]; - [self endBackgroundTask]; - - [self.executingQuery invalidateQuery]; - - id<GTLRQueryProtocol> originalQuery = self.originalQuery; - self.executingQuery = originalQuery; - [originalQuery invalidateQuery]; - - _service = nil; - _fetcherService = nil; - _authorizer = nil; - _testBlock = nil; -} - -#if GTM_BACKGROUND_TASK_FETCHING -// When the fetcher's substitute UIApplication object is present, GTLRService -// will use that instead of UIApplication. This is just to reduce duplicating -// that plumbing for testing. -+ (nullable id<GTMUIApplicationProtocol>)fetcherUIApplication { - id<GTMUIApplicationProtocol> app = [GTMSessionFetcher substituteUIApplication]; - if (app) return app; - - static Class applicationClass = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - BOOL isAppExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; - if (!isAppExtension) { - Class cls = NSClassFromString(@"UIApplication"); - if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { - applicationClass = cls; - } - } - }); - - if (applicationClass) { - app = (id<GTMUIApplicationProtocol>)[applicationClass sharedApplication]; - } - return app; -} -#endif // GTM_BACKGROUND_TASK_FETCHING - -- (void)startBackgroundTask { -#if GTM_BACKGROUND_TASK_FETCHING - GTLR_DEBUG_ASSERT(self.backgroundTaskIdentifier == UIBackgroundTaskInvalid, - @"Redundant GTLRService background task: %tu", self.backgroundTaskIdentifier); - - NSString *taskName = [[self.executingQuery class] description]; - - id<GTMUIApplicationProtocol> app = [[self class] fetcherUIApplication]; - - // We'll use a locally-scoped task ID variable so the expiration block is guaranteed - // to refer to this task rather than to whatever task the property has. - __block UIBackgroundTaskIdentifier bgTaskID = - [app beginBackgroundTaskWithName:taskName - expirationHandler:^{ - // Background task expiration callback. This block is always invoked by - // UIApplication on the main thread. - if (bgTaskID != UIBackgroundTaskInvalid) { - @synchronized(self) { - if (bgTaskID == self.backgroundTaskIdentifier) { - self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; - } - } - // This explicitly ends the captured bgTaskID rather than the backgroundTaskIdentifier - // property to ensure expiration is handled even if the property has changed. - [app endBackgroundTask:bgTaskID]; - } - }]; - @synchronized(self) { - self.backgroundTaskIdentifier = bgTaskID; - } -#endif // GTM_BACKGROUND_TASK_FETCHING -} - -- (void)endBackgroundTask { -#if GTM_BACKGROUND_TASK_FETCHING - // Whenever the connection stops or a next page is about to be fetched, - // tell UIApplication we're done. - UIBackgroundTaskIdentifier bgTaskID; - @synchronized(self) { - bgTaskID = self.backgroundTaskIdentifier; - self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; - } - if (bgTaskID != UIBackgroundTaskInvalid) { - [[[self class] fetcherUIApplication] endBackgroundTask:bgTaskID]; - } -#endif // GTM_BACKGROUND_TASK_FETCHING -} - -- (void)releaseTicketCallbacks { - self.uploadProgressBlock = nil; - self.retryBlock = nil; -} - -- (void)notifyStarting:(BOOL)isStarting { - GTLR_DEBUG_ASSERT(!GTLR_AreBoolsEqual(isStarting, _needsStopNotification), - @"Notification mismatch (isStarting=%d)", isStarting); - if (GTLR_AreBoolsEqual(isStarting, _needsStopNotification)) return; - - NSString *name; - if (isStarting) { - name = kGTLRServiceTicketStartedNotification; - _needsStopNotification = YES; - } else { - name = kGTLRServiceTicketStoppedNotification; - _needsStopNotification = NO; - } - [self postNotificationOnMainThreadWithName:name - object:self - userInfo:nil]; -} - -- (id)service { - return _service; -} - -- (void)setObjectFetcher:(GTMSessionFetcher *)fetcher { - @synchronized(self) { - _objectFetcher = fetcher; - } - - [self updateObjectFetcherProgressCallbacks]; -} - -- (GTMSessionFetcher *)objectFetcher { - @synchronized(self) { - return _objectFetcher; - } -} - -- (NSDictionary *)ticketProperties { - // be sure the returned pointer has the life of the autorelease pool, - // in case self is released immediately - __autoreleasing id props = _ticketProperties; - return props; -} - -- (GTLRServiceUploadProgressBlock)uploadProgressBlock { - return _uploadProgressBlock; -} - -- (void)setUploadProgressBlock:(GTLRServiceUploadProgressBlock)block { - if (_uploadProgressBlock != block) { - _uploadProgressBlock = [block copy]; - - [self updateObjectFetcherProgressCallbacks]; - } -} - -- (void)updateObjectFetcherProgressCallbacks { - // Internal method. Do not override. - GTMSessionFetcher *fetcher = [self objectFetcher]; - - if (_uploadProgressBlock) { - // Use a local block variable to avoid a spurious retain cycle warning. - GTMSessionFetcherSendProgressBlock fetcherSentDataBlock = ^(int64_t bytesSent, - int64_t totalBytesSent, - int64_t totalBytesExpectedToSend) { - [self->_service invokeProgressCallbackForTicket:self - deliveredBytes:(unsigned long long)totalBytesSent - totalBytes:(unsigned long long)totalBytesExpectedToSend]; - }; - - fetcher.sendProgressBlock = fetcherSentDataBlock; - } else { - fetcher.sendProgressBlock = nil; - } -} - -- (NSInteger)statusCode { - return [_objectFetcher statusCode]; -} - -- (GTLRQuery *)queryForRequestID:(NSString *)requestID { - id<GTLRQueryProtocol> queryObj = self.executingQuery; - if ([queryObj isBatchQuery]) { - GTLRBatchQuery *batch = (GTLRBatchQuery *)queryObj; - GTLRQuery *result = [batch queryForRequestID:requestID]; - return result; - } else { - GTLR_DEBUG_ASSERT(0, @"just use ticket.executingQuery"); - return nil; - } -} - -@end - -@implementation GTLRServiceExecutionParameters - -@synthesize maxRetryInterval = _maxRetryInterval, - retryEnabled = _retryEnabled, - retryBlock = _retryBlock, - shouldFetchNextPages = _shouldFetchNextPages, - objectClassResolver = _objectClassResolver, - testBlock = _testBlock, - ticketProperties = _ticketProperties, - uploadProgressBlock = _uploadProgressBlock, - callbackQueue = _callbackQueue; - -- (id)copyWithZone:(NSZone *)zone { - GTLRServiceExecutionParameters *newObject = [[self class] allocWithZone:zone]; - newObject.maxRetryInterval = self.maxRetryInterval; - newObject.retryEnabled = self.retryEnabled; - newObject.retryBlock = self.retryBlock; - newObject.shouldFetchNextPages = self.shouldFetchNextPages; - newObject.objectClassResolver = self.objectClassResolver; - newObject.testBlock = self.testBlock; - newObject.ticketProperties = self.ticketProperties; - newObject.uploadProgressBlock = self.uploadProgressBlock; - newObject.callbackQueue = self.callbackQueue; - return newObject; -} - -- (BOOL)hasParameters { - if (self.maxRetryInterval != nil) return YES; - if (self.retryEnabled != nil) return YES; - if (self.retryBlock) return YES; - if (self.shouldFetchNextPages != nil) return YES; - if (self.objectClassResolver) return YES; - if (self.testBlock) return YES; - if (self.ticketProperties) return YES; - if (self.uploadProgressBlock) return YES; - if (self.callbackQueue) return YES; - return NO; -} - -@end - - -@implementation GTLRResourceURLQuery - -@synthesize resourceURL = _resourceURL; - -+ (instancetype)queryWithResourceURL:(NSURL *)resourceURL - objectClass:(Class)objectClass { - GTLRResourceURLQuery *query = [[self alloc] initWithPathURITemplate:@"_usingGTLRResourceURLQuery_" - HTTPMethod:nil - pathParameterNames:nil]; - query.expectedObjectClass = objectClass; - query.resourceURL = resourceURL; - return query; -} - -- (instancetype)copyWithZone:(NSZone *)zone { - GTLRResourceURLQuery *result = [super copyWithZone:zone]; - result->_resourceURL = self->_resourceURL; - return result; -} - -// TODO: description - -@end - -@implementation GTLRObjectCollectionImpl -@dynamic nextPageToken; -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRUploadParameters.h b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRUploadParameters.h @@ -1,124 +0,0 @@ -/* 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. - */ - -// Uploading documentation: -// https://github.com/google/google-api-objectivec-client-for-rest/wiki#uploading-files - -#import <Foundation/Foundation.h> - -#import "GTLRDefines.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * Upload parameters are required for chunked-resumable or simple/multipart uploads. - * - * The MIME type and one source for data (@c NSData, file URL, or @c NSFileHandle) must - * be specified. - */ -@interface GTLRUploadParameters : NSObject <NSCopying> - -/** - * The type of media being uploaded. - */ -@property(atomic, copy, nullable) NSString *MIMEType; - -/** - * The media to be uploaded, represented as @c NSData. - */ -@property(atomic, retain, nullable) NSData *data; - -/** - * The URL for the local file to be uploaded. - */ -@property(atomic, retain, nullable) NSURL *fileURL; - -/** - * The media to be uploaded, represented as @c NSFileHandle. - * - * @note This property is provided for compatibility with older code. - * Uploading using @c fileURL is preferred over @c fileHandle - */ -@property(atomic, retain, nullable) NSFileHandle *fileHandle; - -/** - * Resuming an in-progress resumable, chunked upload is done with the upload location URL, - * and requires a file URL or file handle for uploading. - */ -@property(atomic, retain, nullable) NSURL *uploadLocationURL; - -/** - * Small uploads (for example, under 200K) can be done with a single multipart upload - * request. The upload body must be provided as NSData, not a file URL or file handle. - * - * Default value is NO. - */ -@property(atomic, assign) BOOL shouldUploadWithSingleRequest; - -/** - * Uploads may be done without a JSON body as metadata in the initial request. - * - * Default value is NO. - */ -@property(atomic, assign) BOOL shouldSendUploadOnly; - -/** - * Uploads may use a background session when uploading via GTMSessionUploadFetcher. - * Since background session fetches are slower than foreground fetches, this defaults - * to NO. - * - * It's reasonable for an application to set this to YES for a rare upload of a large file. - * - * Default value is NO. - * - * For more information about the hazards of background sessions, see the header comments for - * the GTMSessionFetcher useBackgroundSession property. - */ -@property(atomic, assign) BOOL useBackgroundSession; - -/** - * Constructor for uploading from @c NSData. - * - * @param data The data to uploaded. - * @param mimeType The media's type. - * - * @return The upload parameters object. - */ -+ (instancetype)uploadParametersWithData:(NSData *)data - MIMEType:(NSString *)mimeType; - -/** - * Constructor for uploading from a file URL. - * - * @param fileURL The file to upload. - * @param mimeType The media's type. - * - * @return The upload parameters object. - */ -+ (instancetype)uploadParametersWithFileURL:(NSURL *)fileURL - MIMEType:(NSString *)mimeType; - -/** - * Constructor for uploading from a file handle. - * - * @note This method is provided for compatibility with older code. To upload files, - * use a file URL. - */ -+ (instancetype)uploadParametersWithFileHandle:(NSFileHandle *)fileHandle - MIMEType:(NSString *)mimeType; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Objects/GTLRUploadParameters.m b/Pods/GoogleAPIClientForREST/Source/Objects/GTLRUploadParameters.m @@ -1,119 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#include <objc/runtime.h> - -#import "GTLRUploadParameters.h" - -@implementation GTLRUploadParameters - -@synthesize MIMEType = _MIMEType, - data = _data, - fileHandle = _fileHandle, - uploadLocationURL = _uploadLocationURL, - fileURL = _fileURL, - shouldUploadWithSingleRequest = _shouldUploadWithSingleRequest, - shouldSendUploadOnly = _shouldSendUploadOnly, - useBackgroundSession = _useBackgroundSession; - -+ (instancetype)uploadParametersWithData:(NSData *)data - MIMEType:(NSString *)mimeType { - GTLRUploadParameters *params = [[self alloc] init]; - params.data = data; - params.MIMEType = mimeType; - return params; -} - -+ (instancetype)uploadParametersWithFileHandle:(NSFileHandle *)fileHandle - MIMEType:(NSString *)mimeType { - GTLRUploadParameters *params = [[self alloc] init]; - params.fileHandle = fileHandle; - params.MIMEType = mimeType; - return params; -} - -+ (instancetype)uploadParametersWithFileURL:(NSURL *)fileURL - MIMEType:(NSString *)mimeType { - GTLRUploadParameters *params = [[self alloc] init]; - params.fileURL = fileURL; - params.MIMEType = mimeType; - return params; -} - -- (id)copyWithZone:(NSZone *)zone { - GTLRUploadParameters *newParams = [[[self class] allocWithZone:zone] init]; - newParams.MIMEType = self.MIMEType; - newParams.data = self.data; - newParams.fileHandle = self.fileHandle; - newParams.fileURL = self.fileURL; - newParams.uploadLocationURL = self.uploadLocationURL; - newParams.shouldUploadWithSingleRequest = self.shouldUploadWithSingleRequest; - newParams.shouldSendUploadOnly = self.shouldSendUploadOnly; - newParams.useBackgroundSession = self.useBackgroundSession; - return newParams; -} - -#if DEBUG -- (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 (_fileURL) { - str = [NSString stringWithFormat:@"file:%@", [_fileURL path]]; - [array addObject:str]; - } - - if (_uploadLocationURL) { - str = [NSString stringWithFormat:@"uploadLocation:%@", - [_uploadLocationURL absoluteString]]; - [array addObject:str]; - } - - if (_shouldSendUploadOnly) { - [array addObject:@"shouldSendUploadOnly"]; - } - - if (_shouldUploadWithSingleRequest) { - [array addObject:@"uploadWithSingleRequest"]; - } - - if (_useBackgroundSession) { - [array addObject:@"useBackgroundSession"]; - } - - NSString *descStr = [array componentsJoinedByString:@", "]; - str = [NSString stringWithFormat:@"%@ %p: {%@}", - [self class], self, descStr]; - return str; -} -#endif // DEBUG - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRBase64.h b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRBase64.h @@ -1,29 +0,0 @@ -/* Copyright (c) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import <Foundation/Foundation.h> - -NS_ASSUME_NONNULL_BEGIN - -NSData * _Nullable GTLRDecodeBase64(NSString * _Nullable base64Str); -NSString * _Nullable GTLREncodeBase64(NSData * _Nullable data); - -// "Web-safe" encoding substitutes - and _ for + and / in the encoding table, -// per http://www.ietf.org/rfc/rfc4648.txt section 5. - -NSData * _Nullable GTLRDecodeWebSafeBase64(NSString * _Nullable base64Str); -NSString * _Nullable GTLREncodeWebSafeBase64(NSData * _Nullable data); - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRBase64.m b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRBase64.m @@ -1,143 +0,0 @@ -/* 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. - */ - -#if !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRBase64.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]; - - int8_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]; - return result; -} - -NSString *GTLREncodeBase64(NSData *data) { - return EncodeBase64StringCommon(data, gStandardEncodingTable); -} - -NSString *GTLREncodeWebSafeBase64(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]] = (char)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; - - NSInteger inputLength = (NSInteger)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:(NSUInteger)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++] = (uint8_t)((table[i0] << 2) | (table[i1] >> 4)); - if (outputPoint < outputLength) { - output[outputPoint++] = (uint8_t)(((table[i1] & 0xF) << 4) | (table[i2] >> 2)); - } - if (outputPoint < outputLength) { - output[outputPoint++] = (uint8_t)(((table[i2] & 0x3) << 6) | table[i3]); - } - } - - return data; -} - -NSData *GTLRDecodeBase64(NSString *base64Str) { - static char decodingTable[128]; - static BOOL hasInited = NO; - - if (!hasInited) { - CreateDecodingTable(gStandardEncodingTable, sizeof(gStandardEncodingTable), - decodingTable); - hasInited = YES; - } - return DecodeBase64StringCommon(base64Str, decodingTable); -} - -NSData *GTLRDecodeWebSafeBase64(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/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRFramework.h b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRFramework.h @@ -1,34 +0,0 @@ -/* Copyright (c) 2011 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import <Foundation/Foundation.h> - -#import "GTLRDefines.h" - -NS_ASSUME_NONNULL_BEGIN - -// 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 GTLRFrameworkVersion(NSUInteger * _Nullable major, - NSUInteger * _Nullable minor, - NSUInteger * _Nullable release); - -// Returns the version in @"a.b" or @"a.b.c" format -NSString *GTLRFrameworkVersionString(void); - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRFramework.m b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRFramework.m @@ -1,44 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#include "GTLRFramework.h" - -void GTLRFrameworkVersion(NSUInteger* major, NSUInteger* minor, NSUInteger* release) { - // version 3.0.0 - if (major) *major = 3; - if (minor) *minor = 0; - if (release) *release = 0; -} - -NSString *GTLRFrameworkVersionString(void) { - NSUInteger major, minor, release; - NSString *libVersionString; - - GTLRFrameworkVersion(&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/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRURITemplate.h b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRURITemplate.h @@ -1,48 +0,0 @@ -/* 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 <Foundation/Foundation.h> - -#ifndef SKIP_GTLR_DEFINES - #import "GTLRDefines.h" -#endif - -NS_ASSUME_NONNULL_BEGIN - -// -// URI Template -// -// http://tools.ietf.org/html/draft-gregorio-uritemplate-04 -// -// NOTE: This implemention is only a subset of the spec. It should be able -// to parse any tempate that matches the spec, but if the template makes use -// of a feature that is not supported, it will fail with an error. -// - -@interface GTLRURITemplate : NSObject - -// Process the template. If the template uses an unsupported feature, it will -// throw an exception to help catch that limitation. Currently unsupported -// feature is partial result modifiers (prefix/suffix). -// -// valueProvider should be anything that implements -objectForKey:. At the -// simplest level, this can be an NSDictionary. However, a custom class that -// implements valueForKey my be better for some uses. -+ (NSString *)expandTemplate:(NSString *)URITemplate - values:(NSDictionary *)valueProvider; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRURITemplate.m b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRURITemplate.m @@ -1,511 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRURITemplate.h" - -// Key constants for handling variables. -static NSString *const kVariable = @"variable"; // NSString -static NSString *const kExplode = @"explode"; // NSString -static NSString *const kPartial = @"partial"; // NSString -static NSString *const kPartialValue = @"partialValue"; // NSNumber - -// Help for passing the Expansion info in one shot. -struct ExpansionInfo { - // Constant for the whole expansion. - unichar expressionOperator; - __unsafe_unretained NSString *joiner; - BOOL allowReservedInEscape; - - // Update for each variable. - __unsafe_unretained NSString *explode; -}; - -// Helper just to shorten the lines when needed. -static NSString *UnescapeString(NSString *str) { - return [str stringByRemovingPercentEncoding]; -} - -static NSString *EscapeString(NSString *str, BOOL allowReserved) { - // The spec is a little hard to map onto the charsets, so force - // reserved bits in/out. - NSMutableCharacterSet *cs = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; - NSString * const kReservedChars = @":/?#[]@!$&'()*+,;="; - if (allowReserved) { - [cs addCharactersInString:kReservedChars]; - } else { - [cs removeCharactersInString:kReservedChars]; - } - NSString *resultStr = [str stringByAddingPercentEncodingWithAllowedCharacters:cs]; - return resultStr; -} - -static NSString *StringFromNSNumber(NSNumber *rawValue) { - NSString *strValue; - // NSNumber doesn't expose a way to tell if it is holding a BOOL or something - // else. -[NSNumber objCType] for a BOOL is the same as @encoding(char), but - // in the 64bit runtine @encoding(BOOL) (or for "bool") won't match that as - // the 64bit runtime actually has a true boolean type. Instead we reply on - // checking if the numbers are the CFBoolean constants to force true/value - // values. - if ((rawValue == (NSNumber *)kCFBooleanTrue) || - (rawValue == (NSNumber *)kCFBooleanFalse)) { - strValue = (rawValue.boolValue ? @"true" : @"false"); - } else { - strValue = [rawValue stringValue]; - } - return strValue; -} - -@implementation GTLRURITemplate - -#pragma mark Internal Helpers - -+ (BOOL)parseExpression:(NSString *)expression - expressionOperator:(unichar*)outExpressionOperator - variables:(NSMutableArray **)outVariables - defaultValues:(NSMutableDictionary **)outDefaultValues { - - // Please see the spec for full details, but here are the basics: - // - // URI-Template = *( literals / expression ) - // expression = "{" [ operator ] variable-list "}" - // variable-list = varspec *( "," varspec ) - // varspec = varname [ modifier ] [ "=" default ] - // varname = varchar *( varchar / "." ) - // modifier = explode / partial - // explode = ( "*" / "+" ) - // partial = ( substring / remainder ) offset - // - // Examples: - // http://www.example.com/foo{?query,number} - // http://maps.com/mapper{?address*} - // http://directions.org/directions{?from+,to+} - // http://search.org/query{?terms+=none} - // - - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.2 - // Operator and op-reserve characters - static NSCharacterSet *operatorSet = nil; - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.4.1 - // Explode characters - static NSCharacterSet *explodeSet = nil; - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.4.2 - // Partial (prefix/subset) characters - static NSCharacterSet *partialSet = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - operatorSet = [NSCharacterSet characterSetWithCharactersInString:@"+./;?|!@"]; - explodeSet = [NSCharacterSet characterSetWithCharactersInString:@"*+"]; - partialSet = [NSCharacterSet characterSetWithCharactersInString:@":^"]; - }); - - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-3.3 - // Empty expression inlines the expression. - if (expression.length == 0) return NO; - - // Pull off any operator. - *outExpressionOperator = 0; - unichar firstChar = [expression characterAtIndex:0]; - if ([operatorSet characterIsMember:firstChar]) { - *outExpressionOperator = firstChar; - expression = [expression substringFromIndex:1]; - } - - if (expression.length == 0) return NO; - - // Need to find atleast one varspec for the expresssion to be considered - // valid. - BOOL gotAVarspec = NO; - - // Split the variable list. - NSArray *varspecs = [expression componentsSeparatedByString:@","]; - - // Extract the defaults, explodes and modifiers from the varspecs. - *outVariables = [NSMutableArray arrayWithCapacity:varspecs.count]; - for (__strong NSString *varspec in varspecs) { - NSString *defaultValue = nil; - - if (varspec.length == 0) continue; - - NSMutableDictionary *varInfo = - [NSMutableDictionary dictionaryWithCapacity:4]; - - // Check for a default (foo=bar). - NSRange range = [varspec rangeOfString:@"="]; - if (range.location != NSNotFound) { - defaultValue = - UnescapeString([varspec substringFromIndex:range.location + 1]); - varspec = [varspec substringToIndex:range.location]; - - if (varspec.length == 0) continue; - } - - // Check for explode (foo*). - NSUInteger lenLessOne = varspec.length - 1; - if ([explodeSet characterIsMember:[varspec characterAtIndex:lenLessOne]]) { - [varInfo setObject:[varspec substringFromIndex:lenLessOne] forKey:kExplode]; - varspec = [varspec substringToIndex:lenLessOne]; - if (varspec.length == 0) continue; - } else { - // Check for partial (prefix/suffix) (foo:12). - range = [varspec rangeOfCharacterFromSet:partialSet]; - if (range.location != NSNotFound) { - NSString *partialMode = [varspec substringWithRange:range]; - NSString *valueStr = [varspec substringFromIndex:range.location + 1]; - // If there wasn't a value for the partial, ignore it. - if (valueStr.length > 0) { - [varInfo setObject:partialMode forKey:kPartial]; - // TODO: Should validate valueStr is just a number... - [varInfo setObject:[NSNumber numberWithInteger:[valueStr integerValue]] - forKey:kPartialValue]; - } - varspec = [varspec substringToIndex:range.location]; - if (varspec.length == 0) continue; - } - } - - // Spec allows percent escaping in names, so undo that. - varspec = UnescapeString(varspec); - - // Save off the cleaned up variable name. - [varInfo setObject:varspec forKey:kVariable]; - [*outVariables addObject:varInfo]; - gotAVarspec = YES; - - // Now that the variable has been cleaned up, store its default. - if (defaultValue) { - if (*outDefaultValues == nil) { - *outDefaultValues = [NSMutableDictionary dictionary]; - } - [*outDefaultValues setObject:defaultValue forKey:varspec]; - } - } - // All done. - return gotAVarspec; -} - -+ (NSString *)expandVariables:(NSArray *)variables - expressionOperator:(unichar)expressionOperator - values:(NSDictionary *)valueProvider - defaultValues:(NSMutableDictionary *)defaultValues { - NSString *prefix = nil; - struct ExpansionInfo expansionInfo = { - .expressionOperator = expressionOperator, - .joiner = nil, - .allowReservedInEscape = NO, - .explode = nil, - }; - switch (expressionOperator) { - case 0: - expansionInfo.joiner = @","; - prefix = @""; - break; - case '+': - expansionInfo.joiner = @","; - prefix = @""; - // The reserved character are safe from escaping. - expansionInfo.allowReservedInEscape = YES; - break; - case '.': - expansionInfo.joiner = @"."; - prefix = @"."; - break; - case '/': - expansionInfo.joiner = @"/"; - prefix = @"/"; - break; - case ';': - expansionInfo.joiner = @";"; - prefix = @";"; - break; - case '?': - expansionInfo.joiner = @"&"; - prefix = @"?"; - break; - default: - [NSException raise:@"GTLRURITemplateUnsupported" - format:@"Unknown expression operator '%C'", expressionOperator]; - break; - } - - NSMutableArray *results = [NSMutableArray arrayWithCapacity:variables.count]; - - for (NSDictionary *varInfo in variables) { - NSString *variable = [varInfo objectForKey:kVariable]; - - expansionInfo.explode = [varInfo objectForKey:kExplode]; - // Look up the variable value. - id rawValue = [valueProvider objectForKey:variable]; - - // If the value is an empty array or dictionary, the default is still used. - if (([rawValue isKindOfClass:[NSArray class]] - || [rawValue isKindOfClass:[NSDictionary class]]) - && ((NSArray *)rawValue).count == 0) { - rawValue = nil; - } - - // Got nothing? Check defaults. - if (rawValue == nil) { - rawValue = [defaultValues objectForKey:variable]; - } - - // If we didn't get any value, on to the next thing. - if (!rawValue) { - continue; - } - - // Time do to the work... - NSString *result = nil; - if ([rawValue isKindOfClass:[NSString class]]) { - result = [self expandString:rawValue - variableName:variable - expansionInfo:&expansionInfo]; - } else if ([rawValue isKindOfClass:[NSNumber class]]) { - // Turn the number into a string and send it on its way. - NSString *strValue = StringFromNSNumber(rawValue); - result = [self expandString:strValue - variableName:variable - expansionInfo:&expansionInfo]; - } else if ([rawValue isKindOfClass:[NSArray class]]) { - result = [self expandArray:rawValue - variableName:variable - expansionInfo:&expansionInfo]; - } else if ([rawValue isKindOfClass:[NSDictionary class]]) { - result = [self expandDictionary:rawValue - variableName:variable - expansionInfo:&expansionInfo]; - } else { - [NSException raise:@"GTLRURITemplateUnsupported" - format:@"Variable returned unsupported type (%@)", - NSStringFromClass([rawValue class])]; - } - - // Did it generate anything? - if (result == nil) - continue; - - // Apply partial. - // Defaults should get partial applied? - // ( http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.5 ) - NSString *partial = [varInfo objectForKey:kPartial]; - if (partial.length > 0) { - [NSException raise:@"GTLRURITemplateUnsupported" - format:@"Unsupported partial on expansion %@", partial]; - } - - // Add the result - [results addObject:result]; - } - - // Join and add any needed prefix. - NSString *joinedResults = - [results componentsJoinedByString:expansionInfo.joiner]; - if ((prefix.length > 0) && (joinedResults.length > 0)) { - return [prefix stringByAppendingString:joinedResults]; - } - return joinedResults; -} - -+ (NSString *)expandString:(NSString *)valueStr - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo { - NSString *escapedValue = - EscapeString(valueStr, expansionInfo->allowReservedInEscape); - switch (expansionInfo->expressionOperator) { - case ';': - case '?': - if (valueStr.length > 0) { - return [NSString stringWithFormat:@"%@=%@", variableName, escapedValue]; - } - return variableName; - default: - return escapedValue; - } -} - -+ (NSString *)expandArray:(NSArray *)valueArray - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo { - NSMutableArray *results = [NSMutableArray arrayWithCapacity:valueArray.count]; - // When joining variable with value, use "var.val" except for 'path' and - // 'form' style expression, use 'var=val' then. - char variableValueJoiner = '.'; - unichar expressionOperator = expansionInfo->expressionOperator; - if ((expressionOperator == ';') || (expressionOperator == '?')) { - variableValueJoiner = '='; - } - // Loop over the values. - for (id rawValue in valueArray) { - NSString *value; - if ([rawValue isKindOfClass:[NSNumber class]]) { - value = StringFromNSNumber((id)rawValue); - } else if ([rawValue isKindOfClass:[NSString class]]) { - value = rawValue; - } else { - [NSException raise:@"GTLRURITemplateUnsupported" - format:@"Variable '%@' returned NSArray with unsupported type (%@), array: %@", - variableName, NSStringFromClass([rawValue class]), valueArray]; - } - // Escape it. - value = EscapeString(value, expansionInfo->allowReservedInEscape); - // Should variable names be used? - if ([expansionInfo->explode isEqual:@"+"]) { - value = [NSString stringWithFormat:@"%@%c%@", - variableName, variableValueJoiner, value]; - } - [results addObject:value]; - } - if (results.count > 0) { - // Use the default joiner unless there was no explode request, then a list - // always gets comma seperated. - NSString *joiner = expansionInfo->joiner; - if (expansionInfo->explode == nil) { - joiner = @","; - } - // Join the values. - NSString *joined = [results componentsJoinedByString:joiner]; - // 'form' style without an explode gets the variable name set to the - // joined list of values. - if ((expressionOperator == '?') && (expansionInfo->explode == nil)) { - return [NSString stringWithFormat:@"%@=%@", variableName, joined]; - } - return joined; - } - return nil; -} - -+ (NSString *)expandDictionary:(NSDictionary *)valueDict - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo { - NSMutableArray *results = [NSMutableArray arrayWithCapacity:valueDict.count]; - // When joining variable with value: - // - Default to the joiner... - // - No explode, always comma... - // - For 'path' and 'form' style expression, use 'var=val'. - NSString *keyValueJoiner = expansionInfo->joiner; - unichar expressionOperator = expansionInfo->expressionOperator; - if (expansionInfo->explode == nil) { - keyValueJoiner = @","; - } else if ((expressionOperator == ';') || (expressionOperator == '?')) { - keyValueJoiner = @"="; - } - // Loop over the sorted keys. - NSArray *sortedKeys = [valueDict.allKeys sortedArrayUsingSelector:@selector(compare:)]; - for (__strong NSString *key in sortedKeys) { - NSString *value = [valueDict objectForKey:key]; - // Escape them. - key = EscapeString(key, expansionInfo->allowReservedInEscape); - value = EscapeString(value, expansionInfo->allowReservedInEscape); - // Should variable names be used? - if ([expansionInfo->explode isEqual:@"+"]) { - key = [NSString stringWithFormat:@"%@.%@", variableName, key]; - } - if ((expressionOperator == '?' || expressionOperator == ';') - && (value.length == 0)) { - [results addObject:key]; - } else { - NSString *pair = [NSString stringWithFormat:@"%@%@%@", - key, keyValueJoiner, value]; - [results addObject:pair]; - } - } - if (results.count) { - // Use the default joiner unless there was no explode request, then a list - // always gets comma seperated. - NSString *joiner = expansionInfo->joiner; - if (expansionInfo->explode == nil) { - joiner = @","; - } - // Join the values. - NSString *joined = [results componentsJoinedByString:joiner]; - // 'form' style without an explode gets the variable name set to the - // joined list of values. - if ((expressionOperator == '?') && (expansionInfo->explode == nil)) { - return [NSString stringWithFormat:@"%@=%@", variableName, joined]; - } - return joined; - } - return nil; -} - -#pragma mark Public API - -+ (NSString *)expandTemplate:(NSString *)uriTemplate - values:(NSDictionary *)valueProvider { - NSMutableString *result = - [NSMutableString stringWithCapacity:uriTemplate.length]; - - NSScanner *scanner = [NSScanner scannerWithString:uriTemplate]; - [scanner setCharactersToBeSkipped:nil]; - - // Defaults have to live through the full evaluation, so if any are encoured - // they are reused throughout the expansion calls. - NSMutableDictionary *defaultValues = nil; - - // Pull out the expressions for processing. - while (![scanner isAtEnd]) { - NSString *skipped = nil; - // Find the next '{'. - if ([scanner scanUpToString:@"{" intoString:&skipped]) { - // Add anything before it to the result. - [result appendString:skipped]; - } - // Advance over the '{'. - [scanner scanString:@"{" intoString:nil]; - // Collect the expression. - NSString *expression = nil; - if ([scanner scanUpToString:@"}" intoString:&expression]) { - // Collect the trailing '}' on the expression. - BOOL hasTrailingBrace = [scanner scanString:@"}" intoString:nil]; - - // Parse the expression. - NSMutableArray *variables = nil; - unichar expressionOperator = 0; - if ([self parseExpression:expression - expressionOperator:&expressionOperator - variables:&variables - defaultValues:&defaultValues]) { - // Do the expansion. - NSString *substitution = [self expandVariables:variables - expressionOperator:expressionOperator - values:valueProvider - defaultValues:defaultValues]; - if (substitution) { - [result appendString:substitution]; - } - } else { - // Failed to parse, add the raw expression to the output. - if (hasTrailingBrace) { - [result appendFormat:@"{%@}", expression]; - } else { - [result appendFormat:@"{%@", expression]; - } - } - } else if (![scanner isAtEnd]) { - // Empty expression ('{}'). Copy over the opening brace and the trailing - // one will be copied by the next cycle of the loop. - [result appendString:@"{"]; - } - } - - return result; -} - -@end diff --git a/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRUtilities.h b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRUtilities.h @@ -1,52 +0,0 @@ -/* Copyright (c) 2011 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import <Foundation/Foundation.h> - -#ifndef SKIP_GTLR_DEFINES - #import "GTLRDefines.h" -#endif - -NS_ASSUME_NONNULL_BEGIN - -// Helper functions for implementing isEqual: -BOOL GTLR_AreEqualOrBothNil(id _Nullable obj1, id _Nullable obj2); -BOOL GTLR_AreBoolsEqual(BOOL b1, BOOL b2); - -// Helper to ensure a number is a number. -// -// The Google API 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 *GTLR_EnsureNSNumber(NSNumber *num); - -@interface GTLRUtilities : NSObject - -// 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; - -+ (nullable id)firstObjectFromArray:(NSArray *)sourceArray - withValue:(id)desiredValue - forKeyPath:(NSString *)keyPath; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRUtilities.m b/Pods/GoogleAPIClientForREST/Source/Utilities/GTLRUtilities.m @@ -1,117 +0,0 @@ -/* 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 !__has_feature(objc_arc) -#error "This file needs to be compiled with ARC enabled." -#endif - -#import "GTLRUtilities.h" - -#include <objc/runtime.h> - -@implementation GTLRUtilities - -#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 (GTLR_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 (GTLR_AreEqualOrBothNil(val, desiredValue)) { - // found a match; return it - return obj; - } - } - return nil; -} - -#pragma mark Version helpers - -@end - -// isEqual: has the fatal flaw that it doesn't deal well with the receiver -// being nil. We'll use this utility instead. -BOOL GTLR_AreEqualOrBothNil(id obj1, id obj2) { - if (obj1 == obj2) { - return YES; - } - if (obj1 && obj2) { - BOOL areEqual = [(NSObject *)obj1 isEqual:obj2]; - return areEqual; - } - return NO; -} - -BOOL GTLR_AreBoolsEqual(BOOL b1, BOOL b2) { - // avoid comparison problems with boolean types by negating - // both booleans - return (!b1 == !b2); -} - -NSNumber *GTLR_EnsureNSNumber(NSNumber *num) { - // If the server returned a string object where we expect a number, try - // to make a number object. - if ([num isKindOfClass:[NSString class]]) { - NSNumber *newNum; - NSString *str = (NSString *)num; - if ([str rangeOfString:@"."].location != NSNotFound) { - // This is a floating-point number. - // Force the parser to use '.' as the decimal separator. - static NSLocale *usLocale = nil; - @synchronized([GTLRUtilities class]) { - if (usLocale == nil) { - usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - } - newNum = [NSDecimalNumber decimalNumberWithString:(NSString*)num - locale:(id)usLocale]; - } - } else { - // NSDecimalNumber +decimalNumberWithString:locale: - // does not correctly create an NSNumber for large values like - // 71100000000007780. - if ([str hasPrefix:@"-"]) { - newNum = @([str longLongValue]); - } else { - const char *utf8 = str.UTF8String; - unsigned long long ull = strtoull(utf8, NULL, 10); - newNum = @(ull); - } - } - if (newNum != nil) { - num = newNum; - } - } - return num; -} diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock @@ -1,27 +1,16 @@ PODS: - - GoogleAPIClientForREST (1.3.8): - - GoogleAPIClientForREST/Core (= 1.3.8) - - GTMSessionFetcher (>= 1.1.7) - - GoogleAPIClientForREST/Core (1.3.8): - - GTMSessionFetcher (>= 1.1.7) - - GTMSessionFetcher (1.2.1): - - GTMSessionFetcher/Full (= 1.2.1) - - GTMSessionFetcher/Core (1.2.1) - - GTMSessionFetcher/Full (1.2.1): - - GTMSessionFetcher/Core (= 1.2.1) + - SheetsTranslator (0.3) DEPENDENCIES: - - GoogleAPIClientForREST (~> 1.3.8) + - SheetsTranslator (~> 0.3) SPEC REPOS: https://github.com/cocoapods/specs.git: - - GoogleAPIClientForREST - - GTMSessionFetcher + - SheetsTranslator SPEC CHECKSUMS: - GoogleAPIClientForREST: 5447a194eae517986cafe6421a5330b80b820591 - GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca + SheetsTranslator: eb4971995a2b9c5324724e51dc183137d9654ee8 -PODFILE CHECKSUM: dbc5c2766ede4673e953e610de8390e5215f6c55 +PODFILE CHECKSUM: f6bd879d2a8c3815ad086d6049bd0ec6a3540bf6 COCOAPODS: 1.6.1 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj @@ -6,199 +6,69 @@ objectVersion = 48; objects = { +/* Begin PBXAggregateTarget section */ + 7E0EB9491F5037B2F1BAF224516D448C /* SheetsTranslator */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 5880A54E2519538C10BD09593B3EFE34 /* Build configuration list for PBXAggregateTarget "SheetsTranslator" */; + buildPhases = ( + ); + dependencies = ( + ); + name = SheetsTranslator; + }; +/* End PBXAggregateTarget section */ + /* Begin PBXBuildFile section */ - 0259933BCBC56AA6C06A6C9965DAF0B1 /* GTMSessionFetcherService.m in Sources */ = {isa = PBXBuildFile; fileRef = 66AFD895BB32492C28401C014FEA5B2A /* GTMSessionFetcherService.m */; }; - 05F189E1E5B58CD99B4BD4EF466BC28F /* GTMSessionFetcherLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A37FA7630E26C1BB376757F9B56D929 /* GTMSessionFetcherLogging.m */; }; - 0A84CCA22F1C8173E6B37F2069950093 /* Pods-TeachersAssistantTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 176E21E234D1EF72C0869C40836ECBCC /* Pods-TeachersAssistantTests-dummy.m */; }; - 0BD74743A49183F02ED5A060896D395C /* GTLRQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = F60610D99772BFEF6876443BB367B406 /* GTLRQuery.m */; }; - 143CA95BCA38E98A1C953F12ED7A68C5 /* GTLRBatchResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 65F97E32937BB5235135137421037918 /* GTLRBatchResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 1E7061DAA1598078FD6F7467039FD69A /* GTLRBase64.h in Headers */ = {isa = PBXBuildFile; fileRef = C4CD076CCAAC4DDE1AD8F8D908810C14 /* GTLRBase64.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 231E72C5222861B3A2749B5999FCCD3B /* GTLRBatchQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D4EB57F9DFC922E6EC6E7FDE6B66497 /* GTLRBatchQuery.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 25018FB43685B12C22C192ACA8B195DF /* GTLRUploadParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BAC2903B8C1077CF9EECF374BCBBE01 /* GTLRUploadParameters.m */; }; - 27ADCD707AC02EE76FB6A8D9E2DA2BCB /* GTLRDuration.m in Sources */ = {isa = PBXBuildFile; fileRef = DA49BC58BF18A35373D3EA2DBF421D00 /* GTLRDuration.m */; }; - 2B77711505B6A35910051173A84BECF0 /* GTLRBatchResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 156A1A9280D93A7463591064FCCAD02F /* GTLRBatchResult.m */; }; - 305C5C21B3895A0E26391E09987C2385 /* GTLRUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 961467D2BF26CCA63537C0D3487B1DB8 /* GTLRUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 382F74A3FBE759251A5B6AB8868A4765 /* GTLRRuntimeCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 278E2C6B9FF0A381750BFB75627FFDB3 /* GTLRRuntimeCommon.m */; }; - 45B0D2B682D7C862E294F1467980E2E3 /* GTLRDateTime.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D5E10D54CC70F2A3DD8909F38107D22 /* GTLRDateTime.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 47E2FBDB54550F73850F0B39FABAC84C /* GTMSessionFetcher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7C69C428FB7737AC8AB7F9954BA14AF /* GTMSessionFetcher.framework */; }; - 4B2F27E4AAC32FDEF59BAE37A9027501 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCA4CC8015A178888F4A57DDED67433A /* Foundation.framework */; }; - 4B4ED067E7A6A0E4102A77EF6E12B6C5 /* GTLRDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = E4C698A16FDC77A62625C9DA6165582D /* GTLRDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4CDF43FD9B915EF76C6A771BA4D2C0A4 /* GTLRDateTime.m in Sources */ = {isa = PBXBuildFile; fileRef = 37E7B98E15AE2BF1DD55266099D7B651 /* GTLRDateTime.m */; }; - 4CF2FAAEE1D988441F281430D392B6BF /* GTMGatherInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = EF176B2AA0FA643A494621B8C192EDB5 /* GTMGatherInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5F50C32122B3ADA9EA0D5CE4ABEFDD78 /* GoogleAPIClientForREST-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 35B8090078E48E239D39288A69412AD5 /* GoogleAPIClientForREST-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 62B708568BAC8B6D5C9084BEC87B90E4 /* GTLRObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 2E013B0D53EF922E03F88FAFCBD9FF17 /* GTLRObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 62DB18E73656128001751C0A2F73648B /* GTLRService.h in Headers */ = {isa = PBXBuildFile; fileRef = 085E24A1AC949B7CDB4C719B3C53EA14 /* GTLRService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 66C7544B5A09F539D7D4DB6F937F61B0 /* GTMGatherInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D3AE8D1C29E34EC8CE96ACB0CDED5E5 /* GTMGatherInputStream.m */; }; - 6C571A236A1EBAA0111698DE1FC4D0A4 /* Pods-TeachersAssistantTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 029A66B7819D4FD2C4DD1328C5488419 /* Pods-TeachersAssistantTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7260CEF6719019E19D1A0D894188B05F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCA4CC8015A178888F4A57DDED67433A /* Foundation.framework */; }; - 790C8B1BED5D639EADE6BD6E45C9E8F5 /* GTLRURITemplate.h in Headers */ = {isa = PBXBuildFile; fileRef = F77C7F83EE5CFC28448551424FFA648B /* GTLRURITemplate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 7F46C4E77AF7E4C2F9751A5154F7BFEB /* GoogleAPIClientForREST-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B8F6CCC3AB370F4B07F5B1EFFF4DD0E /* GoogleAPIClientForREST-dummy.m */; }; - 825E4CB2EA00517293F381A582600B6A /* GTLRUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA885921F7C975C7E54FC245579DCDF /* GTLRUtilities.m */; }; - 877465AAAEA86697CEC56A0CC9F0AA43 /* GTMSessionFetcherLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = DA0FCEE372F04B2A01DB11789130BC5B /* GTMSessionFetcherLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F7C625A44A243746CFA5568B05E023 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCA4CC8015A178888F4A57DDED67433A /* Foundation.framework */; }; - 89D083B4302DD4BFEFD5E4EC85350D77 /* GTMMIMEDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = FD8BE7C44C98327BFA930F0F6F23D72A /* GTMMIMEDocument.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 91BF7F097ACC8FD1F9BF6248F400719A /* GTMSessionFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = D96F98D3386360D1DDE0C7ACF9E76014 /* GTMSessionFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 98FC93E3E80044F01349D3BBDC818CC0 /* GTLRBatchQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = B390CCF062FC2081F94C3B2C3E894ACC /* GTLRBatchQuery.m */; }; - AD4147888DF506ACFCCE3FA5819A425C /* GTLRObject.m in Sources */ = {isa = PBXBuildFile; fileRef = CB7EA1458FDD080BFADB7A928D369059 /* GTLRObject.m */; }; - AD645B60AF3D1B463ECE1BCC70209B7D /* GTLRErrorObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 15B773B78B52F266A0CC624DDFFAE7EF /* GTLRErrorObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B3073DFBB5CB806BCFD875B3A5D949BB /* Pods-TeachersAssistant-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 74104373A68924B948E2FD66656DDF04 /* Pods-TeachersAssistant-dummy.m */; }; - B58498DCE67D2464A50F18A12F0C6FC8 /* GTLRFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = E3726C85C7B6D56594A3E93E16CB8CD7 /* GTLRFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BBFBB062310CB7A6DBD5A9C82310C5D4 /* GTLRErrorObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 62E9F4BAC2AA0264F8658C43B4E58636 /* GTLRErrorObject.m */; }; - BE578FD68DD6E0A6369DE0A0A5404B6F /* GTLRBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = 9122A91940337F4A5E3FE7E589B77E2B /* GTLRBase64.m */; }; - C08BC708C768AB5D4A7759073374C09A /* GTLRUploadParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = E9E89C72B14FF0804BDF968943B9430E /* GTLRUploadParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C0F61C910AA6E3457E18FBD400862107 /* GTMSessionUploadFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D39543E4D61DF7D7495633B6CCB2AAB /* GTMSessionUploadFetcher.m */; }; - C5CE0F21B7E068FD8FBAADC46F7EA854 /* Pods-TeachersAssistant-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DFABC77294AE1B3C79891E913990217 /* Pods-TeachersAssistant-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CB4139747C70971C4984E7511A20146E /* GTMReadMonitorInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F8D1B4EFF5D66AE0AC686E8D76CA5A5 /* GTMReadMonitorInputStream.m */; }; - CB4E2DCF06B3563AE98E03932A5D80A0 /* GTLRDuration.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E8AB8288409C217E896484EDEB7D484 /* GTLRDuration.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D1F90521F48C8F6A488BC10751B720E1 /* GTLRQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = B9CE91C5B6E4E8D7B8A7E5FEC3AEB4BC /* GTLRQuery.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DA7B909C6F910C653E12B053843E424E /* GTLRURITemplate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0C1C1A79A5F7F06B97A360EEF3274C /* GTLRURITemplate.m */; }; - DCE0FED8DC34E000ED3EFA06C77CD76C /* GTMSessionUploadFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 01D3802510F36289EF4A957360AB8B4E /* GTMSessionUploadFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E0CB35D900D10D1C7E37F987ABFF8938 /* GTMReadMonitorInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F361C9292422B96C48EEA211001CD82 /* GTMReadMonitorInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E43064D55A1482C00F38585873D6BB48 /* GTMMIMEDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 4996B5C022E49C108C735CCD2E8FAE2B /* GTMMIMEDocument.m */; }; - E982A92F18C647036E12507A4A4389C1 /* GTMSessionFetcher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6932F6AD90C0858CB4BD08B122853D /* GTMSessionFetcher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA6D1F0D9C9CB43E1C240B7052B65CC5 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABBEE353D5FCEB9ED26E64872A4AB2B6 /* Security.framework */; }; - EA93FC0BAF02B8E5D9CA7137B8F35AD9 /* GTLRService.m in Sources */ = {isa = PBXBuildFile; fileRef = 63940FFB83E85057EEB621E27DE09111 /* GTLRService.m */; }; - EBB7AAAB95C04DC7F4B473EB68F1FD84 /* GTMSessionFetcher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = EC32701EAC620041889026F257CF930A /* GTMSessionFetcher-dummy.m */; }; - F0F798FAF8F64C21D8A06294F9CCF82B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCA4CC8015A178888F4A57DDED67433A /* Foundation.framework */; }; - F19CCC1BE7E7EFF05FE57EBE25F21414 /* GTMSessionFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = A174B22A98E274B816F8496BCC54DAB4 /* GTMSessionFetcher.m */; }; - FCB136E76490B4268311A8230BA408EA /* GTLRRuntimeCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B56C5D0A1A2E8BCAF6EC0BFE4BD9FB6 /* GTLRRuntimeCommon.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FCCB8D11147B399C0F89A9F45A138BC7 /* GTMSessionFetcherService.h in Headers */ = {isa = PBXBuildFile; fileRef = 3361F9111575058DB2A8609E3DBF9B89 /* GTMSessionFetcherService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF53FEBEA1CECB739B37F452ABE18FC2 /* GTLRFramework.m in Sources */ = {isa = PBXBuildFile; fileRef = 941A7548E49FA90C92AECE6F6F72CDD7 /* GTLRFramework.m */; }; + 0A84CCA22F1C8173E6B37F2069950093 /* Pods-TeachersAssistantTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 68969D6928EE46B31780E10AD93CFD89 /* Pods-TeachersAssistantTests-dummy.m */; }; + 59B1F1C65C698A4886896EE8FF01171B /* Pods-TeachersAssistant-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 53B1664AEDBB405D89BDC1830E4B2F0F /* Pods-TeachersAssistant-dummy.m */; }; + 6C571A236A1EBAA0111698DE1FC4D0A4 /* Pods-TeachersAssistantTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = ECFFEA04571CCE4256E9CF44018F34FD /* Pods-TeachersAssistantTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 715919889385EC1350587CE39194906C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB4607EFCA7C5F75397649E792E2AFCB /* Foundation.framework */; }; + 741713D9CE960264061601D1DE80CBD2 /* Pods-TeachersAssistant-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 72BBFEE5BC80C0DE9A031A654D125E71 /* Pods-TeachersAssistant-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F0F798FAF8F64C21D8A06294F9CCF82B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB4607EFCA7C5F75397649E792E2AFCB /* Foundation.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 558BF2DE4D4799B5794C4AFA6DB7B7F7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 70193AD59F957BCD7AC6BECC549C2C8E; - remoteInfo = GoogleAPIClientForREST; - }; - 788133539002A098132EB82ED2D1EADC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = EF3C6612CDE86D2A1A64BAA50E0787C3; - remoteInfo = GTMSessionFetcher; - }; - AB7DE924C39B46D36AF45A0CEF559572 /* PBXContainerItemProxy */ = { + 29BAD06A72020E55F7FB0D1A27D3B1E9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = EF3C6612CDE86D2A1A64BAA50E0787C3; - remoteInfo = GTMSessionFetcher; + remoteGlobalIDString = 7E0EB9491F5037B2F1BAF224516D448C; + remoteInfo = SheetsTranslator; }; CBA0C327941F96015A5D0C2A0F65E1D9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; proxyType = 1; - remoteGlobalIDString = 2B5D02F570FFD6BDFE13FF35208146B1; + remoteGlobalIDString = 8D811D2B2A5E36F51C6C9F24B939B303; remoteInfo = "Pods-TeachersAssistant"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 01D3802510F36289EF4A957360AB8B4E /* GTMSessionUploadFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionUploadFetcher.h; path = Source/GTMSessionUploadFetcher.h; sourceTree = "<group>"; }; - 029A66B7819D4FD2C4DD1328C5488419 /* Pods-TeachersAssistantTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-TeachersAssistantTests-umbrella.h"; sourceTree = "<group>"; }; - 031780C18D1D961B19C0925A08FD435D /* GTMSessionFetcher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GTMSessionFetcher-Info.plist"; sourceTree = "<group>"; }; - 085E24A1AC949B7CDB4C719B3C53EA14 /* GTLRService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRService.h; path = Source/Objects/GTLRService.h; sourceTree = "<group>"; }; - 08BCC69787B382B4AACFA48AED07D11C /* Pods_TeachersAssistant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_TeachersAssistant.framework; path = "Pods-TeachersAssistant.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; - 0D5E10D54CC70F2A3DD8909F38107D22 /* GTLRDateTime.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRDateTime.h; path = Source/Objects/GTLRDateTime.h; sourceTree = "<group>"; }; - 0F8D1B4EFF5D66AE0AC686E8D76CA5A5 /* GTMReadMonitorInputStream.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMReadMonitorInputStream.m; path = Source/GTMReadMonitorInputStream.m; sourceTree = "<group>"; }; - 118B269F032567135C30C461CA7A23AD /* Pods-TeachersAssistant.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-TeachersAssistant.modulemap"; sourceTree = "<group>"; }; - 123565447F11349B461EAA44530EFD01 /* Pods-TeachersAssistantTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistantTests-acknowledgements.plist"; sourceTree = "<group>"; }; - 156A1A9280D93A7463591064FCCAD02F /* GTLRBatchResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRBatchResult.m; path = Source/Objects/GTLRBatchResult.m; sourceTree = "<group>"; }; - 15B773B78B52F266A0CC624DDFFAE7EF /* GTLRErrorObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRErrorObject.h; path = Source/Objects/GTLRErrorObject.h; sourceTree = "<group>"; }; - 176E21E234D1EF72C0869C40836ECBCC /* Pods-TeachersAssistantTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-TeachersAssistantTests-dummy.m"; sourceTree = "<group>"; }; - 1BAC2903B8C1077CF9EECF374BCBBE01 /* GTLRUploadParameters.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRUploadParameters.m; path = Source/Objects/GTLRUploadParameters.m; sourceTree = "<group>"; }; - 278E2C6B9FF0A381750BFB75627FFDB3 /* GTLRRuntimeCommon.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRRuntimeCommon.m; path = Source/Objects/GTLRRuntimeCommon.m; sourceTree = "<group>"; }; - 28EC55CDE5ECEB600750BA9EFB6497AA /* GoogleAPIClientForREST.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GoogleAPIClientForREST.framework; path = GoogleAPIClientForREST.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 2D39543E4D61DF7D7495633B6CCB2AAB /* GTMSessionUploadFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionUploadFetcher.m; path = Source/GTMSessionUploadFetcher.m; sourceTree = "<group>"; }; - 2E013B0D53EF922E03F88FAFCBD9FF17 /* GTLRObject.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRObject.h; path = Source/Objects/GTLRObject.h; sourceTree = "<group>"; }; - 3361F9111575058DB2A8609E3DBF9B89 /* GTMSessionFetcherService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherService.h; path = Source/GTMSessionFetcherService.h; sourceTree = "<group>"; }; - 35039373C75F0CAA94D50D97AF90BF12 /* Pods-TeachersAssistantTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-TeachersAssistantTests-acknowledgements.markdown"; sourceTree = "<group>"; }; - 35B8090078E48E239D39288A69412AD5 /* GoogleAPIClientForREST-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleAPIClientForREST-umbrella.h"; sourceTree = "<group>"; }; - 37E7B98E15AE2BF1DD55266099D7B651 /* GTLRDateTime.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRDateTime.m; path = Source/Objects/GTLRDateTime.m; sourceTree = "<group>"; }; - 3DFABC77294AE1B3C79891E913990217 /* Pods-TeachersAssistant-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-TeachersAssistant-umbrella.h"; sourceTree = "<group>"; }; - 44581CF76B50941A6B5755E48A3F0B73 /* Pods-TeachersAssistantTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-TeachersAssistantTests.modulemap"; sourceTree = "<group>"; }; - 4996B5C022E49C108C735CCD2E8FAE2B /* GTMMIMEDocument.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMMIMEDocument.m; path = Source/GTMMIMEDocument.m; sourceTree = "<group>"; }; - 4A37FA7630E26C1BB376757F9B56D929 /* GTMSessionFetcherLogging.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherLogging.m; path = Source/GTMSessionFetcherLogging.m; sourceTree = "<group>"; }; - 4D65ABF8CCC3AF286C22D5B65FC16D8A /* GTMSessionFetcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GTMSessionFetcher.framework; path = GTMSessionFetcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 62E9F4BAC2AA0264F8658C43B4E58636 /* GTLRErrorObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRErrorObject.m; path = Source/Objects/GTLRErrorObject.m; sourceTree = "<group>"; }; - 63940FFB83E85057EEB621E27DE09111 /* GTLRService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRService.m; path = Source/Objects/GTLRService.m; sourceTree = "<group>"; }; - 64AFF54F34EBE50066D296C25F3EC48F /* GTMSessionFetcher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-prefix.pch"; sourceTree = "<group>"; }; - 65F97E32937BB5235135137421037918 /* GTLRBatchResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRBatchResult.h; path = Source/Objects/GTLRBatchResult.h; sourceTree = "<group>"; }; - 66AFD895BB32492C28401C014FEA5B2A /* GTMSessionFetcherService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherService.m; path = Source/GTMSessionFetcherService.m; sourceTree = "<group>"; }; - 6CBEAF0154F92F0CD942C90DCF6C7A1B /* Pods-TeachersAssistantTests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistantTests-Info.plist"; sourceTree = "<group>"; }; - 6CF6CDA458E493AD3A13C09451C84F01 /* Pods-TeachersAssistant-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-TeachersAssistant-acknowledgements.markdown"; sourceTree = "<group>"; }; - 6D3AE8D1C29E34EC8CE96ACB0CDED5E5 /* GTMGatherInputStream.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMGatherInputStream.m; path = Source/GTMGatherInputStream.m; sourceTree = "<group>"; }; - 6E8AB8288409C217E896484EDEB7D484 /* GTLRDuration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRDuration.h; path = Source/Objects/GTLRDuration.h; sourceTree = "<group>"; }; - 74104373A68924B948E2FD66656DDF04 /* Pods-TeachersAssistant-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-TeachersAssistant-dummy.m"; sourceTree = "<group>"; }; - 7D4EB57F9DFC922E6EC6E7FDE6B66497 /* GTLRBatchQuery.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRBatchQuery.h; path = Source/Objects/GTLRBatchQuery.h; sourceTree = "<group>"; }; - 80D0ABF6F63F3EF080A906F8C625412E /* Pods-TeachersAssistant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistant.debug.xcconfig"; sourceTree = "<group>"; }; - 90AE378D1FFFBA091EF3B82D4C606245 /* GoogleAPIClientForREST-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GoogleAPIClientForREST-prefix.pch"; sourceTree = "<group>"; }; - 9122A91940337F4A5E3FE7E589B77E2B /* GTLRBase64.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRBase64.m; path = Source/Utilities/GTLRBase64.m; sourceTree = "<group>"; }; - 941A7548E49FA90C92AECE6F6F72CDD7 /* GTLRFramework.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRFramework.m; path = Source/Utilities/GTLRFramework.m; sourceTree = "<group>"; }; - 961467D2BF26CCA63537C0D3487B1DB8 /* GTLRUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRUtilities.h; path = Source/Utilities/GTLRUtilities.h; sourceTree = "<group>"; }; - 96C607A0030873A86250384D32E69039 /* GoogleAPIClientForREST.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GoogleAPIClientForREST.modulemap; sourceTree = "<group>"; }; - 9B56C5D0A1A2E8BCAF6EC0BFE4BD9FB6 /* GTLRRuntimeCommon.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRRuntimeCommon.h; path = Source/Objects/GTLRRuntimeCommon.h; sourceTree = "<group>"; }; - 9B8F6CCC3AB370F4B07F5B1EFFF4DD0E /* GoogleAPIClientForREST-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GoogleAPIClientForREST-dummy.m"; sourceTree = "<group>"; }; - 9BA885921F7C975C7E54FC245579DCDF /* GTLRUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRUtilities.m; path = Source/Utilities/GTLRUtilities.m; sourceTree = "<group>"; }; - 9D0C1C1A79A5F7F06B97A360EEF3274C /* GTLRURITemplate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRURITemplate.m; path = Source/Utilities/GTLRURITemplate.m; sourceTree = "<group>"; }; + 2064F876EB35354079F4B4E7C61BB78C /* Pods-TeachersAssistantTests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistantTests-Info.plist"; sourceTree = "<group>"; }; + 2593D361D3E3CF460C4BA860F7162623 /* Pods-TeachersAssistantTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-TeachersAssistantTests.modulemap"; sourceTree = "<group>"; }; + 323EE53D0E077C0FFF05690D19B17BC0 /* Pods-TeachersAssistant-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-TeachersAssistant-acknowledgements.markdown"; sourceTree = "<group>"; }; + 487A571D8270AD9DBC4D5B4D623A17C5 /* Pods-TeachersAssistantTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistantTests-acknowledgements.plist"; sourceTree = "<group>"; }; + 51B8D5225514CB922E30EC9C225BFD60 /* SheetsTranslator.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SheetsTranslator.xcconfig; sourceTree = "<group>"; }; + 53B1664AEDBB405D89BDC1830E4B2F0F /* Pods-TeachersAssistant-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-TeachersAssistant-dummy.m"; sourceTree = "<group>"; }; + 5DE088C489A470F3DBB49C659AE00CE2 /* Pods-TeachersAssistantTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistantTests.release.xcconfig"; sourceTree = "<group>"; }; + 5EFC6BA502D17737843A18B6594EA639 /* Pods_TeachersAssistantTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_TeachersAssistantTests.framework; path = "Pods-TeachersAssistantTests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 68969D6928EE46B31780E10AD93CFD89 /* Pods-TeachersAssistantTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-TeachersAssistantTests-dummy.m"; sourceTree = "<group>"; }; + 712DF69EA41A4DDB46B20CF5E438D55F /* Pods-TeachersAssistant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistant.release.xcconfig"; sourceTree = "<group>"; }; + 72BBFEE5BC80C0DE9A031A654D125E71 /* Pods-TeachersAssistant-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-TeachersAssistant-umbrella.h"; sourceTree = "<group>"; }; + 7FEDD941F5EF3021D18B42CC63A8B064 /* Pods-TeachersAssistant-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistant-acknowledgements.plist"; sourceTree = "<group>"; }; + 8697266AF1651BBCB0E02A6818EC909A /* Pods-TeachersAssistantTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistantTests.debug.xcconfig"; sourceTree = "<group>"; }; + 93C669D461B393AB37F791633C7ECEEF /* Pods-TeachersAssistantTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-TeachersAssistantTests-acknowledgements.markdown"; sourceTree = "<group>"; }; 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9F361C9292422B96C48EEA211001CD82 /* GTMReadMonitorInputStream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMReadMonitorInputStream.h; path = Source/GTMReadMonitorInputStream.h; sourceTree = "<group>"; }; - A174B22A98E274B816F8496BCC54DAB4 /* GTMSessionFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcher.m; path = Source/GTMSessionFetcher.m; sourceTree = "<group>"; }; - A40C263DBAD4C39D2E09F730467D5085 /* Pods-TeachersAssistantTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistantTests.debug.xcconfig"; sourceTree = "<group>"; }; - A61A5FBB4C4917E85820D47EA05A7A31 /* Pods-TeachersAssistant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistant.release.xcconfig"; sourceTree = "<group>"; }; - A7C69C428FB7737AC8AB7F9954BA14AF /* GTMSessionFetcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTMSessionFetcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - A91F81008C5187F2E124CB8EC5EA8024 /* GTMSessionFetcher.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMSessionFetcher.xcconfig; sourceTree = "<group>"; }; - ABBEE353D5FCEB9ED26E64872A4AB2B6 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; - B141A2B8E0C6A4286A1DA134E884BE97 /* Pods_TeachersAssistantTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_TeachersAssistantTests.framework; path = "Pods-TeachersAssistantTests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; - B390CCF062FC2081F94C3B2C3E894ACC /* GTLRBatchQuery.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRBatchQuery.m; path = Source/Objects/GTLRBatchQuery.m; sourceTree = "<group>"; }; - B50337BA0606A576144D70B0E05ED8C1 /* Pods-TeachersAssistant-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistant-Info.plist"; sourceTree = "<group>"; }; - B9CE91C5B6E4E8D7B8A7E5FEC3AEB4BC /* GTLRQuery.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRQuery.h; path = Source/Objects/GTLRQuery.h; sourceTree = "<group>"; }; - BF6932F6AD90C0858CB4BD08B122853D /* GTMSessionFetcher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-umbrella.h"; sourceTree = "<group>"; }; - C4CD076CCAAC4DDE1AD8F8D908810C14 /* GTLRBase64.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRBase64.h; path = Source/Utilities/GTLRBase64.h; sourceTree = "<group>"; }; - CB7EA1458FDD080BFADB7A928D369059 /* GTLRObject.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRObject.m; path = Source/Objects/GTLRObject.m; sourceTree = "<group>"; }; - D036D2B7B2CE7D2BB207A2A4849EAA58 /* GoogleAPIClientForREST-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GoogleAPIClientForREST-Info.plist"; sourceTree = "<group>"; }; - D2C38B5767FC35488427CDB72E1252BE /* Pods-TeachersAssistant-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistant-acknowledgements.plist"; sourceTree = "<group>"; }; - D96F98D3386360D1DDE0C7ACF9E76014 /* GTMSessionFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcher.h; path = Source/GTMSessionFetcher.h; sourceTree = "<group>"; }; - DA0FCEE372F04B2A01DB11789130BC5B /* GTMSessionFetcherLogging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherLogging.h; path = Source/GTMSessionFetcherLogging.h; sourceTree = "<group>"; }; - DA49BC58BF18A35373D3EA2DBF421D00 /* GTLRDuration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRDuration.m; path = Source/Objects/GTLRDuration.m; sourceTree = "<group>"; }; - E3726C85C7B6D56594A3E93E16CB8CD7 /* GTLRFramework.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRFramework.h; path = Source/Utilities/GTLRFramework.h; sourceTree = "<group>"; }; - E4C698A16FDC77A62625C9DA6165582D /* GTLRDefines.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRDefines.h; path = Source/GTLRDefines.h; sourceTree = "<group>"; }; - E9E89C72B14FF0804BDF968943B9430E /* GTLRUploadParameters.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRUploadParameters.h; path = Source/Objects/GTLRUploadParameters.h; sourceTree = "<group>"; }; - EC32701EAC620041889026F257CF930A /* GTMSessionFetcher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GTMSessionFetcher-dummy.m"; sourceTree = "<group>"; }; - EF176B2AA0FA643A494621B8C192EDB5 /* GTMGatherInputStream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMGatherInputStream.h; path = Source/GTMGatherInputStream.h; sourceTree = "<group>"; }; - F529CA2B381C222BE9ECC27FA5F94111 /* Pods-TeachersAssistant-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-TeachersAssistant-frameworks.sh"; sourceTree = "<group>"; }; - F60610D99772BFEF6876443BB367B406 /* GTLRQuery.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTLRQuery.m; path = Source/Objects/GTLRQuery.m; sourceTree = "<group>"; }; - F77C7F83EE5CFC28448551424FFA648B /* GTLRURITemplate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTLRURITemplate.h; path = Source/Utilities/GTLRURITemplate.h; sourceTree = "<group>"; }; - F7C39F533C62291956B73FFBAC3BEA6A /* GoogleAPIClientForREST.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleAPIClientForREST.xcconfig; sourceTree = "<group>"; }; - FAB365696B9D3A38472FAC11B630C9CA /* GTMSessionFetcher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GTMSessionFetcher.modulemap; sourceTree = "<group>"; }; - FCA4CC8015A178888F4A57DDED67433A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - FD8BE7C44C98327BFA930F0F6F23D72A /* GTMMIMEDocument.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMMIMEDocument.h; path = Source/GTMMIMEDocument.h; sourceTree = "<group>"; }; - FE024B7689A72CA8CC54C9B407F9507C /* Pods-TeachersAssistantTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistantTests.release.xcconfig"; sourceTree = "<group>"; }; + A0AA143A60A8CCFF6198114B99E582AD /* Pods-TeachersAssistant.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-TeachersAssistant.modulemap"; sourceTree = "<group>"; }; + A484B3C6B804C2A2725BB106CCA79CD2 /* Pods-TeachersAssistant-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-TeachersAssistant-Info.plist"; sourceTree = "<group>"; }; + B69955D80F9E5A6D8CD476BCD08F9999 /* Pods-TeachersAssistant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-TeachersAssistant.debug.xcconfig"; sourceTree = "<group>"; }; + CB4607EFCA7C5F75397649E792E2AFCB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + ECFFEA04571CCE4256E9CF44018F34FD /* Pods-TeachersAssistantTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-TeachersAssistantTests-umbrella.h"; sourceTree = "<group>"; }; + F03A69F5842E504313F19AD16F34A1C2 /* Pods_TeachersAssistant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_TeachersAssistant.framework; path = "Pods-TeachersAssistant.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 054C7BD617B0509F6CFA3658F9ADA4AD /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B2F27E4AAC32FDEF59BAE37A9027501 /* Foundation.framework in Frameworks */, - 47E2FBDB54550F73850F0B39FABAC84C /* GTMSessionFetcher.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 1CC1732E7605318A00DDBDB18FDDD2A8 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 87F7C625A44A243746CFA5568B05E023 /* Foundation.framework in Frameworks */, - EA6D1F0D9C9CB43E1C240B7052B65CC5 /* Security.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 42AFFA9AC72D8A2FF83C883E890B5F2A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -207,244 +77,128 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 84EFDE6B861DAF6A18F6CEE6ACE05591 /* Frameworks */ = { + 6FEA42326538A6F122C71D093CE4F703 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7260CEF6719019E19D1A0D894188B05F /* Foundation.framework in Frameworks */, + 715919889385EC1350587CE39194906C /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0FBF9B71E9B8C5E178D8C2E26CE101D7 /* Pods-TeachersAssistantTests */ = { - isa = PBXGroup; - children = ( - 44581CF76B50941A6B5755E48A3F0B73 /* Pods-TeachersAssistantTests.modulemap */, - 35039373C75F0CAA94D50D97AF90BF12 /* Pods-TeachersAssistantTests-acknowledgements.markdown */, - 123565447F11349B461EAA44530EFD01 /* Pods-TeachersAssistantTests-acknowledgements.plist */, - 176E21E234D1EF72C0869C40836ECBCC /* Pods-TeachersAssistantTests-dummy.m */, - 6CBEAF0154F92F0CD942C90DCF6C7A1B /* Pods-TeachersAssistantTests-Info.plist */, - 029A66B7819D4FD2C4DD1328C5488419 /* Pods-TeachersAssistantTests-umbrella.h */, - A40C263DBAD4C39D2E09F730467D5085 /* Pods-TeachersAssistantTests.debug.xcconfig */, - FE024B7689A72CA8CC54C9B407F9507C /* Pods-TeachersAssistantTests.release.xcconfig */, - ); - name = "Pods-TeachersAssistantTests"; - path = "Target Support Files/Pods-TeachersAssistantTests"; - sourceTree = "<group>"; - }; - 46A909CE8B6AE14585E6AE200FB49F59 /* Support Files */ = { + 1A8FEB746EDF891F6B3D429F2BFB57E4 /* Support Files */ = { isa = PBXGroup; children = ( - 96C607A0030873A86250384D32E69039 /* GoogleAPIClientForREST.modulemap */, - F7C39F533C62291956B73FFBAC3BEA6A /* GoogleAPIClientForREST.xcconfig */, - 9B8F6CCC3AB370F4B07F5B1EFFF4DD0E /* GoogleAPIClientForREST-dummy.m */, - D036D2B7B2CE7D2BB207A2A4849EAA58 /* GoogleAPIClientForREST-Info.plist */, - 90AE378D1FFFBA091EF3B82D4C606245 /* GoogleAPIClientForREST-prefix.pch */, - 35B8090078E48E239D39288A69412AD5 /* GoogleAPIClientForREST-umbrella.h */, + 51B8D5225514CB922E30EC9C225BFD60 /* SheetsTranslator.xcconfig */, ); name = "Support Files"; - path = "../Target Support Files/GoogleAPIClientForREST"; + path = "../Target Support Files/SheetsTranslator"; sourceTree = "<group>"; }; - 4ADD1838B1ABF81938826238AA61140A /* Products */ = { + 2A82A2C7E810EB7C837684646EF1D49E /* SheetsTranslator */ = { isa = PBXGroup; children = ( - 28EC55CDE5ECEB600750BA9EFB6497AA /* GoogleAPIClientForREST.framework */, - 4D65ABF8CCC3AF286C22D5B65FC16D8A /* GTMSessionFetcher.framework */, - 08BCC69787B382B4AACFA48AED07D11C /* Pods_TeachersAssistant.framework */, - B141A2B8E0C6A4286A1DA134E884BE97 /* Pods_TeachersAssistantTests.framework */, + 1A8FEB746EDF891F6B3D429F2BFB57E4 /* Support Files */, ); - name = Products; + name = SheetsTranslator; + path = SheetsTranslator; sourceTree = "<group>"; }; - 4DEA211B00F10A3BE6AAB7ADFFA1162E /* Frameworks */ = { + 55C4487E06B8A644EBB77910FD464E2D /* Pods-TeachersAssistant */ = { isa = PBXGroup; children = ( - A7C69C428FB7737AC8AB7F9954BA14AF /* GTMSessionFetcher.framework */, - FC1A34E42428A1E2D0FCA8C6E6B9F499 /* iOS */, + A0AA143A60A8CCFF6198114B99E582AD /* Pods-TeachersAssistant.modulemap */, + 323EE53D0E077C0FFF05690D19B17BC0 /* Pods-TeachersAssistant-acknowledgements.markdown */, + 7FEDD941F5EF3021D18B42CC63A8B064 /* Pods-TeachersAssistant-acknowledgements.plist */, + 53B1664AEDBB405D89BDC1830E4B2F0F /* Pods-TeachersAssistant-dummy.m */, + A484B3C6B804C2A2725BB106CCA79CD2 /* Pods-TeachersAssistant-Info.plist */, + 72BBFEE5BC80C0DE9A031A654D125E71 /* Pods-TeachersAssistant-umbrella.h */, + B69955D80F9E5A6D8CD476BCD08F9999 /* Pods-TeachersAssistant.debug.xcconfig */, + 712DF69EA41A4DDB46B20CF5E438D55F /* Pods-TeachersAssistant.release.xcconfig */, ); - name = Frameworks; + name = "Pods-TeachersAssistant"; + path = "Target Support Files/Pods-TeachersAssistant"; sourceTree = "<group>"; }; - 53D26FAC150F45E3579F8607BA8BAD03 /* GTMSessionFetcher */ = { + 61DC4C579DBBE61D0ACC83D1C12321C9 /* Targets Support Files */ = { isa = PBXGroup; children = ( - B901D7A314C7DD3767801F689D45D7C8 /* Core */, - A39622AC4E4F6CDEE3A4E39EE5C74076 /* Full */, - AAE5785F77D8783FEB319A17F7E1E960 /* Support Files */, + 55C4487E06B8A644EBB77910FD464E2D /* Pods-TeachersAssistant */, + D6A3E3478D9653F1FED135D15CBF0957 /* Pods-TeachersAssistantTests */, ); - name = GTMSessionFetcher; - path = GTMSessionFetcher; + name = "Targets Support Files"; sourceTree = "<group>"; }; - 562E31A1C1FB3E3474A483B9DF11DE2D /* Pods */ = { + 65C8CCABB455DD939DEBE861BEFF7E3A /* Pods */ = { isa = PBXGroup; children = ( - 7302224407A36BAF248FB955CABB9FEA /* GoogleAPIClientForREST */, - 53D26FAC150F45E3579F8607BA8BAD03 /* GTMSessionFetcher */, + 2A82A2C7E810EB7C837684646EF1D49E /* SheetsTranslator */, ); name = Pods; sourceTree = "<group>"; }; - 7302224407A36BAF248FB955CABB9FEA /* GoogleAPIClientForREST */ = { - isa = PBXGroup; - children = ( - 9A2F3797269678F3B58E933D3BAEC8FC /* Core */, - 46A909CE8B6AE14585E6AE200FB49F59 /* Support Files */, - ); - name = GoogleAPIClientForREST; - path = GoogleAPIClientForREST; - sourceTree = "<group>"; - }; - 9A2F3797269678F3B58E933D3BAEC8FC /* Core */ = { + 9B055D0CFEA43187E72B03DED11F5662 /* iOS */ = { isa = PBXGroup; children = ( - C4CD076CCAAC4DDE1AD8F8D908810C14 /* GTLRBase64.h */, - 9122A91940337F4A5E3FE7E589B77E2B /* GTLRBase64.m */, - 7D4EB57F9DFC922E6EC6E7FDE6B66497 /* GTLRBatchQuery.h */, - B390CCF062FC2081F94C3B2C3E894ACC /* GTLRBatchQuery.m */, - 65F97E32937BB5235135137421037918 /* GTLRBatchResult.h */, - 156A1A9280D93A7463591064FCCAD02F /* GTLRBatchResult.m */, - 0D5E10D54CC70F2A3DD8909F38107D22 /* GTLRDateTime.h */, - 37E7B98E15AE2BF1DD55266099D7B651 /* GTLRDateTime.m */, - E4C698A16FDC77A62625C9DA6165582D /* GTLRDefines.h */, - 6E8AB8288409C217E896484EDEB7D484 /* GTLRDuration.h */, - DA49BC58BF18A35373D3EA2DBF421D00 /* GTLRDuration.m */, - 15B773B78B52F266A0CC624DDFFAE7EF /* GTLRErrorObject.h */, - 62E9F4BAC2AA0264F8658C43B4E58636 /* GTLRErrorObject.m */, - E3726C85C7B6D56594A3E93E16CB8CD7 /* GTLRFramework.h */, - 941A7548E49FA90C92AECE6F6F72CDD7 /* GTLRFramework.m */, - 2E013B0D53EF922E03F88FAFCBD9FF17 /* GTLRObject.h */, - CB7EA1458FDD080BFADB7A928D369059 /* GTLRObject.m */, - B9CE91C5B6E4E8D7B8A7E5FEC3AEB4BC /* GTLRQuery.h */, - F60610D99772BFEF6876443BB367B406 /* GTLRQuery.m */, - 9B56C5D0A1A2E8BCAF6EC0BFE4BD9FB6 /* GTLRRuntimeCommon.h */, - 278E2C6B9FF0A381750BFB75627FFDB3 /* GTLRRuntimeCommon.m */, - 085E24A1AC949B7CDB4C719B3C53EA14 /* GTLRService.h */, - 63940FFB83E85057EEB621E27DE09111 /* GTLRService.m */, - E9E89C72B14FF0804BDF968943B9430E /* GTLRUploadParameters.h */, - 1BAC2903B8C1077CF9EECF374BCBBE01 /* GTLRUploadParameters.m */, - F77C7F83EE5CFC28448551424FFA648B /* GTLRURITemplate.h */, - 9D0C1C1A79A5F7F06B97A360EEF3274C /* GTLRURITemplate.m */, - 961467D2BF26CCA63537C0D3487B1DB8 /* GTLRUtilities.h */, - 9BA885921F7C975C7E54FC245579DCDF /* GTLRUtilities.m */, + CB4607EFCA7C5F75397649E792E2AFCB /* Foundation.framework */, ); - name = Core; - sourceTree = "<group>"; - }; - A39622AC4E4F6CDEE3A4E39EE5C74076 /* Full */ = { - isa = PBXGroup; - children = ( - EF176B2AA0FA643A494621B8C192EDB5 /* GTMGatherInputStream.h */, - 6D3AE8D1C29E34EC8CE96ACB0CDED5E5 /* GTMGatherInputStream.m */, - FD8BE7C44C98327BFA930F0F6F23D72A /* GTMMIMEDocument.h */, - 4996B5C022E49C108C735CCD2E8FAE2B /* GTMMIMEDocument.m */, - 9F361C9292422B96C48EEA211001CD82 /* GTMReadMonitorInputStream.h */, - 0F8D1B4EFF5D66AE0AC686E8D76CA5A5 /* GTMReadMonitorInputStream.m */, - ); - name = Full; - sourceTree = "<group>"; - }; - AAE5785F77D8783FEB319A17F7E1E960 /* Support Files */ = { - isa = PBXGroup; - children = ( - FAB365696B9D3A38472FAC11B630C9CA /* GTMSessionFetcher.modulemap */, - A91F81008C5187F2E124CB8EC5EA8024 /* GTMSessionFetcher.xcconfig */, - EC32701EAC620041889026F257CF930A /* GTMSessionFetcher-dummy.m */, - 031780C18D1D961B19C0925A08FD435D /* GTMSessionFetcher-Info.plist */, - 64AFF54F34EBE50066D296C25F3EC48F /* GTMSessionFetcher-prefix.pch */, - BF6932F6AD90C0858CB4BD08B122853D /* GTMSessionFetcher-umbrella.h */, - ); - name = "Support Files"; - path = "../Target Support Files/GTMSessionFetcher"; - sourceTree = "<group>"; - }; - AEB6E5ED4592D8BEF6808CBB5ABD44EA /* Pods-TeachersAssistant */ = { - isa = PBXGroup; - children = ( - 118B269F032567135C30C461CA7A23AD /* Pods-TeachersAssistant.modulemap */, - 6CF6CDA458E493AD3A13C09451C84F01 /* Pods-TeachersAssistant-acknowledgements.markdown */, - D2C38B5767FC35488427CDB72E1252BE /* Pods-TeachersAssistant-acknowledgements.plist */, - 74104373A68924B948E2FD66656DDF04 /* Pods-TeachersAssistant-dummy.m */, - F529CA2B381C222BE9ECC27FA5F94111 /* Pods-TeachersAssistant-frameworks.sh */, - B50337BA0606A576144D70B0E05ED8C1 /* Pods-TeachersAssistant-Info.plist */, - 3DFABC77294AE1B3C79891E913990217 /* Pods-TeachersAssistant-umbrella.h */, - 80D0ABF6F63F3EF080A906F8C625412E /* Pods-TeachersAssistant.debug.xcconfig */, - A61A5FBB4C4917E85820D47EA05A7A31 /* Pods-TeachersAssistant.release.xcconfig */, - ); - name = "Pods-TeachersAssistant"; - path = "Target Support Files/Pods-TeachersAssistant"; + name = iOS; sourceTree = "<group>"; }; - B901D7A314C7DD3767801F689D45D7C8 /* Core */ = { + CD293B6EBFE3AE45C72CAC803ABB0B8C /* Products */ = { isa = PBXGroup; children = ( - D96F98D3386360D1DDE0C7ACF9E76014 /* GTMSessionFetcher.h */, - A174B22A98E274B816F8496BCC54DAB4 /* GTMSessionFetcher.m */, - DA0FCEE372F04B2A01DB11789130BC5B /* GTMSessionFetcherLogging.h */, - 4A37FA7630E26C1BB376757F9B56D929 /* GTMSessionFetcherLogging.m */, - 3361F9111575058DB2A8609E3DBF9B89 /* GTMSessionFetcherService.h */, - 66AFD895BB32492C28401C014FEA5B2A /* GTMSessionFetcherService.m */, - 01D3802510F36289EF4A957360AB8B4E /* GTMSessionUploadFetcher.h */, - 2D39543E4D61DF7D7495633B6CCB2AAB /* GTMSessionUploadFetcher.m */, + F03A69F5842E504313F19AD16F34A1C2 /* Pods_TeachersAssistant.framework */, + 5EFC6BA502D17737843A18B6594EA639 /* Pods_TeachersAssistantTests.framework */, ); - name = Core; + name = Products; sourceTree = "<group>"; }; CF1408CF629C7361332E53B88F7BD30C = { isa = PBXGroup; children = ( 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - 4DEA211B00F10A3BE6AAB7ADFFA1162E /* Frameworks */, - 562E31A1C1FB3E3474A483B9DF11DE2D /* Pods */, - 4ADD1838B1ABF81938826238AA61140A /* Products */, - E201F6F1A7795FB7DB590924C510BDB4 /* Targets Support Files */, + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, + 65C8CCABB455DD939DEBE861BEFF7E3A /* Pods */, + CD293B6EBFE3AE45C72CAC803ABB0B8C /* Products */, + 61DC4C579DBBE61D0ACC83D1C12321C9 /* Targets Support Files */, ); sourceTree = "<group>"; }; - E201F6F1A7795FB7DB590924C510BDB4 /* Targets Support Files */ = { + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { isa = PBXGroup; children = ( - AEB6E5ED4592D8BEF6808CBB5ABD44EA /* Pods-TeachersAssistant */, - 0FBF9B71E9B8C5E178D8C2E26CE101D7 /* Pods-TeachersAssistantTests */, + 9B055D0CFEA43187E72B03DED11F5662 /* iOS */, ); - name = "Targets Support Files"; + name = Frameworks; sourceTree = "<group>"; }; - FC1A34E42428A1E2D0FCA8C6E6B9F499 /* iOS */ = { + D6A3E3478D9653F1FED135D15CBF0957 /* Pods-TeachersAssistantTests */ = { isa = PBXGroup; children = ( - FCA4CC8015A178888F4A57DDED67433A /* Foundation.framework */, - ABBEE353D5FCEB9ED26E64872A4AB2B6 /* Security.framework */, + 2593D361D3E3CF460C4BA860F7162623 /* Pods-TeachersAssistantTests.modulemap */, + 93C669D461B393AB37F791633C7ECEEF /* Pods-TeachersAssistantTests-acknowledgements.markdown */, + 487A571D8270AD9DBC4D5B4D623A17C5 /* Pods-TeachersAssistantTests-acknowledgements.plist */, + 68969D6928EE46B31780E10AD93CFD89 /* Pods-TeachersAssistantTests-dummy.m */, + 2064F876EB35354079F4B4E7C61BB78C /* Pods-TeachersAssistantTests-Info.plist */, + ECFFEA04571CCE4256E9CF44018F34FD /* Pods-TeachersAssistantTests-umbrella.h */, + 8697266AF1651BBCB0E02A6818EC909A /* Pods-TeachersAssistantTests.debug.xcconfig */, + 5DE088C489A470F3DBB49C659AE00CE2 /* Pods-TeachersAssistantTests.release.xcconfig */, ); - name = iOS; + name = "Pods-TeachersAssistantTests"; + path = "Target Support Files/Pods-TeachersAssistantTests"; sourceTree = "<group>"; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - 3BB78E3A97AF72FDFF27F408456FB45F /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 4CF2FAAEE1D988441F281430D392B6BF /* GTMGatherInputStream.h in Headers */, - 89D083B4302DD4BFEFD5E4EC85350D77 /* GTMMIMEDocument.h in Headers */, - E0CB35D900D10D1C7E37F987ABFF8938 /* GTMReadMonitorInputStream.h in Headers */, - E982A92F18C647036E12507A4A4389C1 /* GTMSessionFetcher-umbrella.h in Headers */, - 91BF7F097ACC8FD1F9BF6248F400719A /* GTMSessionFetcher.h in Headers */, - 877465AAAEA86697CEC56A0CC9F0AA43 /* GTMSessionFetcherLogging.h in Headers */, - FCCB8D11147B399C0F89A9F45A138BC7 /* GTMSessionFetcherService.h in Headers */, - DCE0FED8DC34E000ED3EFA06C77CD76C /* GTMSessionUploadFetcher.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6BE7497F191CA5AF4A354E7BEEEC0105 /* Headers */ = { + 4757D7BA0C878B51A903935268434217 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - C5CE0F21B7E068FD8FBAADC46F7EA854 /* Pods-TeachersAssistant-umbrella.h in Headers */, + 741713D9CE960264061601D1DE80CBD2 /* Pods-TeachersAssistant-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -456,69 +210,26 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F57B334B60CEEAA7B2C1B79D66162B36 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 5F50C32122B3ADA9EA0D5CE4ABEFDD78 /* GoogleAPIClientForREST-umbrella.h in Headers */, - 1E7061DAA1598078FD6F7467039FD69A /* GTLRBase64.h in Headers */, - 231E72C5222861B3A2749B5999FCCD3B /* GTLRBatchQuery.h in Headers */, - 143CA95BCA38E98A1C953F12ED7A68C5 /* GTLRBatchResult.h in Headers */, - 45B0D2B682D7C862E294F1467980E2E3 /* GTLRDateTime.h in Headers */, - 4B4ED067E7A6A0E4102A77EF6E12B6C5 /* GTLRDefines.h in Headers */, - CB4E2DCF06B3563AE98E03932A5D80A0 /* GTLRDuration.h in Headers */, - AD645B60AF3D1B463ECE1BCC70209B7D /* GTLRErrorObject.h in Headers */, - B58498DCE67D2464A50F18A12F0C6FC8 /* GTLRFramework.h in Headers */, - 62B708568BAC8B6D5C9084BEC87B90E4 /* GTLRObject.h in Headers */, - D1F90521F48C8F6A488BC10751B720E1 /* GTLRQuery.h in Headers */, - FCB136E76490B4268311A8230BA408EA /* GTLRRuntimeCommon.h in Headers */, - 62DB18E73656128001751C0A2F73648B /* GTLRService.h in Headers */, - C08BC708C768AB5D4A7759073374C09A /* GTLRUploadParameters.h in Headers */, - 790C8B1BED5D639EADE6BD6E45C9E8F5 /* GTLRURITemplate.h in Headers */, - 305C5C21B3895A0E26391E09987C2385 /* GTLRUtilities.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - 2B5D02F570FFD6BDFE13FF35208146B1 /* Pods-TeachersAssistant */ = { + 8D811D2B2A5E36F51C6C9F24B939B303 /* Pods-TeachersAssistant */ = { isa = PBXNativeTarget; - buildConfigurationList = 738722911C991930027B35774521B7F9 /* Build configuration list for PBXNativeTarget "Pods-TeachersAssistant" */; + buildConfigurationList = 064CE142CCBB7F754EF5A8095B7D635B /* Build configuration list for PBXNativeTarget "Pods-TeachersAssistant" */; buildPhases = ( - 6BE7497F191CA5AF4A354E7BEEEC0105 /* Headers */, - 47F618A83F8527BFEAF513CB44C26867 /* Sources */, - 84EFDE6B861DAF6A18F6CEE6ACE05591 /* Frameworks */, - 242EC4AB351D90DCC21C39E1F9B1FD68 /* Resources */, + 4757D7BA0C878B51A903935268434217 /* Headers */, + BBF59520F1D674767EC5984FCC1ED01B /* Sources */, + 6FEA42326538A6F122C71D093CE4F703 /* Frameworks */, + B105B84BC8B28A4BDC49D77F27348E44 /* Resources */, ); buildRules = ( ); dependencies = ( - 062F472295E0FB6D7AEA72EAA2A54156 /* PBXTargetDependency */, - FAA4071411D935EB8CCDAD0B3F25F868 /* PBXTargetDependency */, + BFCF6E73F0621150E21114BC07948F2E /* PBXTargetDependency */, ); name = "Pods-TeachersAssistant"; productName = "Pods-TeachersAssistant"; - productReference = 08BCC69787B382B4AACFA48AED07D11C /* Pods_TeachersAssistant.framework */; - productType = "com.apple.product-type.framework"; - }; - 70193AD59F957BCD7AC6BECC549C2C8E /* GoogleAPIClientForREST */ = { - isa = PBXNativeTarget; - buildConfigurationList = 280AF7B639A1AE6C203ADF301996EC83 /* Build configuration list for PBXNativeTarget "GoogleAPIClientForREST" */; - buildPhases = ( - F57B334B60CEEAA7B2C1B79D66162B36 /* Headers */, - F4A7D16270832B0EC1CD7126AC4291C8 /* Sources */, - 054C7BD617B0509F6CFA3658F9ADA4AD /* Frameworks */, - 91E9E7E50119719FC109E982FE76042C /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 7805F743CD08543910A00715F8F6921A /* PBXTargetDependency */, - ); - name = GoogleAPIClientForREST; - productName = GoogleAPIClientForREST; - productReference = 28EC55CDE5ECEB600750BA9EFB6497AA /* GoogleAPIClientForREST.framework */; + productReference = F03A69F5842E504313F19AD16F34A1C2 /* Pods_TeachersAssistant.framework */; productType = "com.apple.product-type.framework"; }; 9115383C4EEFD422CC2FA6BC0AEEF0EE /* Pods-TeachersAssistantTests */ = { @@ -537,25 +248,7 @@ ); name = "Pods-TeachersAssistantTests"; productName = "Pods-TeachersAssistantTests"; - productReference = B141A2B8E0C6A4286A1DA134E884BE97 /* Pods_TeachersAssistantTests.framework */; - productType = "com.apple.product-type.framework"; - }; - EF3C6612CDE86D2A1A64BAA50E0787C3 /* GTMSessionFetcher */ = { - isa = PBXNativeTarget; - buildConfigurationList = F69C5E3EF0EDB7B90F7ACAC42BB21610 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */; - buildPhases = ( - 3BB78E3A97AF72FDFF27F408456FB45F /* Headers */, - C3138F29F9B820D51CE987A72EAFF7C7 /* Sources */, - 1CC1732E7605318A00DDBDB18FDDD2A8 /* Frameworks */, - 50BECB9E27710FF42974E7AE9A6E7F43 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = GTMSessionFetcher; - productName = GTMSessionFetcher; - productReference = 4D65ABF8CCC3AF286C22D5B65FC16D8A /* GTMSessionFetcher.framework */; + productReference = 5EFC6BA502D17737843A18B6594EA639 /* Pods_TeachersAssistantTests.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ @@ -575,14 +268,13 @@ en, ); mainGroup = CF1408CF629C7361332E53B88F7BD30C; - productRefGroup = 4ADD1838B1ABF81938826238AA61140A /* Products */; + productRefGroup = CD293B6EBFE3AE45C72CAC803ABB0B8C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 70193AD59F957BCD7AC6BECC549C2C8E /* GoogleAPIClientForREST */, - EF3C6612CDE86D2A1A64BAA50E0787C3 /* GTMSessionFetcher */, - 2B5D02F570FFD6BDFE13FF35208146B1 /* Pods-TeachersAssistant */, + 8D811D2B2A5E36F51C6C9F24B939B303 /* Pods-TeachersAssistant */, 9115383C4EEFD422CC2FA6BC0AEEF0EE /* Pods-TeachersAssistantTests */, + 7E0EB9491F5037B2F1BAF224516D448C /* SheetsTranslator */, ); }; /* End PBXProject section */ @@ -595,21 +287,7 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 242EC4AB351D90DCC21C39E1F9B1FD68 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 50BECB9E27710FF42974E7AE9A6E7F43 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 91E9E7E50119719FC109E982FE76042C /* Resources */ = { + B105B84BC8B28A4BDC49D77F27348E44 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -619,48 +297,11 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 47F618A83F8527BFEAF513CB44C26867 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B3073DFBB5CB806BCFD875B3A5D949BB /* Pods-TeachersAssistant-dummy.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C3138F29F9B820D51CE987A72EAFF7C7 /* Sources */ = { + BBF59520F1D674767EC5984FCC1ED01B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 66C7544B5A09F539D7D4DB6F937F61B0 /* GTMGatherInputStream.m in Sources */, - E43064D55A1482C00F38585873D6BB48 /* GTMMIMEDocument.m in Sources */, - CB4139747C70971C4984E7511A20146E /* GTMReadMonitorInputStream.m in Sources */, - EBB7AAAB95C04DC7F4B473EB68F1FD84 /* GTMSessionFetcher-dummy.m in Sources */, - F19CCC1BE7E7EFF05FE57EBE25F21414 /* GTMSessionFetcher.m in Sources */, - 05F189E1E5B58CD99B4BD4EF466BC28F /* GTMSessionFetcherLogging.m in Sources */, - 0259933BCBC56AA6C06A6C9965DAF0B1 /* GTMSessionFetcherService.m in Sources */, - C0F61C910AA6E3457E18FBD400862107 /* GTMSessionUploadFetcher.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F4A7D16270832B0EC1CD7126AC4291C8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7F46C4E77AF7E4C2F9751A5154F7BFEB /* GoogleAPIClientForREST-dummy.m in Sources */, - BE578FD68DD6E0A6369DE0A0A5404B6F /* GTLRBase64.m in Sources */, - 98FC93E3E80044F01349D3BBDC818CC0 /* GTLRBatchQuery.m in Sources */, - 2B77711505B6A35910051173A84BECF0 /* GTLRBatchResult.m in Sources */, - 4CDF43FD9B915EF76C6A771BA4D2C0A4 /* GTLRDateTime.m in Sources */, - 27ADCD707AC02EE76FB6A8D9E2DA2BCB /* GTLRDuration.m in Sources */, - BBFBB062310CB7A6DBD5A9C82310C5D4 /* GTLRErrorObject.m in Sources */, - FF53FEBEA1CECB739B37F452ABE18FC2 /* GTLRFramework.m in Sources */, - AD4147888DF506ACFCCE3FA5819A425C /* GTLRObject.m in Sources */, - 0BD74743A49183F02ED5A060896D395C /* GTLRQuery.m in Sources */, - 382F74A3FBE759251A5B6AB8868A4765 /* GTLRRuntimeCommon.m in Sources */, - EA93FC0BAF02B8E5D9CA7137B8F35AD9 /* GTLRService.m in Sources */, - 25018FB43685B12C22C192ACA8B195DF /* GTLRUploadParameters.m in Sources */, - DA7B909C6F910C653E12B053843E424E /* GTLRURITemplate.m in Sources */, - 825E4CB2EA00517293F381A582600B6A /* GTLRUtilities.m in Sources */, + 59B1F1C65C698A4886896EE8FF01171B /* Pods-TeachersAssistant-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -675,64 +316,21 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 062F472295E0FB6D7AEA72EAA2A54156 /* PBXTargetDependency */ = { + BFCF6E73F0621150E21114BC07948F2E /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = GTMSessionFetcher; - target = EF3C6612CDE86D2A1A64BAA50E0787C3 /* GTMSessionFetcher */; - targetProxy = AB7DE924C39B46D36AF45A0CEF559572 /* PBXContainerItemProxy */; - }; - 7805F743CD08543910A00715F8F6921A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = GTMSessionFetcher; - target = EF3C6612CDE86D2A1A64BAA50E0787C3 /* GTMSessionFetcher */; - targetProxy = 788133539002A098132EB82ED2D1EADC /* PBXContainerItemProxy */; + name = SheetsTranslator; + target = 7E0EB9491F5037B2F1BAF224516D448C /* SheetsTranslator */; + targetProxy = 29BAD06A72020E55F7FB0D1A27D3B1E9 /* PBXContainerItemProxy */; }; EB4921EFA7B1ACE9CB14B01E8D6AA7B9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "Pods-TeachersAssistant"; - target = 2B5D02F570FFD6BDFE13FF35208146B1 /* Pods-TeachersAssistant */; + target = 8D811D2B2A5E36F51C6C9F24B939B303 /* Pods-TeachersAssistant */; targetProxy = CBA0C327941F96015A5D0C2A0F65E1D9 /* PBXContainerItemProxy */; }; - FAA4071411D935EB8CCDAD0B3F25F868 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = GoogleAPIClientForREST; - target = 70193AD59F957BCD7AC6BECC549C2C8E /* GoogleAPIClientForREST */; - targetProxy = 558BF2DE4D4799B5794C4AFA6DB7B7F7 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 026F7DC614C89E01C44B24058B56A1D5 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F7C39F533C62291956B73FFBAC3BEA6A /* GoogleAPIClientForREST.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap"; - PRODUCT_MODULE_NAME = GoogleAPIClientForREST; - PRODUCT_NAME = GoogleAPIClientForREST; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; 07488D4657FB0A78086563621D425F8A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -797,10 +395,11 @@ }; name = Debug; }; - 46992557C544161799C77D65D9095352 /* Release */ = { + 2135025F2E1F20E99AC042D3AD097488 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F7C39F533C62291956B73FFBAC3BEA6A /* GoogleAPIClientForREST.xcconfig */; + baseConfigurationReference = B69955D80F9E5A6D8CD476BCD08F9999 /* Pods-TeachersAssistant.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -810,59 +409,41 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-Info.plist"; + INFOPLIST_FILE = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap"; - PRODUCT_MODULE_NAME = GoogleAPIClientForREST; - PRODUCT_NAME = GoogleAPIClientForREST; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Release; + name = Debug; }; - 6BA1E5F08380297D3623BD359BB4E6CC /* Debug */ = { + 504142585A18DA7EAB9AB20C2A48BFE2 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A91F81008C5187F2E124CB8EC5EA8024 /* GTMSessionFetcher.xcconfig */; + baseConfigurationReference = 51B8D5225514CB922E30EC9C225BFD60 /* SheetsTranslator.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; - PRODUCT_MODULE_NAME = GTMSessionFetcher; - PRODUCT_NAME = GTMSessionFetcher; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; }; name = Debug; }; - 98BEBE48302FFCD74988E05AE3ECA56B /* Release */ = { + 56BB3CB4152C8C6474893CF98A133625 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A61A5FBB4C4917E85820D47EA05A7A31 /* Pods-TeachersAssistant.release.xcconfig */; + baseConfigurationReference = 712DF69EA41A4DDB46B20CF5E438D55F /* Pods-TeachersAssistant.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CODE_SIGN_IDENTITY = ""; @@ -894,6 +475,20 @@ }; name = Release; }; + 975D1511491E64104E41C764AEAF7766 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 51B8D5225514CB922E30EC9C225BFD60 /* SheetsTranslator.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; A1962E6FF39BBAC201A2E5DDF99557DF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -956,7 +551,7 @@ }; A91C1089EC051EA16D3436CB75BBDD4E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A40C263DBAD4C39D2E09F730467D5085 /* Pods-TeachersAssistantTests.debug.xcconfig */; + baseConfigurationReference = 8697266AF1651BBCB0E02A6818EC909A /* Pods-TeachersAssistantTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CODE_SIGN_IDENTITY = ""; @@ -987,74 +582,9 @@ }; name = Debug; }; - A93163473AD32534E87956099A47F219 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = A91F81008C5187F2E124CB8EC5EA8024 /* GTMSessionFetcher.xcconfig */; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; - PRODUCT_MODULE_NAME = GTMSessionFetcher; - PRODUCT_NAME = GTMSessionFetcher; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - A9F245220578F41B9636AC6B1C6372B2 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 80D0ABF6F63F3EF080A906F8C625412E /* Pods-TeachersAssistant.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; F045264B613674F3119ECB97E01F2E61 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FE024B7689A72CA8CC54C9B407F9507C /* Pods-TeachersAssistantTests.release.xcconfig */; + baseConfigurationReference = 5DE088C489A470F3DBB49C659AE00CE2 /* Pods-TeachersAssistantTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CODE_SIGN_IDENTITY = ""; @@ -1089,11 +619,11 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 280AF7B639A1AE6C203ADF301996EC83 /* Build configuration list for PBXNativeTarget "GoogleAPIClientForREST" */ = { + 064CE142CCBB7F754EF5A8095B7D635B /* Build configuration list for PBXNativeTarget "Pods-TeachersAssistant" */ = { isa = XCConfigurationList; buildConfigurations = ( - 026F7DC614C89E01C44B24058B56A1D5 /* Debug */, - 46992557C544161799C77D65D9095352 /* Release */, + 2135025F2E1F20E99AC042D3AD097488 /* Debug */, + 56BB3CB4152C8C6474893CF98A133625 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -1116,20 +646,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 738722911C991930027B35774521B7F9 /* Build configuration list for PBXNativeTarget "Pods-TeachersAssistant" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A9F245220578F41B9636AC6B1C6372B2 /* Debug */, - 98BEBE48302FFCD74988E05AE3ECA56B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F69C5E3EF0EDB7B90F7ACAC42BB21610 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */ = { + 5880A54E2519538C10BD09593B3EFE34 /* Build configuration list for PBXAggregateTarget "SheetsTranslator" */ = { isa = XCConfigurationList; buildConfigurations = ( - 6BA1E5F08380297D3623BD359BB4E6CC /* Debug */, - A93163473AD32534E87956099A47F219 /* Release */, + 504142585A18DA7EAB9AB20C2A48BFE2 /* Debug */, + 975D1511491E64104E41C764AEAF7766 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<Scheme - LastUpgradeVersion = "0930" - version = "1.3"> - <BuildAction - parallelizeBuildables = "YES" - buildImplicitDependencies = "YES"> - <BuildActionEntries> - <BuildActionEntry - buildForAnalyzing = "YES" - buildForTesting = "YES" - buildForRunning = "YES" - buildForProfiling = "YES" - buildForArchiving = "YES"> - <BuildableReference - BuildableIdentifier = "primary" - BlueprintIdentifier = "EF3C6612CDE86D2A1A64BAA50E0787C3" - BuildableName = "GTMSessionFetcher.framework" - BlueprintName = "GTMSessionFetcher" - ReferencedContainer = "container:Pods.xcodeproj"> - </BuildableReference> - </BuildActionEntry> - </BuildActionEntries> - </BuildAction> - <TestAction - selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" - selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - buildConfiguration = "Debug"> - <AdditionalOptions> - </AdditionalOptions> - </TestAction> - <LaunchAction - selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" - selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - launchStyle = "0" - useCustomWorkingDirectory = "NO" - ignoresPersistentStateOnLaunch = "NO" - debugDocumentVersioning = "YES" - debugServiceExtension = "internal" - buildConfiguration = "Debug" - allowLocationSimulation = "YES"> - <AdditionalOptions> - </AdditionalOptions> - </LaunchAction> - <ProfileAction - savedToolIdentifier = "" - useCustomWorkingDirectory = "NO" - debugDocumentVersioning = "YES" - buildConfiguration = "Release" - shouldUseLaunchSchemeArgsEnv = "YES"> - </ProfileAction> - <AnalyzeAction - buildConfiguration = "Debug"> - </AnalyzeAction> - <ArchiveAction - buildConfiguration = "Release" - revealArchiveInOrganizer = "YES"> - </ArchiveAction> -</Scheme> diff --git a/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/GoogleAPIClientForREST.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/GoogleAPIClientForREST.xcscheme @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<Scheme - LastUpgradeVersion = "0930" - version = "1.3"> - <BuildAction - parallelizeBuildables = "YES" - buildImplicitDependencies = "YES"> - <BuildActionEntries> - <BuildActionEntry - buildForAnalyzing = "YES" - buildForTesting = "YES" - buildForRunning = "YES" - buildForProfiling = "YES" - buildForArchiving = "YES"> - <BuildableReference - BuildableIdentifier = "primary" - BlueprintIdentifier = "70193AD59F957BCD7AC6BECC549C2C8E" - BuildableName = "GoogleAPIClientForREST.framework" - BlueprintName = "GoogleAPIClientForREST" - ReferencedContainer = "container:Pods.xcodeproj"> - </BuildableReference> - </BuildActionEntry> - </BuildActionEntries> - </BuildAction> - <TestAction - selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" - selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - buildConfiguration = "Debug"> - <AdditionalOptions> - </AdditionalOptions> - </TestAction> - <LaunchAction - selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" - selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - launchStyle = "0" - useCustomWorkingDirectory = "NO" - ignoresPersistentStateOnLaunch = "NO" - debugDocumentVersioning = "YES" - debugServiceExtension = "internal" - buildConfiguration = "Debug" - allowLocationSimulation = "YES"> - <AdditionalOptions> - </AdditionalOptions> - </LaunchAction> - <ProfileAction - savedToolIdentifier = "" - useCustomWorkingDirectory = "NO" - debugDocumentVersioning = "YES" - buildConfiguration = "Release" - shouldUseLaunchSchemeArgsEnv = "YES"> - </ProfileAction> - <AnalyzeAction - buildConfiguration = "Debug"> - </AnalyzeAction> - <ArchiveAction - buildConfiguration = "Release" - revealArchiveInOrganizer = "YES"> - </ArchiveAction> -</Scheme> diff --git a/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/Pods-TeachersAssistant.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/Pods-TeachersAssistant.xcscheme @@ -14,7 +14,7 @@ buildForAnalyzing = "YES"> <BuildableReference BuildableIdentifier = "primary" - BlueprintIdentifier = "2B5D02F570FFD6BDFE13FF35208146B1" + BlueprintIdentifier = "8D811D2B2A5E36F51C6C9F24B939B303" BuildableName = "Pods_TeachersAssistant.framework" BlueprintName = "Pods-TeachersAssistant" ReferencedContainer = "container:Pods.xcodeproj"> @@ -47,7 +47,7 @@ <MacroExpansion> <BuildableReference BuildableIdentifier = "primary" - BlueprintIdentifier = "2B5D02F570FFD6BDFE13FF35208146B1" + BlueprintIdentifier = "8D811D2B2A5E36F51C6C9F24B939B303" BuildableName = "Pods_TeachersAssistant.framework" BlueprintName = "Pods-TeachersAssistant" ReferencedContainer = "container:Pods.xcodeproj"> diff --git a/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/SheetsTranslator.xcscheme b/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/SheetsTranslator.xcscheme @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0930" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForAnalyzing = "YES" + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "7E0EB9491F5037B2F1BAF224516D448C" + BuildableName = "SheetsTranslator" + BlueprintName = "SheetsTranslator" + ReferencedContainer = "container:Pods.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + buildConfiguration = "Debug"> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + buildConfiguration = "Debug" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES" + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/xcschememanagement.plist b/Pods/Pods.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,33 +4,26 @@ <dict> <key>SchemeUserState</key> <dict> - <key>GTMSessionFetcher.xcscheme</key> - <dict> - <key>isShown</key> - <false/> - <key>orderHint</key> - <integer>1</integer> - </dict> - <key>GoogleAPIClientForREST.xcscheme</key> + <key>Pods-TeachersAssistant.xcscheme</key> <dict> <key>isShown</key> <false/> <key>orderHint</key> <integer>0</integer> </dict> - <key>Pods-TeachersAssistant.xcscheme</key> + <key>Pods-TeachersAssistantTests.xcscheme</key> <dict> <key>isShown</key> <false/> <key>orderHint</key> - <integer>2</integer> + <integer>1</integer> </dict> - <key>Pods-TeachersAssistantTests.xcscheme</key> + <key>SheetsTranslator.xcscheme</key> <dict> <key>isShown</key> <false/> <key>orderHint</key> - <integer>3</integer> + <integer>2</integer> </dict> </dict> <key>SuppressBuildableAutocreation</key> diff --git a/Pods/SheetsTranslator/LICENSE b/Pods/SheetsTranslator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Droids On Roids + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Pods/SheetsTranslator/sheets b/Pods/SheetsTranslator/sheets Binary files differ. diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> -<dict> - <key>CFBundleDevelopmentRegion</key> - <string>en</string> - <key>CFBundleExecutable</key> - <string>${EXECUTABLE_NAME}</string> - <key>CFBundleIdentifier</key> - <string>${PRODUCT_BUNDLE_IDENTIFIER}</string> - <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> - <key>CFBundleName</key> - <string>${PRODUCT_NAME}</string> - <key>CFBundlePackageType</key> - <string>FMWK</string> - <key>CFBundleShortVersionString</key> - <string>1.2.1</string> - <key>CFBundleSignature</key> - <string>????</string> - <key>CFBundleVersion</key> - <string>${CURRENT_PROJECT_VERSION}</string> - <key>NSPrincipalClass</key> - <string></string> -</dict> -</plist> diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m @@ -1,5 +0,0 @@ -#import <Foundation/Foundation.h> -@interface PodsDummy_GTMSessionFetcher : NSObject -@end -@implementation PodsDummy_GTMSessionFetcher -@end diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch @@ -1,12 +0,0 @@ -#ifdef __OBJC__ -#import <UIKit/UIKit.h> -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h @@ -1,23 +0,0 @@ -#ifdef __OBJC__ -#import <UIKit/UIKit.h> -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - -#import "GTMSessionFetcher.h" -#import "GTMSessionFetcherLogging.h" -#import "GTMSessionFetcherService.h" -#import "GTMSessionUploadFetcher.h" -#import "GTMGatherInputStream.h" -#import "GTMMIMEDocument.h" -#import "GTMReadMonitorInputStream.h" - -FOUNDATION_EXPORT double GTMSessionFetcherVersionNumber; -FOUNDATION_EXPORT const unsigned char GTMSessionFetcherVersionString[]; - diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap @@ -1,6 +0,0 @@ -framework module GTMSessionFetcher { - umbrella header "GTMSessionFetcher-umbrella.h" - - export * - module * { export * } -} diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.xcconfig b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.xcconfig @@ -1,9 +0,0 @@ -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -OTHER_LDFLAGS = $(inherited) -framework "Security" -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/GTMSessionFetcher -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES diff --git a/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-Info.plist b/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-Info.plist @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> -<dict> - <key>CFBundleDevelopmentRegion</key> - <string>en</string> - <key>CFBundleExecutable</key> - <string>${EXECUTABLE_NAME}</string> - <key>CFBundleIdentifier</key> - <string>${PRODUCT_BUNDLE_IDENTIFIER}</string> - <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> - <key>CFBundleName</key> - <string>${PRODUCT_NAME}</string> - <key>CFBundlePackageType</key> - <string>FMWK</string> - <key>CFBundleShortVersionString</key> - <string>1.3.8</string> - <key>CFBundleSignature</key> - <string>????</string> - <key>CFBundleVersion</key> - <string>${CURRENT_PROJECT_VERSION}</string> - <key>NSPrincipalClass</key> - <string></string> -</dict> -</plist> diff --git a/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-dummy.m b/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-dummy.m @@ -1,5 +0,0 @@ -#import <Foundation/Foundation.h> -@interface PodsDummy_GoogleAPIClientForREST : NSObject -@end -@implementation PodsDummy_GoogleAPIClientForREST -@end diff --git a/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-prefix.pch b/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-prefix.pch @@ -1,12 +0,0 @@ -#ifdef __OBJC__ -#import <UIKit/UIKit.h> -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - diff --git a/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-umbrella.h b/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST-umbrella.h @@ -1,31 +0,0 @@ -#ifdef __OBJC__ -#import <UIKit/UIKit.h> -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - -#import "GTLRDefines.h" -#import "GTLRBatchQuery.h" -#import "GTLRBatchResult.h" -#import "GTLRDateTime.h" -#import "GTLRDuration.h" -#import "GTLRErrorObject.h" -#import "GTLRObject.h" -#import "GTLRQuery.h" -#import "GTLRRuntimeCommon.h" -#import "GTLRService.h" -#import "GTLRUploadParameters.h" -#import "GTLRBase64.h" -#import "GTLRFramework.h" -#import "GTLRURITemplate.h" -#import "GTLRUtilities.h" - -FOUNDATION_EXPORT double GoogleAPIClientForRESTVersionNumber; -FOUNDATION_EXPORT const unsigned char GoogleAPIClientForRESTVersionString[]; - diff --git a/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap b/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.modulemap @@ -1,6 +0,0 @@ -framework module GoogleAPIClientForREST { - umbrella header "GoogleAPIClientForREST-umbrella.h" - - export * - module * { export * } -} diff --git a/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.xcconfig b/Pods/Target Support Files/GoogleAPIClientForREST/GoogleAPIClientForREST.xcconfig @@ -1,9 +0,0 @@ -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleAPIClientForREST -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES diff --git a/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-acknowledgements.markdown b/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-acknowledgements.markdown @@ -1,415 +1,28 @@ # Acknowledgements This application makes use of the following third party libraries: -## GTMSessionFetcher +## SheetsTranslator +MIT License - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright (c) 2018 Droids On Roids - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - 1. Definitions. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. - - -## GoogleAPIClientForREST - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-acknowledgements.plist b/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-acknowledgements.plist @@ -14,425 +14,32 @@ </dict> <dict> <key>FooterText</key> - <string> - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. -</string> - <key>License</key> - <string>Apache</string> - <key>Title</key> - <string>GTMSessionFetcher</string> - <key>Type</key> - <string>PSGroupSpecifier</string> - </dict> - <dict> - <key>FooterText</key> - <string> - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. + <string>MIT License + +Copyright (c) 2018 Droids On Roids + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. </string> <key>License</key> - <string>Apache</string> + <string>MIT</string> <key>Title</key> - <string>GoogleAPIClientForREST</string> + <string>SheetsTranslator</string> <key>Type</key> <string>PSGroupSpecifier</string> </dict> diff --git a/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-frameworks.sh b/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-frameworks.sh @@ -1,165 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -function on_error { - echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" -} -trap 'on_error $LINENO' ERR - -if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then - # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy - # frameworks to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" -mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - -COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" -SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" - -# Used as a return value for each invocation of `strip_invalid_archs` function. -STRIP_BINARY_RETVAL=0 - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -# Copies and strips a vendored framework -install_framework() -{ - if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then - local source="${BUILT_PRODUCTS_DIR}/$1" - elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then - local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" - elif [ -r "$1" ]; then - local source="$1" - fi - - local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - - if [ -L "${source}" ]; then - echo "Symlinked..." - source="$(readlink "${source}")" - fi - - # Use filter instead of exclude so missing patterns don't throw errors. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" - - local basename - basename="$(basename -s .framework "$1")" - binary="${destination}/${basename}.framework/${basename}" - - if ! [ -r "$binary" ]; then - binary="${destination}/${basename}" - elif [ -L "${binary}" ]; then - echo "Destination binary is symlinked..." - dirname="$(dirname "${binary}")" - binary="${dirname}/$(readlink "${binary}")" - fi - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then - strip_invalid_archs "$binary" - fi - - # Resign the code if required by the build settings to avoid unstable apps - code_sign_if_enabled "${destination}/$(basename "$1")" - - # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. - if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then - local swift_runtime_libs - swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) - for lib in $swift_runtime_libs; do - echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" - rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" - code_sign_if_enabled "${destination}/${lib}" - done - fi -} - -# Copies and strips a vendored dSYM -install_dsym() { - local source="$1" - if [ -r "$source" ]; then - # Copy the dSYM into a the targets temp dir. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" - - local basename - basename="$(basename -s .framework.dSYM "$source")" - binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then - strip_invalid_archs "$binary" - fi - - if [[ $STRIP_BINARY_RETVAL == 1 ]]; then - # Move the stripped file into its final destination. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" - else - # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. - touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" - fi - fi -} - -# Signs a framework with the provided identity -code_sign_if_enabled() { - if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then - # Use the current code_sign_identity - echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" - local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" - - if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - code_sign_cmd="$code_sign_cmd &" - fi - echo "$code_sign_cmd" - eval "$code_sign_cmd" - fi -} - -# Strip invalid architectures -strip_invalid_archs() { - binary="$1" - # Get architectures for current target binary - binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" - # Intersect them with the architectures we are building for - intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" - # If there are no archs supported by this binary then warn the user - if [[ -z "$intersected_archs" ]]; then - echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." - STRIP_BINARY_RETVAL=0 - return - fi - stripped="" - for arch in $binary_archs; do - if ! [[ "${ARCHS}" == *"$arch"* ]]; then - # Strip non-valid architectures in-place - lipo -remove "$arch" -output "$binary" "$binary" - stripped="$stripped $arch" - fi - done - if [[ "$stripped" ]]; then - echo "Stripped $binary of architectures:$stripped" - fi - STRIP_BINARY_RETVAL=1 -} - - -if [[ "$CONFIGURATION" == "Debug" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework" - install_framework "${BUILT_PRODUCTS_DIR}/GoogleAPIClientForREST/GoogleAPIClientForREST.framework" -fi -if [[ "$CONFIGURATION" == "Release" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework" - install_framework "${BUILT_PRODUCTS_DIR}/GoogleAPIClientForREST/GoogleAPIClientForREST.framework" -fi -if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - wait -fi diff --git a/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.debug.xcconfig b/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.debug.xcconfig @@ -1,8 +1,5 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST/GoogleAPIClientForREST.framework/Headers" LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -framework "GTMSessionFetcher" -framework "GoogleAPIClientForREST" -framework "Security" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. diff --git a/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.release.xcconfig b/Pods/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.release.xcconfig @@ -1,8 +1,5 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST/GoogleAPIClientForREST.framework/Headers" LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -framework "GTMSessionFetcher" -framework "GoogleAPIClientForREST" -framework "Security" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. diff --git a/Pods/Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.debug.xcconfig b/Pods/Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.debug.xcconfig @@ -1,8 +1,5 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST/GoogleAPIClientForREST.framework/Headers" LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -framework "GTMSessionFetcher" -framework "GoogleAPIClientForREST" -framework "Security" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. diff --git a/Pods/Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.release.xcconfig b/Pods/Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.release.xcconfig @@ -1,8 +1,5 @@ -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GoogleAPIClientForREST/GoogleAPIClientForREST.framework/Headers" LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' -OTHER_LDFLAGS = $(inherited) -framework "GTMSessionFetcher" -framework "GoogleAPIClientForREST" -framework "Security" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. diff --git a/Pods/Target Support Files/SheetsTranslator/SheetsTranslator.xcconfig b/Pods/Target Support Files/SheetsTranslator/SheetsTranslator.xcconfig @@ -0,0 +1,8 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SheetsTranslator +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/SheetsTranslator +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/Teachers' Assistant/AppDelegate.swift b/Teachers' Assistant/AppDelegate.swift @@ -7,6 +7,7 @@ // import UIKit +import UserNotifications @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -15,7 +16,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + /* PERMISSION REQUESTS START */ + + //Notification permissions and welcome notification + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in + let welcomeNotification = UNMutableNotificationContent() + welcomeNotification.title = "Welcome" + welcomeNotification.body = "Enjoy the app!" + welcomeNotification.sound = UNNotificationSound.default() + let welcomeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) + _ = UNNotificationRequest(identifier: "welcomePing", content: welcomeNotification, trigger: welcomeTrigger) + + /* PERMISSION REQUESTS END */ + } + return true } diff --git a/Teachers' Assistant/ViewController.swift b/Teachers' Assistant/ViewController.swift @@ -7,7 +7,6 @@ // import UIKit -import GoogleAPIClientForREST class ViewController: UIViewController { diff --git a/Teachers' Assistant/ViewControllerHome.swift b/Teachers' Assistant/ViewControllerHome.swift @@ -7,7 +7,6 @@ // import UIKit -import GoogleAPIClientForREST class ViewControllerHome: UIViewController { @@ -88,7 +87,7 @@ class ViewControllerHome: UIViewController { } else { signInOut.setImage(#imageLiteral(resourceName: "Sign-In (Orange).png"), for: .normal) } - //Google Drive functionality + } /* SIGN IN-OUT BUTTON END */ diff --git a/Teachers' Assistant/ViewControllerSettings.swift b/Teachers' Assistant/ViewControllerSettings.swift @@ -7,7 +7,6 @@ // import UIKit -import GoogleAPIClientForREST class ViewControllerSettings: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate { diff --git a/TeachersAssistant.xcodeproj/project.pbxproj b/TeachersAssistant.xcodeproj/project.pbxproj @@ -7,8 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 8D05B90C2A1C660E42AA56AE /* Pods_TeachersAssistant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D10E8B200C95EFE160F2EC8 /* Pods_TeachersAssistant.framework */; }; - 906A6F4745D1FA15B86160B9 /* Pods_TeachersAssistantTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11EF5C302B8B1C8D48F3D0F2 /* Pods_TeachersAssistantTests.framework */; }; B30366162221E45A00BB9DC6 /* Brandeis_Web_HiRes_Orange.png in Resources */ = {isa = PBXBuildFile; fileRef = B30366152221E45900BB9DC6 /* Brandeis_Web_HiRes_Orange.png */; }; B30366182221E60C00BB9DC6 /* ViewControllerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B30366172221E60C00BB9DC6 /* ViewControllerSettings.swift */; }; B34CDB9F221650410031C28B /* Sign-Out (Dark Blue).png in Resources */ = {isa = PBXBuildFile; fileRef = B34CDB8C221650360031C28B /* Sign-Out (Dark Blue).png */; }; @@ -42,6 +40,8 @@ B3D8E1DF220F7847007CAD3A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B3D8E1DD220F7847007CAD3A /* LaunchScreen.storyboard */; }; B3D8E1EA220F7848007CAD3A /* Teachers__AssistantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3D8E1E9220F7848007CAD3A /* Teachers__AssistantTests.swift */; }; B3D8E1FB220F8172007CAD3A /* ViewControllerHome.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3D8E1FA220F8172007CAD3A /* ViewControllerHome.swift */; }; + BCC3A0C7A0CAAA7FEAC087F6 /* Pods_TeachersAssistantTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBC663FC589A30C2947174E5 /* Pods_TeachersAssistantTests.framework */; }; + F0E543F1802D80B2A40C6E71 /* Pods_TeachersAssistant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0697CC05CCD89B781D8B034F /* Pods_TeachersAssistant.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -55,12 +55,11 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 11EF5C302B8B1C8D48F3D0F2 /* Pods_TeachersAssistantTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TeachersAssistantTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 1723407E824D6432322BF145 /* Pods-TeachersAssistant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistant.release.xcconfig"; path = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.release.xcconfig"; sourceTree = "<group>"; }; - 3FCDA47A9F6775F2E7818B5F /* Pods-TeachersAssistantTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistantTests.release.xcconfig"; path = "Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.release.xcconfig"; sourceTree = "<group>"; }; - 7088E7DE163DD403B94F95F6 /* Pods-TeachersAssistantTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistantTests.debug.xcconfig"; path = "Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.debug.xcconfig"; sourceTree = "<group>"; }; - 800B9D84351418F21FB4E4AC /* Pods-TeachersAssistant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistant.debug.xcconfig"; path = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.debug.xcconfig"; sourceTree = "<group>"; }; - 8D10E8B200C95EFE160F2EC8 /* Pods_TeachersAssistant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TeachersAssistant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0697CC05CCD89B781D8B034F /* Pods_TeachersAssistant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TeachersAssistant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1C01C2AA3F2F7EF0FF5983F0 /* Pods-TeachersAssistant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistant.debug.xcconfig"; path = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.debug.xcconfig"; sourceTree = "<group>"; }; + 236832ABAE1FB432A9A9CB0D /* Pods-TeachersAssistantTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistantTests.debug.xcconfig"; path = "Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.debug.xcconfig"; sourceTree = "<group>"; }; + 76257347082C2FA388478743 /* Pods-TeachersAssistantTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistantTests.release.xcconfig"; path = "Target Support Files/Pods-TeachersAssistantTests/Pods-TeachersAssistantTests.release.xcconfig"; sourceTree = "<group>"; }; + 910BFE7031BA5BA4545B32C7 /* Pods-TeachersAssistant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeachersAssistant.release.xcconfig"; path = "Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant.release.xcconfig"; sourceTree = "<group>"; }; B30366152221E45900BB9DC6 /* Brandeis_Web_HiRes_Orange.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Brandeis_Web_HiRes_Orange.png; sourceTree = "<group>"; }; B30366172221E60C00BB9DC6 /* ViewControllerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerSettings.swift; sourceTree = "<group>"; }; B34CDB8C221650360031C28B /* Sign-Out (Dark Blue).png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Sign-Out (Dark Blue).png"; sourceTree = "<group>"; }; @@ -98,6 +97,7 @@ B3D8E1E9220F7848007CAD3A /* Teachers__AssistantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Teachers__AssistantTests.swift; sourceTree = "<group>"; }; B3D8E1EB220F7848007CAD3A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B3D8E1FA220F8172007CAD3A /* ViewControllerHome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerHome.swift; sourceTree = "<group>"; }; + DBC663FC589A30C2947174E5 /* Pods_TeachersAssistantTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TeachersAssistantTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -105,7 +105,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8D05B90C2A1C660E42AA56AE /* Pods_TeachersAssistant.framework in Frameworks */, + F0E543F1802D80B2A40C6E71 /* Pods_TeachersAssistant.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -113,18 +113,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 906A6F4745D1FA15B86160B9 /* Pods_TeachersAssistantTests.framework in Frameworks */, + BCC3A0C7A0CAAA7FEAC087F6 /* Pods_TeachersAssistantTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 795C191EFA61C2D1AEAF78DB /* Frameworks */ = { + 99FF7E0E85AE17B29BD7939F /* Frameworks */ = { isa = PBXGroup; children = ( - 8D10E8B200C95EFE160F2EC8 /* Pods_TeachersAssistant.framework */, - 11EF5C302B8B1C8D48F3D0F2 /* Pods_TeachersAssistantTests.framework */, + 0697CC05CCD89B781D8B034F /* Pods_TeachersAssistant.framework */, + DBC663FC589A30C2947174E5 /* Pods_TeachersAssistantTests.framework */, ); name = Frameworks; sourceTree = "<group>"; @@ -168,7 +168,7 @@ B3D8E1E8220F7848007CAD3A /* Teachers' AssistantTests */, B3D8E1D2220F7847007CAD3A /* Products */, BC8BCABAA51FEDF2839E3005 /* Pods */, - 795C191EFA61C2D1AEAF78DB /* Frameworks */, + 99FF7E0E85AE17B29BD7939F /* Frameworks */, ); sourceTree = "<group>"; }; @@ -209,10 +209,10 @@ BC8BCABAA51FEDF2839E3005 /* Pods */ = { isa = PBXGroup; children = ( - 800B9D84351418F21FB4E4AC /* Pods-TeachersAssistant.debug.xcconfig */, - 1723407E824D6432322BF145 /* Pods-TeachersAssistant.release.xcconfig */, - 7088E7DE163DD403B94F95F6 /* Pods-TeachersAssistantTests.debug.xcconfig */, - 3FCDA47A9F6775F2E7818B5F /* Pods-TeachersAssistantTests.release.xcconfig */, + 1C01C2AA3F2F7EF0FF5983F0 /* Pods-TeachersAssistant.debug.xcconfig */, + 910BFE7031BA5BA4545B32C7 /* Pods-TeachersAssistant.release.xcconfig */, + 236832ABAE1FB432A9A9CB0D /* Pods-TeachersAssistantTests.debug.xcconfig */, + 76257347082C2FA388478743 /* Pods-TeachersAssistantTests.release.xcconfig */, ); path = Pods; sourceTree = "<group>"; @@ -224,11 +224,10 @@ isa = PBXNativeTarget; buildConfigurationList = B3D8E1EE220F7848007CAD3A /* Build configuration list for PBXNativeTarget "TeachersAssistant" */; buildPhases = ( - 7D432762A029A21E1E5010CF /* [CP] Check Pods Manifest.lock */, + 6EEFF9FA53B027D9BA085CE8 /* [CP] Check Pods Manifest.lock */, B3D8E1CD220F7847007CAD3A /* Sources */, B3D8E1CE220F7847007CAD3A /* Frameworks */, B3D8E1CF220F7847007CAD3A /* Resources */, - 95712E6662D48FC472676213 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -243,7 +242,7 @@ isa = PBXNativeTarget; buildConfigurationList = B3D8E1F1220F7848007CAD3A /* Build configuration list for PBXNativeTarget "TeachersAssistantTests" */; buildPhases = ( - 65EE7C1F76479D92B06A2476 /* [CP] Check Pods Manifest.lock */, + 70B3E066E378518DEB58E664 /* [CP] Check Pods Manifest.lock */, B3D8E1E1220F7848007CAD3A /* Sources */, B3D8E1E2220F7848007CAD3A /* Frameworks */, B3D8E1E3220F7848007CAD3A /* Resources */, @@ -344,7 +343,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 65EE7C1F76479D92B06A2476 /* [CP] Check Pods Manifest.lock */ = { + 6EEFF9FA53B027D9BA085CE8 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -355,14 +354,14 @@ ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TeachersAssistantTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-TeachersAssistant-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 7D432762A029A21E1E5010CF /* [CP] Check Pods Manifest.lock */ = { + 70B3E066E378518DEB58E664 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -373,33 +372,13 @@ ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TeachersAssistant-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-TeachersAssistantTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 95712E6662D48FC472676213 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", - "${BUILT_PRODUCTS_DIR}/GoogleAPIClientForREST/GoogleAPIClientForREST.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleAPIClientForREST.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TeachersAssistant/Pods-TeachersAssistant-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -561,14 +540,14 @@ }; B3D8E1EF220F7848007CAD3A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 800B9D84351418F21FB4E4AC /* Pods-TeachersAssistant.debug.xcconfig */; + baseConfigurationReference = 1C01C2AA3F2F7EF0FF5983F0 /* Pods-TeachersAssistant.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 9WHCFZ6J4N; INFOPLIST_FILE = "Teachers' Assistant/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.figbertinc.Teachers--Assistant"; + PRODUCT_BUNDLE_IDENTIFIER = com.figbertinc.TeachersAssistant; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -577,14 +556,14 @@ }; B3D8E1F0220F7848007CAD3A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1723407E824D6432322BF145 /* Pods-TeachersAssistant.release.xcconfig */; + baseConfigurationReference = 910BFE7031BA5BA4545B32C7 /* Pods-TeachersAssistant.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 9WHCFZ6J4N; INFOPLIST_FILE = "Teachers' Assistant/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.figbertinc.Teachers--Assistant"; + PRODUCT_BUNDLE_IDENTIFIER = com.figbertinc.TeachersAssistant; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -593,7 +572,7 @@ }; B3D8E1F2220F7848007CAD3A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7088E7DE163DD403B94F95F6 /* Pods-TeachersAssistantTests.debug.xcconfig */; + baseConfigurationReference = 236832ABAE1FB432A9A9CB0D /* Pods-TeachersAssistantTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -611,7 +590,7 @@ }; B3D8E1F3220F7848007CAD3A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3FCDA47A9F6775F2E7818B5F /* Pods-TeachersAssistantTests.release.xcconfig */; + baseConfigurationReference = 76257347082C2FA388478743 /* Pods-TeachersAssistantTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; diff --git a/TeachersAssistant.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/xcschememanagement.plist b/TeachersAssistant.xcodeproj/xcuserdata/naomi.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ <key>TeachersAssistant.xcscheme</key> <dict> <key>orderHint</key> - <integer>4</integer> + <integer>3</integer> </dict> </dict> </dict> diff --git a/TeachersAssistant.xcworkspace/xcuserdata/naomi.xcuserdatad/UserInterfaceState.xcuserstate b/TeachersAssistant.xcworkspace/xcuserdata/naomi.xcuserdatad/UserInterfaceState.xcuserstate Binary files differ.