GTLRService.m 115 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884
  1. /* Copyright (c) 2011 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. #import <TargetConditionals.h>
  16. #if TARGET_OS_IPHONE
  17. #import <UIKit/UIKit.h>
  18. #endif
  19. //#import <GoogleAPIClientForREST/GTLRService.h>
  20. #import "GTLRService.h"
  21. //#import <GoogleAPIClientForREST/GTLRFramework.h>
  22. //#import <GoogleAPIClientForREST/GTLRURITemplate.h>
  23. //#import <GoogleAPIClientForREST/GTLRUtilities.h>
  24. #import "GTLRFramework.h"
  25. #import "GTLRURITemplate.h"
  26. #import "GTLRUtilities.h"
  27. #import "GTLRDefines.h"
  28. // TODO: Simplify when the 2.0 SessionFetcher is the min dependency.
  29. #if __has_include(<GTMSessionFetcher/GTMSessionUploadFetcher.h>) // 2.x & CocoaPods
  30. #import <GTMSessionFetcher/GTMSessionUploadFetcher.h>
  31. #import <GTMSessionFetcher/GTMMIMEDocument.h>
  32. #else // SwiftPM 1.x
  33. // #import "../GTMSessionUploadFetcher.h"
  34. // #import "../GTMMIMEDocument.h"
  35. #import "GTMSessionUploadFetcher.h"
  36. #import "GTMMIMEDocument.h"
  37. #endif
  38. #ifndef STRIP_GTM_FETCH_LOGGING
  39. #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined.
  40. #endif
  41. #ifndef GTLR_ASSERT_CURRENT_QUEUE_DEBUG
  42. #if DEBUG && !defined(NS_BLOCK_ASSERTIONS)
  43. static __inline__ __attribute__((always_inline))
  44. void GTLR_ASSERT_CURRENT_QUEUE_DEBUG_IMPL(dispatch_queue_t targetQueue) {
  45. if (@available(iOS 10, *)) {
  46. dispatch_assert_queue(targetQueue);
  47. }
  48. }
  49. #define GTLR_ASSERT_CURRENT_QUEUE_DEBUG(targetQueue) \
  50. GTLR_ASSERT_CURRENT_QUEUE_DEBUG_IMPL(targetQueue)
  51. #else
  52. #define GTLR_ASSERT_CURRENT_QUEUE_DEBUG(targetQueue) do { } while (0)
  53. #endif // DEBUG && !defined(NS_BLOCK_ASSERTIONS)
  54. #endif // GTLR_ASSERT_CURRENT_QUEUE_DEBUG
  55. NSString *const kGTLRServiceErrorDomain = @"com.google.GTLRServiceDomain";
  56. NSString *const kGTLRErrorObjectDomain = @"com.google.GTLRErrorObjectDomain";
  57. NSString *const kGTLRServiceErrorBodyDataKey = @"body";
  58. NSString *const kGTLRServiceErrorContentIDKey = @"contentID";
  59. NSString *const kGTLRStructuredErrorKey = @"GTLRStructuredError";
  60. NSString *const kGTLRETagWildcard = @"*";
  61. NSString *const kGTLRServiceTicketStartedNotification = @"kGTLRServiceTicketStartedNotification";
  62. NSString *const kGTLRServiceTicketStoppedNotification = @"kGTLRServiceTicketStoppedNotification";
  63. NSString *const kGTLRServiceTicketParsingStartedNotification = @"kGTLRServiceTicketParsingStartedNotification";
  64. NSString *const kGTLRServiceTicketParsingStoppedNotification = @"kGTLRServiceTicketParsingStoppedNotification";
  65. NSString *const kXIosBundleIdHeader = @"X-Ios-Bundle-Identifier";
  66. static NSString *const kDeveloperAPIQueryParamKey = @"key";
  67. static const NSUInteger kMaxNumberOfNextPagesFetched = 25;
  68. static const NSUInteger kMaxGETURLLength = 2048;
  69. // we'll enforce 50K chunks minimum just to avoid the server getting hit
  70. // with too many small upload chunks
  71. static const NSUInteger kMinimumUploadChunkSize = 50000;
  72. // Helper to get the ETag if it is defined on an object.
  73. static NSString *ETagIfPresent(GTLRObject *obj) {
  74. NSString *result = [obj.JSON objectForKey:@"etag"];
  75. return result;
  76. }
  77. // Merge two dictionaries. Either may be nil.
  78. // If both are nil, return nil.
  79. // In case of a key collision, values of the second dictionary prevail.
  80. static NSDictionary *MergeDictionaries(NSDictionary *recessiveDict, NSDictionary *dominantDict) {
  81. if (!dominantDict) return recessiveDict;
  82. if (!recessiveDict) return dominantDict;
  83. NSMutableDictionary *worker = [recessiveDict mutableCopy];
  84. [worker addEntriesFromDictionary:dominantDict];
  85. return worker;
  86. }
  87. @interface GTLRServiceTicket ()
  88. - (instancetype)initWithService:(GTLRService *)service
  89. executionParameters:(GTLRServiceExecutionParameters *)params NS_DESIGNATED_INITIALIZER;
  90. // Thread safety: ticket properties are all publicly exposed as read-only.
  91. //
  92. // Service execution of a ticket is serial (started by the app, then executing on the fetcher
  93. // callback queue and then the parse queue), so we don't need to worry about synchronization.
  94. //
  95. // One important exception is when the user invoked cancelTicket. During cancellation, ticket
  96. // properties are released. This should be harmless even during the fetch start-parse-callback
  97. // phase because nothing released in cancelTicket is used to begin a fetch, and the cancellation
  98. // flag will prevent any application callbacks from being invoked.
  99. //
  100. // The cancel and objectFetcher properties are synchronized on the ticket.
  101. // Ticket properties exposed publicly as readonly.
  102. @property(atomic, readwrite, nullable) id<GTLRQueryProtocol> originalQuery;
  103. @property(atomic, readwrite, nullable) id<GTLRQueryProtocol> executingQuery;
  104. @property(atomic, readwrite, nullable) GTMSessionFetcher *objectFetcher;
  105. @property(nonatomic, readwrite, nullable) NSURLRequest *fetchRequest;
  106. @property(nonatomic, readwrite, nullable) GTLRObject *postedObject;
  107. @property(nonatomic, readwrite, nullable) GTLRObject *fetchedObject;
  108. @property(nonatomic, readwrite, nullable) NSError *fetchError;
  109. @property(nonatomic, readwrite) BOOL hasCalledCallback;
  110. @property(nonatomic, readwrite) NSUInteger pagesFetchedCounter;
  111. @property(readwrite, atomic, strong) id<GTLRObjectClassResolver> objectClassResolver;
  112. // Internal properties copied from the service.
  113. @property(nonatomic, assign) BOOL allowInsecureQueries;
  114. @property(nonatomic, strong) GTMSessionFetcherService *fetcherService;
  115. #pragma clang diagnostic push
  116. #pragma clang diagnostic ignored "-Wdeprecated"
  117. @property(nonatomic, strong, nullable) id<GTMFetcherAuthorizationProtocol> authorizer;
  118. #pragma clang diagnostic pop
  119. // Internal properties copied from serviceExecutionParameters.
  120. @property(nonatomic, getter=isRetryEnabled) BOOL retryEnabled;
  121. @property(nonatomic, readwrite) NSTimeInterval maxRetryInterval;
  122. @property(nonatomic, strong, nullable) GTLRServiceRetryBlock retryBlock;
  123. @property(nonatomic, strong, nullable) GTLRServiceUploadProgressBlock uploadProgressBlock;
  124. @property(nonatomic, strong, nullable) GTLRServiceTestBlock testBlock;
  125. @property(nonatomic, readwrite) BOOL shouldFetchNextPages;
  126. // Internal properties used by the service.
  127. #if GTM_BACKGROUND_TASK_FETCHING
  128. // Access to backgroundTaskIdentifier should be protected by @synchronized(self).
  129. @property(nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
  130. #endif // GTM_BACKGROUND_TASK_FETCHING
  131. // Dispatch group enabling waitForTicket: to delay until async callbacks and notifications
  132. // related to the ticket have completed.
  133. @property(nonatomic, readonly) dispatch_group_t callbackGroup;
  134. // startBackgroundTask and endBackgroundTask do nothing if !GTM_BACKGROUND_TASK_FETCHING
  135. - (void)startBackgroundTask;
  136. - (void)endBackgroundTask;
  137. - (void)notifyStarting:(BOOL)isStarting;
  138. - (void)releaseTicketCallbacks;
  139. // Posts a notification on the main queue using the ticket's dispatch group.
  140. - (void)postNotificationOnMainThreadWithName:(NSString *)name
  141. object:(id)object
  142. userInfo:(NSDictionary *)userInfo;
  143. @end
  144. @interface GTLRObject (StandardProperties)
  145. // Common properties on GTLRObject that are invoked below.
  146. @property(nonatomic, copy) NSString *nextPageToken;
  147. @end
  148. // This class encapsulates the pieces of a single batch response, including
  149. // inner http response code and message, inner headers, JSON body (parsed as a dictionary),
  150. // or parsing NSError.
  151. //
  152. // See responsePartsWithMIMEParts: for an example of the wire format data used
  153. // to populate this object.
  154. @interface GTLRBatchResponsePart : NSObject
  155. @property(nonatomic, copy) NSString *contentID;
  156. @property(nonatomic, assign) NSInteger statusCode;
  157. @property(nonatomic, copy) NSString *statusString;
  158. @property(nonatomic, strong) NSDictionary *headers;
  159. @property(nonatomic, strong) NSDictionary *JSON;
  160. @property(nonatomic, strong) NSError *parseError;
  161. @end
  162. @implementation GTLRBatchResponsePart
  163. @synthesize contentID = _contentID,
  164. headers = _headers,
  165. JSON = _JSON,
  166. parseError = _parseError,
  167. statusCode = _statusCode,
  168. statusString = _statusString;
  169. #if DEBUG
  170. - (NSString *)description {
  171. return [NSString stringWithFormat:@"%@ %p: %@\n%ld %@\nheaders:%@\nJSON:%@\nerror:%@",
  172. [self class], self, self.contentID, (long)self.statusCode, self.statusString,
  173. self.headers, self.JSON, self.parseError];
  174. }
  175. #endif
  176. @end
  177. // GTLRResourceURLQuery is an internal class used as a query object placeholder
  178. // when fetchObjectWithURL: is invoked by the client app. This lets the service's
  179. // plumbing treat the request like other queries, without allowing users to
  180. // set arbitrary query properties that may not work as anticipated.
  181. @interface GTLRResourceURLQuery : GTLRQuery
  182. @property(nonatomic, strong, nullable) NSURL *resourceURL;
  183. + (instancetype)queryWithResourceURL:(NSURL *)resourceURL
  184. objectClass:(nullable Class)objectClass;
  185. @end
  186. @implementation GTLRService {
  187. NSString *_userAgent;
  188. NSString *_overrideUserAgent;
  189. NSDictionary *_serviceProperties; // Properties retained for the convenience of the client app.
  190. NSUInteger _uploadChunkSize; // Only applies to resumable chunked uploads.
  191. }
  192. @synthesize additionalHTTPHeaders = _additionalHTTPHeaders,
  193. additionalURLQueryParameters = _additionalURLQueryParameters,
  194. allowInsecureQueries = _allowInsecureQueries,
  195. callbackQueue = _callbackQueue,
  196. APIKey = _apiKey,
  197. APIKeyRestrictionBundleID = _apiKeyRestrictionBundleID,
  198. batchPath = _batchPath,
  199. dataWrapperRequired = _dataWrapperRequired,
  200. fetcherService = _fetcherService,
  201. maxRetryInterval = _maxRetryInterval,
  202. parseQueue = _parseQueue,
  203. prettyPrintQueryParameterNames = _prettyPrintQueryParameterNames,
  204. resumableUploadPath = _resumableUploadPath,
  205. retryBlock = _retryBlock,
  206. retryEnabled = _retryEnabled,
  207. rootURLString = _rootURLString,
  208. servicePath = _servicePath,
  209. shouldFetchNextPages = _shouldFetchNextPages,
  210. simpleUploadPath = _simpleUploadPath,
  211. objectClassResolver = _objectClassResolver,
  212. testBlock = _testBlock,
  213. uploadProgressBlock = _uploadProgressBlock,
  214. userAgentAddition = _userAgentAddition;
  215. + (Class)ticketClass {
  216. return [GTLRServiceTicket class];
  217. }
  218. - (instancetype)init {
  219. self = [super init];
  220. if (self) {
  221. _parseQueue = dispatch_queue_create("com.google.GTLRServiceParse", DISPATCH_QUEUE_SERIAL);
  222. _callbackQueue = dispatch_get_main_queue();
  223. _fetcherService = [[GTMSessionFetcherService alloc] init];
  224. // Make the session fetcher use a background delegate queue instead of bouncing
  225. // through the main queue for its callbacks from NSURLSession. This should improve
  226. // performance, and eventually be the default behavior for the fetcher.
  227. NSOperationQueue *delegateQueue = [[NSOperationQueue alloc] init];
  228. delegateQueue.maxConcurrentOperationCount = 1;
  229. delegateQueue.name = @"com.google.GTLRServiceFetcherDelegate";
  230. _fetcherService.sessionDelegateQueue = delegateQueue;
  231. NSDictionary<NSString *, Class> *kindMap = [[self class] kindStringToClassMap];
  232. _objectClassResolver = [GTLRObjectClassResolver resolverWithKindMap:kindMap];
  233. }
  234. return self;
  235. }
  236. - (NSString *)requestUserAgent {
  237. if (_overrideUserAgent != nil) {
  238. return _overrideUserAgent;
  239. }
  240. NSString *userAgent = self.userAgent;
  241. if (userAgent.length == 0) {
  242. // The service instance is missing an explicit user-agent; use the bundle ID
  243. // or process name. The check for the specific bundle is basically a noop as
  244. // it was the hardcoded value from the framework when the project included
  245. // and Xcode project. It is kept just incase someone happened to use the
  246. // same bundle id so the behavior remains consistent.
  247. NSBundle *owningBundle = [NSBundle bundleForClass:[self class]];
  248. if (owningBundle == nil
  249. || [owningBundle.bundleIdentifier isEqual:@"com.google.GTLR"]) {
  250. owningBundle = [NSBundle mainBundle];
  251. }
  252. userAgent = GTMFetcherApplicationIdentifier(owningBundle);
  253. }
  254. NSString *requestUserAgent = userAgent;
  255. // if the user agent already specifies the library version, we'll
  256. // use it verbatim in the request
  257. NSString *libraryString = @"google-api-objc-client";
  258. NSRange libRange = [userAgent rangeOfString:libraryString
  259. options:NSCaseInsensitiveSearch];
  260. if (libRange.location == NSNotFound) {
  261. // the user agent doesn't specify the client library, so append that
  262. // information, and the system version
  263. NSString *libVersionString = GTLRFrameworkVersionString();
  264. NSString *systemString = GTMFetcherSystemVersionString();
  265. // We don't clean this with GTMCleanedUserAgentString so spaces are
  266. // preserved
  267. NSString *userAgentAddition = self.userAgentAddition;
  268. NSString *customString = userAgentAddition ?
  269. [@" " stringByAppendingString:userAgentAddition] : @"";
  270. // Google servers look for gzip in the user agent before sending gzip-
  271. // encoded responses. See Service.java
  272. requestUserAgent = [NSString stringWithFormat:@"%@ %@/%@ %@%@ (gzip)",
  273. userAgent, libraryString, libVersionString, systemString, customString];
  274. }
  275. return requestUserAgent;
  276. }
  277. - (void)setMainBundleIDRestrictionWithAPIKey:(NSString *)apiKey {
  278. self.APIKey = apiKey;
  279. self.APIKeyRestrictionBundleID = [[NSBundle mainBundle] bundleIdentifier];
  280. }
  281. - (NSMutableURLRequest *)requestForURL:(NSURL *)url
  282. ETag:(NSString *)etag
  283. httpMethod:(NSString *)httpMethod
  284. ticket:(GTLRServiceTicket *)ticket {
  285. // subclasses may add headers to this
  286. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
  287. cachePolicy:NSURLRequestReloadIgnoringCacheData
  288. timeoutInterval:60];
  289. NSString *requestUserAgent = self.requestUserAgent;
  290. [request setValue:requestUserAgent forHTTPHeaderField:@"User-Agent"];
  291. if (httpMethod.length > 0) {
  292. [request setHTTPMethod:httpMethod];
  293. }
  294. if (etag.length > 0) {
  295. // it's rather unexpected for an etagged object to be provided for a GET,
  296. // but we'll check for an etag anyway, similar to HttpGDataRequest.java,
  297. // and if present use it to request only an unchanged resource
  298. BOOL isDoingHTTPGet = (httpMethod == nil
  299. || [httpMethod caseInsensitiveCompare:@"GET"] == NSOrderedSame);
  300. if (isDoingHTTPGet) {
  301. // set the etag header, even if weak, indicating we don't want
  302. // another copy of the resource if it's the same as the object
  303. [request setValue:etag forHTTPHeaderField:@"If-None-Match"];
  304. } else {
  305. // if we're doing PUT or DELETE, set the etag header indicating
  306. // we only want to update the resource if our copy matches the current
  307. // one (unless the etag is weak and so shouldn't be a constraint at all)
  308. BOOL isWeakETag = [etag hasPrefix:@"W/"];
  309. BOOL isModifying =
  310. [httpMethod caseInsensitiveCompare:@"PUT"] == NSOrderedSame
  311. || [httpMethod caseInsensitiveCompare:@"DELETE"] == NSOrderedSame
  312. || [httpMethod caseInsensitiveCompare:@"PATCH"] == NSOrderedSame;
  313. if (isModifying && !isWeakETag) {
  314. [request setValue:etag forHTTPHeaderField:@"If-Match"];
  315. }
  316. }
  317. }
  318. return request;
  319. }
  320. // objectRequestForURL returns an NSMutableURLRequest for a GTLRObject
  321. //
  322. // the object is the object being sent to the server, or nil;
  323. // the http method may be nil for get, or POST, PUT, DELETE
  324. - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
  325. object:(GTLRObject *)object
  326. contentType:(NSString *)contentType
  327. contentLength:(NSString *)contentLength
  328. ETag:(NSString *)etag
  329. httpMethod:(NSString *)httpMethod
  330. additionalHeaders:(NSDictionary *)additionalHeaders
  331. ticket:(GTLRServiceTicket *)ticket {
  332. if (object) {
  333. // if the object being sent has an etag, add it to the request header to
  334. // avoid retrieving a duplicate or to avoid writing over an updated
  335. // version of the resource on the server
  336. //
  337. // Typically, delete requests will provide an explicit ETag parameter, and
  338. // other requests will have the ETag carried inside the object being updated
  339. if (etag == nil) {
  340. etag = ETagIfPresent(object);
  341. }
  342. }
  343. NSMutableURLRequest *request = [self requestForURL:url
  344. ETag:etag
  345. httpMethod:httpMethod
  346. ticket:ticket];
  347. [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
  348. [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
  349. [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"];
  350. if (contentLength) {
  351. [request setValue:contentLength forHTTPHeaderField:@"Content-Length"];
  352. }
  353. // Add the additional http headers from the service, and then from the query
  354. NSDictionary *headers = self.additionalHTTPHeaders;
  355. for (NSString *key in headers) {
  356. NSString *value = [headers objectForKey:key];
  357. [request setValue:value forHTTPHeaderField:key];
  358. }
  359. headers = additionalHeaders;
  360. for (NSString *key in headers) {
  361. NSString *value = [headers objectForKey:key];
  362. [request setValue:value forHTTPHeaderField:key];
  363. }
  364. return request;
  365. }
  366. #pragma mark -
  367. - (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
  368. GTLR_DEBUG_ASSERT(query.bodyObject == nil,
  369. @"requestForQuery: supports only GET methods, but was passed: %@", query);
  370. GTLR_DEBUG_ASSERT(query.uploadParameters == nil,
  371. @"requestForQuery: does not support uploads, but was passed: %@", query);
  372. NSURL *url = [self URLFromQueryObject:query
  373. usePartialPaths:NO
  374. includeServiceURLQueryParams:YES];
  375. // If there is a developer key, add it onto the url.
  376. NSString *apiKey = self.APIKey;
  377. if (apiKey.length > 0) {
  378. NSDictionary *queryParameters;
  379. queryParameters = @{ kDeveloperAPIQueryParamKey : apiKey };
  380. url = [GTLRService URLWithString:url.absoluteString
  381. queryParameters:queryParameters];
  382. }
  383. NSMutableURLRequest *request = [self requestForURL:url
  384. ETag:nil
  385. httpMethod:query.httpMethod
  386. ticket:nil];
  387. NSString *apiRestriction = self.APIKeyRestrictionBundleID;
  388. if ([apiRestriction length] > 0) {
  389. [request setValue:apiRestriction forHTTPHeaderField:kXIosBundleIdHeader];
  390. }
  391. NSDictionary *headers = self.additionalHTTPHeaders;
  392. for (NSString *key in headers) {
  393. NSString *value = [headers objectForKey:key];
  394. [request setValue:value forHTTPHeaderField:key];
  395. }
  396. headers = query.additionalHTTPHeaders;
  397. for (NSString *key in headers) {
  398. NSString *value = [headers objectForKey:key];
  399. [request setValue:value forHTTPHeaderField:key];
  400. }
  401. return request;
  402. }
  403. // common fetch starting method
  404. - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
  405. objectClass:(Class)objectClass
  406. bodyObject:(GTLRObject *)bodyObject
  407. dataToPost:(NSData *)dataToPost
  408. ETag:(NSString *)etag
  409. httpMethod:(NSString *)httpMethod
  410. mayAuthorize:(BOOL)mayAuthorize
  411. completionHandler:(GTLRServiceCompletionHandler)completionHandler
  412. executingQuery:(id<GTLRQueryProtocol>)executingQuery
  413. ticket:(GTLRServiceTicket *)ticket {
  414. // Once inside this method, we should not access any service properties that may reasonably
  415. // be changed by the app, as this method may execute multiple times during query execution
  416. // and we want consistent behavior. Service properties should be copied to the ticket.
  417. GTLR_DEBUG_ASSERT(executingQuery != nil,
  418. @"no query? service additionalURLQueryParameters needs to be added to targetURL");
  419. GTLR_DEBUG_ASSERT(targetURL != nil, @"no url?");
  420. if (targetURL == nil) return nil;
  421. BOOL hasExecutionParams = [executingQuery hasExecutionParameters];
  422. GTLRServiceExecutionParameters *executionParams = (hasExecutionParams ?
  423. executingQuery.executionParameters : nil);
  424. // We need to create a ticket unless one was created earlier (like during authentication.)
  425. if (!ticket) {
  426. ticket = [[[[self class] ticketClass] alloc] initWithService:self
  427. executionParameters:executionParams];
  428. [ticket notifyStarting:YES];
  429. }
  430. // If there is a developer key, add it onto the URL.
  431. NSString *apiKey = ticket.APIKey;
  432. if (apiKey.length > 0) {
  433. NSDictionary *queryParameters;
  434. queryParameters = @{ kDeveloperAPIQueryParamKey : apiKey };
  435. targetURL = [GTLRService URLWithString:targetURL.absoluteString
  436. queryParameters:queryParameters];
  437. }
  438. NSString *contentType = @"application/json; charset=utf-8";
  439. NSString *contentLength; // nil except for single-request uploads.
  440. if ([executingQuery isBatchQuery]) {
  441. contentType = [NSString stringWithFormat:@"multipart/mixed; boundary=%@",
  442. ((GTLRBatchQuery *)executingQuery).boundary];
  443. }
  444. GTLRUploadParameters *uploadParams = executingQuery.uploadParameters;
  445. if (uploadParams.shouldUploadWithSingleRequest) {
  446. NSData *uploadData = uploadParams.data;
  447. NSString *uploadMIMEType = uploadParams.MIMEType;
  448. if (!uploadData) {
  449. GTLR_DEBUG_ASSERT(0, @"Uploading with a single request requires bytes to upload as NSData");
  450. } else {
  451. if (uploadParams.shouldSendUploadOnly) {
  452. contentType = uploadMIMEType;
  453. dataToPost = uploadData;
  454. contentLength = @(dataToPost.length).stringValue;
  455. } else {
  456. GTMMIMEDocument *mimeDoc = [GTMMIMEDocument MIMEDocument];
  457. if (dataToPost) {
  458. // Include the object as metadata with the upload.
  459. [mimeDoc addPartWithHeaders:@{ @"Content-Type" : contentType }
  460. body:dataToPost];
  461. }
  462. [mimeDoc addPartWithHeaders:@{ @"Content-Type" : uploadMIMEType }
  463. body:uploadData];
  464. dispatch_data_t mimeDispatchData;
  465. unsigned long long mimeLength;
  466. NSString *mimeBoundary;
  467. [mimeDoc generateDispatchData:&mimeDispatchData
  468. length:&mimeLength
  469. boundary:&mimeBoundary];
  470. contentType = [NSString stringWithFormat:@"multipart/related; boundary=%@", mimeBoundary];
  471. dataToPost = (NSData *)mimeDispatchData;
  472. contentLength = @(mimeLength).stringValue;
  473. }
  474. }
  475. }
  476. NSDictionary *additionalHeaders = nil;
  477. NSString *restriction = self.APIKeyRestrictionBundleID;
  478. if ([restriction length] > 0) {
  479. additionalHeaders = @{ kXIosBundleIdHeader : restriction };
  480. }
  481. NSDictionary *queryAdditionalHeaders = executingQuery.additionalHTTPHeaders;
  482. if (queryAdditionalHeaders) {
  483. if (additionalHeaders) {
  484. NSMutableDictionary *builder = [additionalHeaders mutableCopy];
  485. [builder addEntriesFromDictionary:queryAdditionalHeaders];
  486. additionalHeaders = builder;
  487. } else {
  488. additionalHeaders = queryAdditionalHeaders;
  489. }
  490. }
  491. NSURLRequest *request = [self objectRequestForURL:targetURL
  492. object:bodyObject
  493. contentType:contentType
  494. contentLength:contentLength
  495. ETag:etag
  496. httpMethod:httpMethod
  497. additionalHeaders:additionalHeaders
  498. ticket:ticket];
  499. ticket.postedObject = bodyObject;
  500. ticket.executingQuery = executingQuery;
  501. GTLRQuery *originalQuery = (GTLRQuery *)ticket.originalQuery;
  502. if (originalQuery == nil) {
  503. originalQuery = (GTLRQuery *)executingQuery;
  504. ticket.originalQuery = originalQuery;
  505. }
  506. // Some proxy servers (and some web servers) have issues with GET URLs being
  507. // too long, trap that and move the query parameters into the body. The
  508. // uploadParams and dataToPost should be nil for a GET, but playing it safe
  509. // and confirming.
  510. NSString *requestHTTPMethod = request.HTTPMethod;
  511. BOOL isDoingHTTPGet =
  512. (requestHTTPMethod == nil
  513. || [requestHTTPMethod caseInsensitiveCompare:@"GET"] == NSOrderedSame);
  514. if (isDoingHTTPGet &&
  515. (request.URL.absoluteString.length >= kMaxGETURLLength) &&
  516. (uploadParams == nil) &&
  517. (dataToPost == nil)) {
  518. NSString *urlString = request.URL.absoluteString;
  519. NSRange range = [urlString rangeOfString:@"?"];
  520. if (range.location != NSNotFound) {
  521. NSURL *trimmedURL = [NSURL URLWithString:[urlString substringToIndex:range.location]];
  522. NSString *urlArgsString = [urlString substringFromIndex:(range.location + 1)];
  523. if (trimmedURL && (urlArgsString.length > 0)) {
  524. dataToPost = [urlArgsString dataUsingEncoding:NSUTF8StringEncoding];
  525. NSMutableURLRequest *mutableRequest = [request mutableCopy];
  526. mutableRequest.URL = trimmedURL;
  527. mutableRequest.HTTPMethod = @"POST";
  528. [mutableRequest setValue:@"GET" forHTTPHeaderField:@"X-HTTP-Method-Override"];
  529. [mutableRequest setValue:@"application/x-www-form-urlencoded"
  530. forHTTPHeaderField:@"Content-Type"];
  531. [mutableRequest setValue:@(dataToPost.length).stringValue
  532. forHTTPHeaderField:@"Content-Length"];
  533. request = mutableRequest;
  534. }
  535. }
  536. }
  537. ticket.fetchRequest = request;
  538. GTLRServiceTestBlock testBlock = ticket.testBlock;
  539. if (testBlock) {
  540. [self simulateFetchWithTicket:ticket
  541. testBlock:testBlock
  542. dataToPost:dataToPost
  543. completionHandler:completionHandler];
  544. return ticket;
  545. }
  546. GTMSessionFetcherService *fetcherService = ticket.fetcherService;
  547. GTMSessionFetcher *fetcher;
  548. if (uploadParams == nil || uploadParams.shouldUploadWithSingleRequest) {
  549. // Create a single-request fetcher.
  550. fetcher = [fetcherService fetcherWithRequest:request];
  551. } else {
  552. fetcher = [self uploadFetcherWithRequest:request
  553. fetcherService:fetcherService
  554. params:uploadParams];
  555. }
  556. if (ticket.allowInsecureQueries) {
  557. fetcher.allowLocalhostRequest = YES;
  558. fetcher.allowedInsecureSchemes = @[ @"http" ];
  559. }
  560. NSString *loggingName = executingQuery.loggingName;
  561. if (loggingName.length > 0) {
  562. NSUInteger pageNumber = ticket.pagesFetchedCounter + 1;
  563. if (pageNumber > 1) {
  564. loggingName = [loggingName stringByAppendingFormat:@", page %lu",
  565. (unsigned long)pageNumber];
  566. }
  567. fetcher.comment = loggingName;
  568. }
  569. if (!mayAuthorize) {
  570. fetcher.authorizer = nil;
  571. } else {
  572. fetcher.authorizer = ticket.authorizer;
  573. }
  574. // copy the ticket's retry settings into the fetcher
  575. fetcher.retryEnabled = ticket.retryEnabled;
  576. fetcher.maxRetryInterval = ticket.maxRetryInterval;
  577. BOOL shouldExamineRetries = (ticket.retryBlock != nil);
  578. if (shouldExamineRetries) {
  579. GTLR_DEBUG_ASSERT(ticket.retryEnabled, @"Setting retry block without retry enabled.");
  580. fetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *error,
  581. GTMSessionFetcherRetryResponse response) {
  582. // The object fetcher may call into this retry block; this one invokes the
  583. // selector provided by the user.
  584. GTLRServiceRetryBlock retryBlock = ticket.retryBlock;
  585. if (!retryBlock) {
  586. response(suggestedWillRetry);
  587. } else {
  588. dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
  589. if (ticket.cancelled) {
  590. response(NO);
  591. return;
  592. }
  593. BOOL willRetry = retryBlock(ticket, suggestedWillRetry, error);
  594. response(willRetry);
  595. });
  596. }
  597. };
  598. }
  599. // Remember the object fetcher in the ticket.
  600. ticket.objectFetcher = fetcher;
  601. // Set the upload data.
  602. fetcher.bodyData = dataToPost;
  603. // Have the fetcher call back on the parse queue.
  604. fetcher.callbackQueue = self.parseQueue;
  605. // If this ticket is paging, end any ongoing background task immediately, and
  606. // rely on the fetcher's background task now instead.
  607. [ticket endBackgroundTask];
  608. [fetcher beginFetchWithCompletionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
  609. // We now have the JSON data for an object, or an error.
  610. GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue);
  611. // Until now, the only async operation has been the fetch, and we rely on the fetcher's
  612. // background task on iOS to get us here if the app was backgrounded.
  613. //
  614. // Now we'll let the ticket create a background task so that the async parsing and call back to
  615. // the app will happen if the app is sent to the background. The ticket is responsible for
  616. // ending the background task.
  617. [ticket startBackgroundTask];
  618. if (ticket.cancelled) {
  619. // If the user cancels the ticket, then cancelTicket will stop the fetcher so this
  620. // callback probably won't occur.
  621. //
  622. // But just for safety, if we get here, skip any parsing steps by fabricating an error.
  623. data = nil;
  624. error = [NSError errorWithDomain:NSURLErrorDomain
  625. code:NSURLErrorCancelled
  626. userInfo:nil];
  627. }
  628. if (error == nil) {
  629. // Successful fetch.
  630. if (data.length > 0) {
  631. [self prepareToParseObjectForFetcher:fetcher
  632. executingQuery:executingQuery
  633. ticket:ticket
  634. error:error
  635. defaultClass:objectClass
  636. completionHandler:completionHandler];
  637. } else {
  638. // no data (such as when deleting)
  639. [self handleParsedObjectForFetcher:fetcher
  640. executingQuery:executingQuery
  641. ticket:ticket
  642. error:nil
  643. parsedObject:nil
  644. hasSentParsingStartNotification:NO
  645. completionHandler:completionHandler];
  646. }
  647. return;
  648. }
  649. // Failed fetch.
  650. NSInteger status = [error code];
  651. if (status >= 300) {
  652. // Return the HTTP error status code along with a more descriptive error
  653. // from within the HTTP response payload.
  654. NSData *responseData = fetcher.downloadedData;
  655. if (responseData.length > 0) {
  656. NSDictionary *responseHeaders = fetcher.responseHeaders;
  657. NSString *responseContentType = [responseHeaders objectForKey:@"Content-Type"];
  658. if (data.length > 0) {
  659. if ([responseContentType hasPrefix:@"application/json"]) {
  660. NSError *parseError = nil;
  661. NSMutableDictionary *jsonWrapper =
  662. [NSJSONSerialization JSONObjectWithData:(NSData * _Nonnull)data
  663. options:NSJSONReadingMutableContainers
  664. error:&parseError];
  665. // If the json parse worked, then extract potentially better
  666. // information.
  667. if (!parseError) {
  668. // HTTP Streaming defined by Google services is is an array
  669. // of requests and replies. This code never makes one of
  670. // these requests; but, some GET apis can actually be to
  671. // a Streaming result (for media?), so the errors can still
  672. // come back in an array.
  673. if ([jsonWrapper isKindOfClass:[NSArray class]]) {
  674. NSArray *jsonWrapperAsArray = (NSArray *)jsonWrapper;
  675. #if DEBUG
  676. if (jsonWrapperAsArray.count > 1) {
  677. GTLR_DEBUG_LOG(@"Got error array with >1 item, only using first. Full list: %@",
  678. jsonWrapperAsArray);
  679. }
  680. #endif
  681. // Use the first.
  682. jsonWrapper = [jsonWrapperAsArray firstObject];
  683. }
  684. // Convert the JSON error payload into a structured error
  685. NSMutableDictionary *errorJSON = [jsonWrapper valueForKey:@"error"];
  686. if (errorJSON) {
  687. GTLRErrorObject *errorObject = [GTLRErrorObject objectWithJSON:errorJSON];
  688. error = [errorObject foundationError];
  689. }
  690. }
  691. } else {
  692. // No structured JSON error was available; make a plaintext server
  693. // error response visible in the error object.
  694. NSString *reasonStr = [[NSString alloc] initWithData:(NSData * _Nonnull)data
  695. encoding:NSUTF8StringEncoding];
  696. NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : reasonStr };
  697. error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain
  698. code:status
  699. userInfo:userInfo];
  700. }
  701. } else {
  702. // Response data length is zero; we'll settle for returning the
  703. // fetcher's error.
  704. }
  705. }
  706. }
  707. [self handleParsedObjectForFetcher:fetcher
  708. executingQuery:executingQuery
  709. ticket:ticket
  710. error:error
  711. parsedObject:nil
  712. hasSentParsingStartNotification:NO
  713. completionHandler:completionHandler];
  714. }]; // fetcher completion handler
  715. // If something weird happens and the networking callbacks have been called
  716. // already synchronously, we don't want to return the ticket since the caller
  717. // will never know when to stop retaining it, so we'll make sure the
  718. // success/failure callbacks have not yet been called by checking the
  719. // ticket
  720. if (ticket.hasCalledCallback) {
  721. return nil;
  722. }
  723. return ticket;
  724. }
  725. - (GTMSessionUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request
  726. fetcherService:(GTMSessionFetcherService *)fetcherService
  727. params:(GTLRUploadParameters *)uploadParams {
  728. // Hang on to the user's requested chunk size, and ensure it's not tiny
  729. NSUInteger uploadChunkSize = [self serviceUploadChunkSize];
  730. if (uploadChunkSize < kMinimumUploadChunkSize) {
  731. uploadChunkSize = kMinimumUploadChunkSize;
  732. }
  733. NSString *uploadMIMEType = uploadParams.MIMEType;
  734. NSData *uploadData = uploadParams.data;
  735. NSURL *uploadFileURL = uploadParams.fileURL;
  736. NSFileHandle *uploadFileHandle = uploadParams.fileHandle;
  737. NSURL *uploadLocationURL = uploadParams.uploadLocationURL;
  738. // Create the upload fetcher.
  739. GTMSessionUploadFetcher *fetcher;
  740. if (uploadLocationURL) {
  741. // Resuming with the session fetcher and a file URL.
  742. GTLR_DEBUG_ASSERT(uploadFileURL != nil, @"Resume requires a file URL");
  743. fetcher = [GTMSessionUploadFetcher uploadFetcherWithLocation:uploadLocationURL
  744. uploadMIMEType:uploadMIMEType
  745. chunkSize:(int64_t)uploadChunkSize
  746. fetcherService:fetcherService];
  747. fetcher.uploadFileURL = uploadFileURL;
  748. } else {
  749. fetcher = [GTMSessionUploadFetcher uploadFetcherWithRequest:request
  750. uploadMIMEType:uploadMIMEType
  751. chunkSize:(int64_t)uploadChunkSize
  752. fetcherService:fetcherService];
  753. if (uploadFileURL) {
  754. fetcher.uploadFileURL = uploadFileURL;
  755. } else if (uploadData) {
  756. fetcher.uploadData = uploadData;
  757. } else if (uploadFileHandle) {
  758. #if DEBUG
  759. if (uploadParams.useBackgroundSession) {
  760. GTLR_DEBUG_LOG(@"Warning: GTLRUploadParameters should be supplied an uploadFileURL rather"
  761. @" than a file handle to support background uploads.\n %@", uploadParams);
  762. }
  763. #endif
  764. fetcher.uploadFileHandle = uploadFileHandle;
  765. }
  766. }
  767. fetcher.useBackgroundSession = uploadParams.useBackgroundSession;
  768. return fetcher;
  769. }
  770. #pragma mark -
  771. - (GTLRServiceTicket *)executeBatchQuery:(GTLRBatchQuery *)batchObj
  772. completionHandler:(GTLRServiceCompletionHandler)completionHandler
  773. ticket:(GTLRServiceTicket *)ticket {
  774. // Copy the original batch object and each query inside so our working queries cannot be modified
  775. // by the caller, and release the callback blocks from the supplied query objects.
  776. GTLRBatchQuery *batchCopy = [batchObj copy];
  777. [batchObj invalidateQuery];
  778. NSArray *queries = batchCopy.queries;
  779. NSUInteger numberOfQueries = queries.count;
  780. if (numberOfQueries == 0) return nil;
  781. // Create the batch of REST calls.
  782. NSMutableSet *requestIDs = [NSMutableSet setWithCapacity:numberOfQueries];
  783. NSMutableSet *loggingNames = [NSMutableSet set];
  784. GTMMIMEDocument *mimeDoc = [GTMMIMEDocument MIMEDocument];
  785. // Each batch part has two "header" sections, an outer and inner.
  786. // The inner headers are preceded by a line specifying the http request.
  787. // So a part looks like this:
  788. //
  789. // --END_OF_PART
  790. // Content-ID: gtlr_3
  791. // Content-Transfer-Encoding: binary
  792. // Content-Type: application/http
  793. //
  794. // POST https://www.googleapis.com/drive/v3/files/
  795. // Content-Length: 0
  796. // Content-Type: application/json
  797. //
  798. // {
  799. // "id": "04109509152946699072k"
  800. // }
  801. for (GTLRQuery *query in queries) {
  802. GTLRObject *bodyObject = query.bodyObject;
  803. NSDictionary *bodyJSON = bodyObject.JSON;
  804. NSString *requestID = query.requestID;
  805. if (requestID.length == 0) {
  806. GTLR_DEBUG_ASSERT(0, @"Invalid query ID: %@", [query class]);
  807. return nil;
  808. }
  809. if ([requestIDs containsObject:requestID]) {
  810. GTLR_DEBUG_ASSERT(0, @"Duplicate request ID in batch: %@", requestID);
  811. return nil;
  812. }
  813. [requestIDs addObject:requestID];
  814. // Create the inner request, body, and headers.
  815. NSURL *requestURL = [self URLFromQueryObject:query
  816. usePartialPaths:YES
  817. includeServiceURLQueryParams:NO];
  818. NSString *requestURLString = requestURL.absoluteString;
  819. NSError *error = nil;
  820. NSData *bodyData;
  821. if (bodyJSON) {
  822. bodyData = [NSJSONSerialization dataWithJSONObject:bodyJSON
  823. options:0
  824. error:&error];
  825. if (bodyData == nil) {
  826. GTLR_DEBUG_ASSERT(0, @"JSON generation error: %@\n JSON: %@", error, bodyJSON);
  827. return nil;
  828. }
  829. }
  830. NSString *httpRequestString = [NSString stringWithFormat:@"%@ %@\r\n",
  831. query.httpMethod ?: @"GET", requestURLString];
  832. NSDictionary *innerPartHeaders = @{ @"Content-Type" : @"application/json",
  833. @"Content-Length" : @(bodyData.length).stringValue };
  834. innerPartHeaders = MergeDictionaries(query.additionalHTTPHeaders, innerPartHeaders);
  835. NSData *innerPartHeadersData = [GTMMIMEDocument dataWithHeaders:innerPartHeaders];
  836. NSMutableData *innerData =
  837. [[httpRequestString dataUsingEncoding:NSUTF8StringEncoding] mutableCopy];
  838. [innerData appendData:innerPartHeadersData];
  839. if (bodyData) {
  840. [innerData appendData:bodyData];
  841. }
  842. // Combine the outer headers with the inner headers and body data.
  843. NSDictionary *outerPartHeaders = @{ @"Content-Type" : @"application/http",
  844. @"Content-ID" : requestID,
  845. @"Content-Transfer-Encoding" : @"binary" };
  846. [mimeDoc addPartWithHeaders:outerPartHeaders
  847. body:innerData];
  848. NSString *loggingName = query.loggingName ?: [[query class] description];
  849. [loggingNames addObject:loggingName];
  850. }
  851. #if !STRIP_GTM_FETCH_LOGGING
  852. // Set the fetcher log comment.
  853. if (!batchCopy.loggingName) {
  854. NSUInteger pageNumber = ticket.pagesFetchedCounter;
  855. NSString *pageStr = @"";
  856. if (pageNumber > 0) {
  857. pageStr = [NSString stringWithFormat:@"page %lu, ",
  858. (unsigned long)(pageNumber + 1)];
  859. }
  860. batchCopy.loggingName = [NSString stringWithFormat:@"batch: %@ (%@%lu queries)",
  861. [loggingNames.allObjects componentsJoinedByString:@", "],
  862. pageStr, (unsigned long)numberOfQueries];
  863. }
  864. #endif
  865. dispatch_data_t mimeDispatchData;
  866. unsigned long long mimeLength;
  867. NSString *mimeBoundary;
  868. [mimeDoc generateDispatchData:&mimeDispatchData
  869. length:&mimeLength
  870. boundary:&mimeBoundary];
  871. batchCopy.boundary = mimeBoundary;
  872. BOOL mayAuthorize = (batchCopy ? !batchCopy.shouldSkipAuthorization : YES);
  873. NSString *rootURLString = self.rootURLString;
  874. NSString *batchPath = self.batchPath ?: @"";
  875. NSString *batchURLString = [rootURLString stringByAppendingString:batchPath];
  876. GTLR_DEBUG_ASSERT(![batchPath hasPrefix:@"/"],
  877. @"batchPath shouldn't start with a slash: %@",
  878. batchPath);
  879. // Query parameters override service parameters.
  880. NSDictionary *mergedQueryParams = MergeDictionaries(self.additionalURLQueryParameters,
  881. batchObj.additionalURLQueryParameters);
  882. NSURL *batchURL;
  883. if (mergedQueryParams.count > 0) {
  884. batchURL = [GTLRService URLWithString:batchURLString
  885. queryParameters:mergedQueryParams];
  886. } else {
  887. batchURL = [NSURL URLWithString:batchURLString];
  888. }
  889. GTLRServiceTicket *resultTicket = [self fetchObjectWithURL:batchURL
  890. objectClass:[GTLRBatchResult class]
  891. bodyObject:nil
  892. dataToPost:(NSData *)mimeDispatchData
  893. ETag:nil
  894. httpMethod:@"POST"
  895. mayAuthorize:mayAuthorize
  896. completionHandler:completionHandler
  897. executingQuery:batchCopy
  898. ticket:ticket];
  899. return resultTicket;
  900. }
  901. #pragma mark -
  902. // Raw REST fetch method.
  903. - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
  904. objectClass:(Class)objectClass
  905. bodyObject:(GTLRObject *)bodyObject
  906. ETag:(NSString *)etag
  907. httpMethod:(NSString *)httpMethod
  908. mayAuthorize:(BOOL)mayAuthorize
  909. completionHandler:(GTLRServiceCompletionHandler)completionHandler
  910. executingQuery:(id<GTLRQueryProtocol>)executingQuery
  911. ticket:(GTLRServiceTicket *)ticket {
  912. // if no URL was supplied, treat this as if the fetch failed (below)
  913. // and immediately return a nil ticket, skipping the callbacks
  914. //
  915. // this might be considered normal (say, updating a read-only entry
  916. // that lacks an edit link) though higher-level calls may assert or
  917. // return errors depending on the specific usage
  918. if (targetURL == nil) return nil;
  919. NSData *dataToPost = nil;
  920. if (bodyObject != nil && !executingQuery.uploadParameters.shouldSendUploadOnly) {
  921. NSError *error = nil;
  922. NSDictionary *whatToSend;
  923. NSDictionary *json = bodyObject.JSON;
  924. if (json == nil) {
  925. // Since a body object was provided, we'll ensure there's at least an empty dictionary.
  926. json = [NSDictionary dictionary];
  927. }
  928. if (_dataWrapperRequired) {
  929. // create the top-level "data" object
  930. whatToSend = @{ @"data" : json };
  931. } else {
  932. whatToSend = json;
  933. }
  934. dataToPost = [NSJSONSerialization dataWithJSONObject:whatToSend
  935. options:0
  936. error:&error];
  937. if (dataToPost == nil) {
  938. GTLR_DEBUG_LOG(@"JSON generation error: %@", error);
  939. }
  940. }
  941. return [self fetchObjectWithURL:targetURL
  942. objectClass:objectClass
  943. bodyObject:bodyObject
  944. dataToPost:dataToPost
  945. ETag:etag
  946. httpMethod:httpMethod
  947. mayAuthorize:mayAuthorize
  948. completionHandler:completionHandler
  949. executingQuery:executingQuery
  950. ticket:ticket];
  951. }
  952. - (void)invokeProgressCallbackForTicket:(GTLRServiceTicket *)ticket
  953. deliveredBytes:(unsigned long long)numReadSoFar
  954. totalBytes:(unsigned long long)total {
  955. GTLRServiceUploadProgressBlock block = ticket.uploadProgressBlock;
  956. if (block) {
  957. dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
  958. if (ticket.cancelled) return;
  959. block(ticket, numReadSoFar, total);
  960. });
  961. }
  962. }
  963. // Three methods handle parsing of the fetched JSON data:
  964. // - prepareToParse posts a start notification and then spawns off parsing
  965. // on the operation queue (if there's an operation queue)
  966. // - parseObject does the parsing of the JSON string
  967. // - handleParsedObject posts the stop notification and calls the callback
  968. // with the parsed object or an error
  969. //
  970. // The middle method may run on a separate thread.
  971. - (void)prepareToParseObjectForFetcher:(GTMSessionFetcher *)fetcher
  972. executingQuery:(id<GTLRQueryProtocol>)executingQuery
  973. ticket:(GTLRServiceTicket *)ticket
  974. error:(NSError *)error
  975. defaultClass:(Class)defaultClass
  976. completionHandler:(GTLRServiceCompletionHandler)completionHandler {
  977. GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue);
  978. [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification
  979. object:ticket
  980. userInfo:nil];
  981. // For unit tests to cancel during parsing, we need a synchronous notification posted.
  982. // Because this notification is intended only for unit tests, there is no public symbol
  983. // for the notification name.
  984. NSNotificationCenter *nc =[NSNotificationCenter defaultCenter];
  985. [nc postNotificationName:@"kGTLRServiceTicketParsingStartedForTestNotification"
  986. object:ticket
  987. userInfo:nil];
  988. NSDictionary *batchClassMap;
  989. if ([executingQuery isBatchQuery]) {
  990. // build a dictionary of expected classes for the batch responses
  991. GTLRBatchQuery *batchQuery = (GTLRBatchQuery *)executingQuery;
  992. NSArray *queries = batchQuery.queries;
  993. batchClassMap = [NSMutableDictionary dictionaryWithCapacity:queries.count];
  994. for (GTLRQuery *singleQuery in queries) {
  995. [batchClassMap setValue:singleQuery.expectedObjectClass
  996. forKey:singleQuery.requestID];
  997. }
  998. }
  999. [self parseObjectFromDataOfFetcher:fetcher
  1000. executingQuery:executingQuery
  1001. ticket:ticket
  1002. error:error
  1003. defaultClass:defaultClass
  1004. batchClassMap:batchClassMap
  1005. hasSentParsingStartNotification:YES
  1006. completionHandler:completionHandler];
  1007. }
  1008. - (void)parseObjectFromDataOfFetcher:(GTMSessionFetcher *)fetcher
  1009. executingQuery:(id<GTLRQueryProtocol>)executingQuery
  1010. ticket:(GTLRServiceTicket *)ticket
  1011. error:(NSError *)error
  1012. defaultClass:(Class)defaultClass
  1013. batchClassMap:(NSDictionary *)batchClassMap
  1014. hasSentParsingStartNotification:(BOOL)hasSentParsingStartNotification
  1015. completionHandler:(GTLRServiceCompletionHandler)completionHandler {
  1016. GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue);
  1017. NSError *fetchError = error;
  1018. NSString *downloadAsDataObjectType = nil;
  1019. if (![executingQuery isBatchQuery]) {
  1020. GTLRQuery *singleQuery = (GTLRQuery *)executingQuery;
  1021. downloadAsDataObjectType = singleQuery.downloadAsDataObjectType;
  1022. }
  1023. NSDictionary *responseHeaders = fetcher.responseHeaders;
  1024. NSString *contentType = [responseHeaders objectForKey:@"Content-Type"];
  1025. NSData *data = fetcher.downloadedData;
  1026. BOOL hasData = data.length > 0;
  1027. BOOL isJSON = [contentType hasPrefix:@"application/json"];
  1028. GTLRObject *parsedObject;
  1029. if (hasData) {
  1030. #if GTLR_LOG_PERFORMANCE
  1031. NSTimeInterval secs1, secs2;
  1032. secs1 = [NSDate timeIntervalSinceReferenceDate];
  1033. #endif
  1034. id<GTLRObjectClassResolver> objectClassResolver = ticket.objectClassResolver;
  1035. if ((downloadAsDataObjectType.length != 0) && fetchError == nil) {
  1036. GTLRDataObject *dataObject = [GTLRDataObject object];
  1037. dataObject.data = data;
  1038. dataObject.contentType = contentType;
  1039. parsedObject = dataObject;
  1040. } else if (isJSON) {
  1041. NSError *parseError = nil;
  1042. NSMutableDictionary *jsonWrapper =
  1043. [NSJSONSerialization JSONObjectWithData:data
  1044. options:NSJSONReadingMutableContainers
  1045. error:&parseError];
  1046. if (jsonWrapper == nil) {
  1047. fetchError = parseError;
  1048. } else {
  1049. NSMutableDictionary *json;
  1050. if (_dataWrapperRequired) {
  1051. json = [jsonWrapper valueForKey:@"data"];
  1052. } else {
  1053. json = jsonWrapper;
  1054. }
  1055. if (json != nil) {
  1056. parsedObject = [GTLRObject objectForJSON:json
  1057. defaultClass:defaultClass
  1058. objectClassResolver:objectClassResolver];
  1059. }
  1060. }
  1061. } else {
  1062. // Has non-JSON data; it may be batch data.
  1063. NSString *boundary;
  1064. BOOL isBatchResponse = [self isContentTypeMultipart:contentType
  1065. boundary:&boundary];
  1066. if (isBatchResponse) {
  1067. NSArray *mimeParts = [GTMMIMEDocument MIMEPartsWithBoundary:boundary
  1068. data:data];
  1069. NSArray *responseParts = [self responsePartsWithMIMEParts:mimeParts];
  1070. GTLRBatchResult *batchResult = [self batchResultWithResponseParts:responseParts
  1071. batchClassMap:batchClassMap
  1072. objectClassResolver:objectClassResolver];
  1073. parsedObject = batchResult;
  1074. } else {
  1075. GTLR_DEBUG_ASSERT(0, @"Got unexpected content type '%@'", contentType);
  1076. }
  1077. } // isJSON
  1078. #if GTLR_LOG_PERFORMANCE
  1079. secs2 = [NSDate timeIntervalSinceReferenceDate];
  1080. NSLog(@"allocation of %@ took %f seconds", objectClass, secs2 - secs1);
  1081. #endif
  1082. }
  1083. [self handleParsedObjectForFetcher:fetcher
  1084. executingQuery:executingQuery
  1085. ticket:ticket
  1086. error:fetchError
  1087. parsedObject:parsedObject
  1088. hasSentParsingStartNotification:hasSentParsingStartNotification
  1089. completionHandler:completionHandler];
  1090. }
  1091. - (void)handleParsedObjectForFetcher:(GTMSessionFetcher *)fetcher
  1092. executingQuery:(id<GTLRQueryProtocol>)executingQuery
  1093. ticket:(GTLRServiceTicket *)ticket
  1094. error:(NSError *)error
  1095. parsedObject:(GTLRObject *)object
  1096. hasSentParsingStartNotification:(BOOL)hasSentParsingStartNotification
  1097. completionHandler:(GTLRServiceCompletionHandler)completionHandler {
  1098. GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue);
  1099. BOOL isResourceURLQuery = [executingQuery isKindOfClass:[GTLRResourceURLQuery class]];
  1100. // There may not be an object due to a fetch or parsing error
  1101. BOOL shouldFetchNextPages = ticket.shouldFetchNextPages && !isResourceURLQuery;
  1102. GTLRObject *previousObject = ticket.fetchedObject;
  1103. BOOL isFirstPage = (previousObject == nil);
  1104. if (shouldFetchNextPages && !isFirstPage && (object != nil)) {
  1105. // Accumulate new results
  1106. object = [self mergedNewResultObject:object
  1107. oldResultObject:previousObject
  1108. forQuery:executingQuery
  1109. ticket:ticket];
  1110. }
  1111. ticket.fetchedObject = object;
  1112. ticket.fetchError = error;
  1113. if (hasSentParsingStartNotification) {
  1114. // we want to always balance the start and stop notifications
  1115. [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification
  1116. object:ticket
  1117. userInfo:nil];
  1118. }
  1119. BOOL shouldCallCallbacks = YES;
  1120. if (error == nil) {
  1121. ++ticket.pagesFetchedCounter;
  1122. // Use the nextPageToken to fetch any later pages for non-batch queries
  1123. //
  1124. // This assumes a pagination model where objects have entries in a known "items"
  1125. // field and a "nextPageToken" field, and queries support a "pageToken"
  1126. // parameter.
  1127. if (shouldFetchNextPages) {
  1128. // Determine if we should fetch more pages of results
  1129. GTLRQuery *nextPageQuery =
  1130. (GTLRQuery *)[self nextPageQueryForQuery:executingQuery
  1131. result:object
  1132. ticket:ticket];
  1133. if (nextPageQuery) {
  1134. BOOL isFetchingMore = [self fetchNextPageWithQuery:nextPageQuery
  1135. completionHandler:completionHandler
  1136. ticket:ticket];
  1137. if (isFetchingMore) {
  1138. shouldCallCallbacks = NO;
  1139. }
  1140. } else {
  1141. // nextPageQuery == nil; no more page tokens are present
  1142. #if DEBUG && !GTLR_SKIP_PAGES_WARNING
  1143. // Each next page followed to accumulate all pages of a feed takes up to
  1144. // a few seconds. When multiple pages are being fetched, that
  1145. // usually indicates that a larger page size (that is, more items per
  1146. // feed fetched) should be requested.
  1147. //
  1148. // To avoid fetching many pages, set query.maxResults so the feed
  1149. // requested is large enough to rarely need to follow next links.
  1150. NSUInteger pageCount = ticket.pagesFetchedCounter;
  1151. if (pageCount > 2) {
  1152. NSString *queryLabel;
  1153. if ([executingQuery isBatchQuery]) {
  1154. queryLabel = @"batch query";
  1155. } else {
  1156. queryLabel = [[executingQuery class] description];
  1157. }
  1158. GTLR_DEBUG_LOG(@"Executing %@ query required fetching %lu pages; use a query with"
  1159. @" a larger maxResults for faster results",
  1160. queryLabel, (unsigned long)pageCount);
  1161. }
  1162. #endif
  1163. } // nextPageQuery
  1164. } else {
  1165. // !ticket.shouldFetchNextPages
  1166. #if DEBUG && !GTLR_SKIP_PAGES_WARNING
  1167. // Let the developer know that there were additional pages that would have been
  1168. // fetched if shouldFetchNextPages was enabled.
  1169. //
  1170. // The client may specify a larger page size with the query's maxResults property,
  1171. // or enable automatic pagination by turning on shouldFetchNextPages on the service
  1172. // or on the query's executionParameters.
  1173. if ([executingQuery respondsToSelector:@selector(pageToken)]
  1174. && [object isKindOfClass:[GTLRCollectionObject class]]
  1175. && [object respondsToSelector:@selector(nextPageToken)]
  1176. && object.nextPageToken.length > 0) {
  1177. GTLR_DEBUG_LOG(@"Executing %@ has additional pages of results not fetched because"
  1178. @" shouldFetchNextPages is not enabled", [executingQuery class]);
  1179. }
  1180. #endif
  1181. } // ticket.shouldFetchNextPages
  1182. } // error == nil
  1183. if (!isFirstPage) {
  1184. // Release callbacks from this completed page's query.
  1185. [executingQuery invalidateQuery];
  1186. }
  1187. // We no longer care about the queries for page 2 or later, so for the client
  1188. // inspecting the ticket in the callback, the executing query should be
  1189. // the original one
  1190. ticket.executingQuery = ticket.originalQuery;
  1191. if (!shouldCallCallbacks) {
  1192. // More fetches are happening.
  1193. } else {
  1194. dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
  1195. // First, call query-specific callback blocks. We do this before the
  1196. // fetch callback to let applications do any final clean-up (or update
  1197. // their UI) in the fetch callback.
  1198. GTLRQuery *originalQuery = (GTLRQuery *)ticket.originalQuery;
  1199. if (!ticket.cancelled) {
  1200. if (![originalQuery isBatchQuery]) {
  1201. // Single query
  1202. GTLRServiceCompletionHandler completionBlock = originalQuery.completionBlock;
  1203. if (completionBlock) {
  1204. completionBlock(ticket, object, error);
  1205. }
  1206. } else {
  1207. [self invokeBatchCompletionsWithTicket:ticket
  1208. batchQuery:(GTLRBatchQuery *)originalQuery
  1209. batchResult:(GTLRBatchResult *)object
  1210. error:error];
  1211. }
  1212. if (completionHandler) {
  1213. completionHandler(ticket, object, error);
  1214. }
  1215. ticket.hasCalledCallback = YES;
  1216. } // !ticket.cancelled
  1217. [ticket releaseTicketCallbacks];
  1218. [ticket endBackgroundTask];
  1219. // Even if the ticket has been cancelled, it should notify that it's stopped.
  1220. [ticket notifyStarting:NO];
  1221. // Release query callback blocks.
  1222. [originalQuery invalidateQuery];
  1223. });
  1224. }
  1225. }
  1226. - (BOOL)isContentTypeMultipart:(NSString *)contentType
  1227. boundary:(NSString **)outBoundary {
  1228. NSScanner *scanner = [NSScanner scannerWithString:contentType];
  1229. // By default, the scanner skips leading whitespace.
  1230. if ([scanner scanString:@"multipart/mixed; boundary=" intoString:NULL]
  1231. && [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet]
  1232. intoString:outBoundary]) {
  1233. return YES;
  1234. }
  1235. return NO;
  1236. }
  1237. - (NSArray <GTLRBatchResponsePart *>*)responsePartsWithMIMEParts:(NSArray <GTMMIMEDocumentPart *>*)mimeParts {
  1238. NSMutableArray *resultParts = [NSMutableArray arrayWithCapacity:mimeParts.count];
  1239. for (GTMMIMEDocumentPart *mimePart in mimeParts) {
  1240. GTLRBatchResponsePart *responsePart = [self responsePartWithMIMEPart:mimePart];
  1241. [resultParts addObject:responsePart];
  1242. }
  1243. return resultParts;
  1244. }
  1245. - (GTLRBatchResponsePart *)responsePartWithMIMEPart:(GTMMIMEDocumentPart *)mimePart {
  1246. // The MIME part body looks like
  1247. //
  1248. // Headers (from the MIME part):
  1249. // Content-Type: application/http
  1250. // Content-ID: response-gtlr_5
  1251. //
  1252. // Body (including inner headers):
  1253. // HTTP/1.1 200 OK
  1254. // Content-Type: application/json; charset=UTF-8
  1255. // Date: Sat, 16 Jan 2016 18:57:05 GMT
  1256. // Expires: Sat, 16 Jan 2016 18:57:05 GMT
  1257. // Cache-Control: private, max-age=0
  1258. // Content-Length: 13459
  1259. //
  1260. // {"kind":"drive#fileList", ...}
  1261. GTLRBatchResponsePart *responsePart = [[GTLRBatchResponsePart alloc] init];
  1262. // The only header in the actual (outer) MIME multipart headers we want is Content-ID.
  1263. //
  1264. // The content ID in the response looks like
  1265. //
  1266. // Content-ID: response-gtlr_5
  1267. //
  1268. // but we will strip the "response-" prefix.
  1269. NSDictionary *mimeHeaders = mimePart.headers;
  1270. NSString *responseContentID = mimeHeaders[@"Content-ID"];
  1271. if ([responseContentID hasPrefix:@"response-"]) {
  1272. responseContentID = [responseContentID substringFromIndex:@"response-".length];
  1273. }
  1274. responsePart.contentID = responseContentID;
  1275. // Split the body from the inner headers at the first CRLFCRLF.
  1276. NSArray <NSNumber *>*offsets;
  1277. NSData *mimePartBody = mimePart.body;
  1278. [GTMMIMEDocument searchData:mimePartBody
  1279. targetBytes:"\r\n\r\n"
  1280. targetLength:4
  1281. foundOffsets:&offsets];
  1282. if (offsets.count == 0) {
  1283. // Parse error.
  1284. NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
  1285. [userInfo setValue:mimePartBody forKey:kGTLRServiceErrorBodyDataKey];
  1286. [userInfo setValue:responseContentID forKey:kGTLRServiceErrorContentIDKey];
  1287. responsePart.parseError = [NSError errorWithDomain:kGTLRServiceErrorDomain
  1288. code:GTLRServiceErrorBatchResponseUnexpected
  1289. userInfo:userInfo];
  1290. } else {
  1291. // Separate the status/inner headers and the actual body.
  1292. NSUInteger partBodyLength = mimePartBody.length;
  1293. NSUInteger separatorOffset = offsets[0].unsignedIntegerValue;
  1294. NSData *innerHeaderData =
  1295. [mimePartBody subdataWithRange:NSMakeRange(0, (NSUInteger)separatorOffset)];
  1296. NSData *partBodyData;
  1297. if (separatorOffset + 4 < partBodyLength) {
  1298. NSUInteger offsetToBodyData = separatorOffset + 4;
  1299. NSUInteger bodyLength = mimePartBody.length - offsetToBodyData;
  1300. partBodyData = [mimePartBody subdataWithRange:NSMakeRange(offsetToBodyData, bodyLength)];
  1301. }
  1302. // Parse to separate the status line and the inner headers (though we don't
  1303. // really do much with either.)
  1304. [GTMMIMEDocument searchData:innerHeaderData
  1305. targetBytes:"\r\n"
  1306. targetLength:2
  1307. foundOffsets:&offsets];
  1308. NSData *statusLine;
  1309. NSData *actualInnerHeaderData;
  1310. if (offsets.count) {
  1311. NSRange statusRange = NSMakeRange(0, offsets[0].unsignedIntegerValue);
  1312. statusLine = [innerHeaderData subdataWithRange:statusRange];
  1313. NSUInteger actualInnerHeaderOffset = offsets[0].unsignedIntegerValue + 2;
  1314. if (innerHeaderData.length - actualInnerHeaderOffset > 0) {
  1315. NSRange actualInnerHeaderRange =
  1316. NSMakeRange(actualInnerHeaderOffset,
  1317. innerHeaderData.length - actualInnerHeaderOffset);
  1318. actualInnerHeaderData = [innerHeaderData subdataWithRange:actualInnerHeaderRange];
  1319. }
  1320. } else {
  1321. // There appears to only be a status line.
  1322. //
  1323. // This means there were no reponse headers. "Date" seems like it should
  1324. // be required, but https://tools.ietf.org/html/rfc7231#section-7.1.1.2
  1325. // lets even that be left off if a server doesn't have a clock it knows
  1326. // to be correct.
  1327. statusLine = innerHeaderData;
  1328. }
  1329. NSString *statusString;
  1330. NSInteger statusCode;
  1331. [self getResponseLineFromData:statusLine
  1332. statusCode:&statusCode
  1333. statusString:&statusString];
  1334. responsePart.statusCode = statusCode;
  1335. responsePart.statusString = statusString;
  1336. if (actualInnerHeaderData) {
  1337. responsePart.headers = [GTMMIMEDocument headersWithData:actualInnerHeaderData];
  1338. }
  1339. // Create JSON from the body.
  1340. // (if there is any, methods like delete return nothing)
  1341. NSMutableDictionary *json;
  1342. if (partBodyData) {
  1343. NSError *parseError = nil;
  1344. json = [NSJSONSerialization JSONObjectWithData:partBodyData
  1345. options:NSJSONReadingMutableContainers
  1346. error:&parseError];
  1347. if (!json) {
  1348. if (!parseError) {
  1349. // There should be an error, but just incase...
  1350. parseError = [NSError errorWithDomain:kGTLRServiceErrorDomain
  1351. code:GTLRServiceErrorBatchResponseUnexpected
  1352. userInfo:nil];
  1353. }
  1354. // Add our content ID and part body data to the parse error.
  1355. NSMutableDictionary *userInfo =
  1356. [NSMutableDictionary dictionaryWithDictionary:parseError.userInfo];
  1357. [userInfo setValue:mimePartBody forKey:kGTLRServiceErrorBodyDataKey];
  1358. [userInfo setValue:responseContentID forKey:kGTLRServiceErrorContentIDKey];
  1359. responsePart.parseError = [NSError errorWithDomain:parseError.domain
  1360. code:parseError.code
  1361. userInfo:userInfo];
  1362. }
  1363. }
  1364. responsePart.JSON = json;
  1365. }
  1366. return responsePart;
  1367. }
  1368. - (void)getResponseLineFromData:(NSData *)data
  1369. statusCode:(NSInteger *)outStatusCode
  1370. statusString:(NSString **)outStatusString {
  1371. // Sample response line:
  1372. // HTTP/1.1 200 OK
  1373. *outStatusCode = -1;
  1374. *outStatusString = @"???";
  1375. NSString *responseLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  1376. if (!responseLine) return;
  1377. NSScanner *scanner = [NSScanner scannerWithString:responseLine];
  1378. // Scanner by default skips whitespace when locating the start of the next characters to
  1379. // scan.
  1380. NSCharacterSet *wsSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
  1381. NSCharacterSet *newlineSet = [NSCharacterSet newlineCharacterSet];
  1382. NSString *httpVersion;
  1383. if ([scanner scanUpToCharactersFromSet:wsSet intoString:&httpVersion]
  1384. && [scanner scanInteger:outStatusCode]
  1385. && [scanner scanUpToCharactersFromSet:newlineSet intoString:outStatusString]) {
  1386. // Got it all.
  1387. #if DEBUG
  1388. if (![httpVersion hasPrefix:@"HTTP/"]) {
  1389. GTLR_DEBUG_LOG(@"GTLRService: Non-standard HTTP Version: %@", httpVersion);
  1390. }
  1391. #endif
  1392. }
  1393. }
  1394. - (GTLRBatchResult *)batchResultWithResponseParts:(NSArray <GTLRBatchResponsePart *>*)parts
  1395. batchClassMap:(NSDictionary *)batchClassMap
  1396. objectClassResolver:(id<GTLRObjectClassResolver>)objectClassResolver {
  1397. // Allow the resolver to override the batch rules class also.
  1398. Class resultClass =
  1399. GTLRObjectResolveClass(objectClassResolver,
  1400. [NSDictionary dictionary],
  1401. [GTLRBatchResult class]);
  1402. GTLRBatchResult *batchResult = [resultClass object];
  1403. NSMutableDictionary *successes = [NSMutableDictionary dictionary];
  1404. NSMutableDictionary *failures = [NSMutableDictionary dictionary];
  1405. NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionary];
  1406. for (GTLRBatchResponsePart *responsePart in parts) {
  1407. NSString *contentID = responsePart.contentID;
  1408. NSDictionary *json = responsePart.JSON;
  1409. NSError *parseError = responsePart.parseError;
  1410. NSInteger statusCode = responsePart.statusCode;
  1411. [responseHeaders setValue:responsePart.headers forKey:contentID];
  1412. if (parseError) {
  1413. GTLRErrorObject *parseErrorObject = [GTLRErrorObject objectWithFoundationError:parseError];
  1414. [failures setValue:parseErrorObject forKey:contentID];
  1415. } else {
  1416. // There is JSON.
  1417. NSMutableDictionary *errorJSON = [json objectForKey:@"error"];
  1418. if (errorJSON) {
  1419. // A JSON error body should be the most informative error.
  1420. GTLRErrorObject *errorObject = [GTLRErrorObject objectWithJSON:errorJSON];
  1421. [failures setValue:errorObject forKey:contentID];
  1422. } else if (statusCode < 200 || statusCode > 399) {
  1423. // Report a fetch failure for this part that lacks a JSON error.
  1424. NSString *errorStr = responsePart.statusString;
  1425. NSDictionary *userInfo = @{
  1426. NSLocalizedDescriptionKey : (errorStr ?: @"<unknown>"),
  1427. };
  1428. NSError *httpError = [NSError errorWithDomain:kGTLRServiceErrorDomain
  1429. code:GTLRServiceErrorBatchResponseStatusCode
  1430. userInfo:userInfo];
  1431. GTLRErrorObject *httpErrorObject = [GTLRErrorObject objectWithFoundationError:httpError];
  1432. [failures setValue:httpErrorObject forKey:contentID];
  1433. } else {
  1434. // The JSON represents a successful response.
  1435. Class defaultClass = batchClassMap[contentID];
  1436. id resultObject = [GTLRObject objectForJSON:[json mutableCopy]
  1437. defaultClass:defaultClass
  1438. objectClassResolver:objectClassResolver];
  1439. if (resultObject == nil) {
  1440. // Methods like delete return no object.
  1441. resultObject = [NSNull null];
  1442. }
  1443. [successes setValue:resultObject forKey:contentID];
  1444. } // errorJSON
  1445. } // parseError
  1446. } // for
  1447. batchResult.successes = successes;
  1448. batchResult.failures = failures;
  1449. batchResult.responseHeaders = responseHeaders;
  1450. return batchResult;
  1451. }
  1452. - (void)invokeBatchCompletionsWithTicket:(GTLRServiceTicket *)ticket
  1453. batchQuery:(GTLRBatchQuery *)batchQuery
  1454. batchResult:(GTLRBatchResult *)batchResult
  1455. error:(NSError *)error {
  1456. // Batch query
  1457. //
  1458. // We'll step through the queries of the original batch, not of the
  1459. // batch result
  1460. GTLR_ASSERT_CURRENT_QUEUE_DEBUG(ticket.callbackQueue);
  1461. NSDictionary *successes = batchResult.successes;
  1462. NSDictionary *failures = batchResult.failures;
  1463. for (GTLRQuery *oneQuery in batchQuery.queries) {
  1464. GTLRServiceCompletionHandler completionBlock = oneQuery.completionBlock;
  1465. if (completionBlock) {
  1466. // If there was no networking error, look for a query-specific
  1467. // error or result
  1468. GTLRObject *oneResult = nil;
  1469. NSError *oneError = error;
  1470. if (oneError == nil) {
  1471. NSString *requestID = [oneQuery requestID];
  1472. GTLRErrorObject *gtlrError = [failures objectForKey:requestID];
  1473. if (gtlrError) {
  1474. oneError = [gtlrError foundationError];
  1475. } else {
  1476. oneResult = [successes objectForKey:requestID];
  1477. if (oneResult == nil) {
  1478. // We found neither a success nor a failure for this query, unexpectedly.
  1479. GTLR_DEBUG_LOG(@"GTLRService: Batch result missing for request %@",
  1480. requestID);
  1481. oneError = [NSError errorWithDomain:kGTLRServiceErrorDomain
  1482. code:GTLRServiceErrorQueryResultMissing
  1483. userInfo:nil];
  1484. }
  1485. }
  1486. }
  1487. completionBlock(ticket, oneResult, oneError);
  1488. }
  1489. }
  1490. }
  1491. - (void)simulateFetchWithTicket:(GTLRServiceTicket *)ticket
  1492. testBlock:(GTLRServiceTestBlock)testBlock
  1493. dataToPost:(NSData *)dataToPost
  1494. completionHandler:(GTLRServiceCompletionHandler)completionHandler {
  1495. GTLRQuery *originalQuery = (GTLRQuery *)ticket.originalQuery;
  1496. ticket.executingQuery = originalQuery;
  1497. testBlock(ticket, ^(id testObject, NSError *testError) {
  1498. dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
  1499. if (!ticket.cancelled) {
  1500. if (testError) {
  1501. // During simulation, we invoke any retry block, but ignore the result.
  1502. const BOOL willRetry = NO;
  1503. GTLRServiceRetryBlock retryBlock = ticket.retryBlock;
  1504. if (retryBlock) {
  1505. (void)retryBlock(ticket, willRetry, testError);
  1506. }
  1507. } else {
  1508. // Simulate upload progress, calling back up to three times.
  1509. if (ticket.uploadProgressBlock) {
  1510. GTLRQuery *query = (GTLRQuery *)ticket.originalQuery;
  1511. unsigned long long uploadLength = [self simulatedUploadLengthForQuery:query
  1512. dataToPost:dataToPost];
  1513. unsigned long long sendReportSize = uploadLength / 3 + 1;
  1514. unsigned long long totalSentSoFar = 0;
  1515. while (totalSentSoFar < uploadLength) {
  1516. unsigned long long bytesRemaining = uploadLength - totalSentSoFar;
  1517. sendReportSize = MIN(sendReportSize, bytesRemaining);
  1518. totalSentSoFar += sendReportSize;
  1519. [self invokeProgressCallbackForTicket:ticket
  1520. deliveredBytes:(unsigned long long)totalSentSoFar
  1521. totalBytes:(unsigned long long)uploadLength];
  1522. }
  1523. [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification
  1524. object:ticket
  1525. userInfo:nil];
  1526. [ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification
  1527. object:ticket
  1528. userInfo:nil];
  1529. }
  1530. }
  1531. if (![originalQuery isBatchQuery]) {
  1532. // Single query
  1533. GTLRServiceCompletionHandler completionBlock = originalQuery.completionBlock;
  1534. if (completionBlock) {
  1535. completionBlock(ticket, testObject, testError);
  1536. }
  1537. } else {
  1538. // Batch query
  1539. GTLR_DEBUG_ASSERT(!testObject || [testObject isKindOfClass:[GTLRBatchResult class]],
  1540. @"Batch queries should have result objects of type GTLRBatchResult (not %@)",
  1541. [testObject class]);
  1542. [self invokeBatchCompletionsWithTicket:ticket
  1543. batchQuery:(GTLRBatchQuery *)originalQuery
  1544. batchResult:(GTLRBatchResult *)testObject
  1545. error:testError];
  1546. } // isBatchQuery
  1547. if (completionHandler) {
  1548. completionHandler(ticket, testObject, testError);
  1549. }
  1550. ticket.hasCalledCallback = YES;
  1551. } // !ticket.cancelled
  1552. // Even if the ticket has been cancelled, it should notify that it's stopped.
  1553. [ticket notifyStarting:NO];
  1554. // Release query callback blocks.
  1555. [originalQuery invalidateQuery];
  1556. }); // dispatch_group_async
  1557. }); // testBlock
  1558. }
  1559. - (unsigned long long)simulatedUploadLengthForQuery:(GTLRQuery *)query
  1560. dataToPost:(NSData *)dataToPost {
  1561. // We're uploading the body object and other posted metadata, plus optionally the
  1562. // data or file specified in the upload parameters.
  1563. unsigned long long uploadLength = dataToPost.length;
  1564. GTLRUploadParameters *uploadParameters = query.uploadParameters;
  1565. if (uploadParameters) {
  1566. NSData *uploadData = uploadParameters.data;
  1567. if (uploadData) {
  1568. uploadLength += uploadData.length;
  1569. } else {
  1570. NSURL *fileURL = uploadParameters.fileURL;
  1571. if (fileURL) {
  1572. NSError *fileError = nil;
  1573. NSNumber *fileSizeNum = nil;
  1574. if ([fileURL getResourceValue:&fileSizeNum
  1575. forKey:NSURLFileSizeKey
  1576. error:&fileError]) {
  1577. uploadLength += fileSizeNum.unsignedLongLongValue;
  1578. }
  1579. } else {
  1580. NSFileHandle *fileHandle = uploadParameters.fileHandle;
  1581. unsigned long long fileLength = [fileHandle seekToEndOfFile];
  1582. uploadLength += fileLength;
  1583. }
  1584. }
  1585. }
  1586. return uploadLength;
  1587. }
  1588. #pragma mark -
  1589. // Given a single or batch query and its result, make a new query
  1590. // for the next pages, if any. Returns nil if there's no additional
  1591. // query to make.
  1592. //
  1593. // This method calls itself recursively to make the individual next page
  1594. // queries for a batch query.
  1595. - (id <GTLRQueryProtocol>)nextPageQueryForQuery:(id<GTLRQueryProtocol>)query
  1596. result:(GTLRObject *)object
  1597. ticket:(GTLRServiceTicket *)ticket {
  1598. if (![query isBatchQuery]) {
  1599. // This is a single query
  1600. GTLRQuery *currentPageQuery = (GTLRQuery *)query;
  1601. // Determine if we should fetch more pages of results
  1602. GTLRQuery *nextPageQuery = nil;
  1603. NSString *nextPageToken = nil;
  1604. if ([object respondsToSelector:@selector(nextPageToken)]
  1605. && [currentPageQuery respondsToSelector:@selector(pageToken)]) {
  1606. nextPageToken = [object performSelector:@selector(nextPageToken)];
  1607. }
  1608. if (nextPageToken && [object isKindOfClass:[GTLRCollectionObject class]]) {
  1609. NSString *itemsKey = [[object class] collectionItemsKey];
  1610. GTLR_DEBUG_ASSERT(itemsKey != nil, @"Missing accumulation items key for %@", [object class]);
  1611. SEL itemsSel = NSSelectorFromString(itemsKey);
  1612. if ([object respondsToSelector:itemsSel]) {
  1613. // Make a query for the next page, preserving the request ID
  1614. nextPageQuery = [currentPageQuery copy];
  1615. nextPageQuery.requestID = currentPageQuery.requestID;
  1616. [nextPageQuery performSelector:@selector(setPageToken:)
  1617. withObject:nextPageToken];
  1618. } else {
  1619. GTLR_DEBUG_ASSERT(0, @"%@ does not implement its collection items property \"%@\"",
  1620. [object class], itemsKey);
  1621. }
  1622. }
  1623. return nextPageQuery;
  1624. } else {
  1625. // This is a batch query
  1626. //
  1627. // Check if there's a next page to fetch for any of the success
  1628. // results by invoking this method recursively on each of those results
  1629. GTLRBatchResult *batchResult = (GTLRBatchResult *)object;
  1630. GTLRBatchQuery *nextPageBatchQuery = nil;
  1631. NSDictionary *successes = batchResult.successes;
  1632. for (NSString *requestID in successes) {
  1633. GTLRObject *singleObject = [successes objectForKey:requestID];
  1634. GTLRQuery *singleQuery = [ticket queryForRequestID:requestID];
  1635. GTLRQuery *newQuery =
  1636. (GTLRQuery *)[self nextPageQueryForQuery:singleQuery
  1637. result:singleObject
  1638. ticket:ticket];
  1639. if (newQuery) {
  1640. // There is another query to fetch
  1641. if (nextPageBatchQuery == nil) {
  1642. nextPageBatchQuery = [GTLRBatchQuery batchQuery];
  1643. }
  1644. [nextPageBatchQuery addQuery:newQuery];
  1645. }
  1646. }
  1647. return nextPageBatchQuery;
  1648. }
  1649. }
  1650. // When a ticket is set to fetch more pages for feeds, this routine
  1651. // initiates the fetch for each additional feed page
  1652. //
  1653. // Returns YES if fetching of the next page has started.
  1654. - (BOOL)fetchNextPageWithQuery:(GTLRQuery *)query
  1655. completionHandler:(GTLRServiceCompletionHandler)handler
  1656. ticket:(GTLRServiceTicket *)ticket {
  1657. // Sanity check the number of pages fetched already
  1658. if (ticket.pagesFetchedCounter > kMaxNumberOfNextPagesFetched) {
  1659. // Sanity check failed: way too many pages were fetched, so the query's
  1660. // page size should be bigger to avoid driving up networking and server
  1661. // overhead.
  1662. //
  1663. // The client should be querying with a higher max results per page
  1664. // to avoid this.
  1665. GTLR_DEBUG_ASSERT(0, @"Fetched too many next pages executing %@;"
  1666. @" increase maxResults page size to avoid this.",
  1667. [query class]);
  1668. return NO;
  1669. }
  1670. GTLRServiceTicket *newTicket;
  1671. if ([query isBatchQuery]) {
  1672. newTicket = [self executeBatchQuery:(GTLRBatchQuery *)query
  1673. completionHandler:handler
  1674. ticket:ticket];
  1675. } else {
  1676. BOOL mayAuthorize = !query.shouldSkipAuthorization;
  1677. NSURL *url = [self URLFromQueryObject:query
  1678. usePartialPaths:NO
  1679. includeServiceURLQueryParams:YES];
  1680. newTicket = [self fetchObjectWithURL:url
  1681. objectClass:query.expectedObjectClass
  1682. bodyObject:query.bodyObject
  1683. ETag:nil
  1684. httpMethod:query.httpMethod
  1685. mayAuthorize:mayAuthorize
  1686. completionHandler:handler
  1687. executingQuery:query
  1688. ticket:ticket];
  1689. }
  1690. // In the bizarre case that the fetch didn't begin, newTicket will be
  1691. // nil. So long as the new ticket is the same as the ticket we're
  1692. // continuing, then we're happy.
  1693. NSAssert(newTicket == ticket || newTicket == nil,
  1694. @"Pagination should not create an additional ticket: %@", newTicket);
  1695. BOOL isFetchingNextPageWithCurrentTicket = (newTicket == ticket);
  1696. return isFetchingNextPageWithCurrentTicket;
  1697. }
  1698. // Given a new single or batch result (meaning additional pages for a previous
  1699. // query result), merge it into the old result, and return the updated object.
  1700. //
  1701. // For a single result, this inserts the old result items into the new result.
  1702. // For batch results, this replaces some of the old items with new items.
  1703. //
  1704. // This method changes the objects passed in (the old result for batches, the new result
  1705. // for individual objects.)
  1706. - (GTLRObject *)mergedNewResultObject:(GTLRObject *)newResult
  1707. oldResultObject:(GTLRObject *)oldResult
  1708. forQuery:(id<GTLRQueryProtocol>)query
  1709. ticket:(GTLRServiceTicket *)ticket {
  1710. GTLR_DEBUG_ASSERT([oldResult isMemberOfClass:[newResult class]],
  1711. @"Trying to merge %@ and %@", [oldResult class], [newResult class]);
  1712. if ([query isBatchQuery]) {
  1713. // Batch query result
  1714. //
  1715. // The new batch results are a subset of the old result's queries, since
  1716. // not all queries in the batch necessarily have additional pages.
  1717. //
  1718. // New success objects replace old success objects, with the old items
  1719. // prepended; new failure objects replace old success objects.
  1720. // We will update the old batch results with accumulated items, using the
  1721. // new objects, and return the old batch.
  1722. //
  1723. // We reuse the old batch results object because it may include some earlier
  1724. // results which did not have additional pages.
  1725. GTLRBatchResult *newBatchResult = (GTLRBatchResult *)newResult;
  1726. GTLRBatchResult *oldBatchResult = (GTLRBatchResult *)oldResult;
  1727. NSDictionary *newSuccesses = newBatchResult.successes;
  1728. if (newSuccesses.count > 0) {
  1729. NSDictionary *oldSuccesses = oldBatchResult.successes;
  1730. NSMutableDictionary *mutableOldSuccesses = [oldSuccesses mutableCopy];
  1731. for (NSString *requestID in newSuccesses) {
  1732. GTLRObject *newObj = [newSuccesses objectForKey:requestID];
  1733. GTLRObject *oldObj = [oldSuccesses objectForKey:requestID];
  1734. GTLRQuery *thisQuery = [ticket queryForRequestID:requestID];
  1735. // Recursively merge the single query's result object, appending new items to the old items.
  1736. GTLRObject *updatedObj = [self mergedNewResultObject:newObj
  1737. oldResultObject:oldObj
  1738. forQuery:thisQuery
  1739. ticket:ticket];
  1740. // In the old batch, replace the old result object with the new one.
  1741. [mutableOldSuccesses setObject:updatedObj forKey:requestID];
  1742. } // for requestID
  1743. oldBatchResult.successes = mutableOldSuccesses;
  1744. } // newSuccesses.count > 0
  1745. NSDictionary *newFailures = newBatchResult.failures;
  1746. if (newFailures.count > 0) {
  1747. NSMutableDictionary *mutableOldSuccesses = [oldBatchResult.successes mutableCopy];
  1748. NSMutableDictionary *mutableOldFailures = [oldBatchResult.failures mutableCopy];
  1749. for (NSString *requestID in newFailures) {
  1750. // In the old batch, replace old successes or failures with the new failure.
  1751. GTLRErrorObject *newError = [newFailures objectForKey:requestID];
  1752. [mutableOldFailures setObject:newError forKey:requestID];
  1753. [mutableOldSuccesses removeObjectForKey:requestID];
  1754. }
  1755. oldBatchResult.failures = mutableOldFailures;
  1756. oldBatchResult.successes = mutableOldSuccesses;
  1757. } // newFailures.count > 0
  1758. return oldBatchResult;
  1759. } else {
  1760. // Single query result
  1761. //
  1762. // Merge the items into the new object, and return the new object.
  1763. NSString *itemsKey = [[oldResult class] collectionItemsKey];
  1764. GTLR_DEBUG_ASSERT([oldResult respondsToSelector:NSSelectorFromString(itemsKey)],
  1765. @"Collection items key \"%@\" not implemented by %@", itemsKey, oldResult);
  1766. if (itemsKey) {
  1767. // Append the new items to the old items.
  1768. NSArray *oldItems = [oldResult valueForKey:itemsKey];
  1769. NSArray *newItems = [newResult valueForKey:itemsKey];
  1770. NSMutableArray *items = [NSMutableArray arrayWithArray:oldItems];
  1771. [items addObjectsFromArray:newItems];
  1772. [newResult setValue:items forKey:itemsKey];
  1773. } else {
  1774. // This shouldn't happen.
  1775. newResult = oldResult;
  1776. }
  1777. return newResult;
  1778. }
  1779. }
  1780. #pragma mark -
  1781. // GTLRQuery methods.
  1782. // Helper to create the URL from the parts.
  1783. - (NSURL *)URLFromQueryObject:(GTLRQuery *)query
  1784. usePartialPaths:(BOOL)usePartialPaths
  1785. includeServiceURLQueryParams:(BOOL)includeServiceURLQueryParams {
  1786. NSString *rootURLString = self.rootURLString;
  1787. // Skip URI template expansion if the resource URL was provided.
  1788. if ([query isKindOfClass:[GTLRResourceURLQuery class]]) {
  1789. // Because the query is created by the service rather than by the user,
  1790. // query.additionalURLQueryParameters must be nil, and usePartialPaths
  1791. // is irrelevant as the query is not in a batch.
  1792. GTLR_DEBUG_ASSERT(!usePartialPaths,
  1793. @"Batch not supported with resource URL fetch");
  1794. GTLR_DEBUG_ASSERT(!query.uploadParameters && !query.useMediaDownloadService
  1795. && !query.downloadAsDataObjectType && !query.additionalURLQueryParameters,
  1796. @"Unsupported query properties");
  1797. NSURL *result = ((GTLRResourceURLQuery *)query).resourceURL;
  1798. if (includeServiceURLQueryParams) {
  1799. NSDictionary *additionalParams = self.additionalURLQueryParameters;
  1800. if (additionalParams.count) {
  1801. result = [GTLRService URLWithString:result.absoluteString
  1802. queryParameters:additionalParams];
  1803. }
  1804. }
  1805. return result;
  1806. }
  1807. // This is all the dance needed due to having query and path parameters for
  1808. // REST based queries.
  1809. NSDictionary *params = query.JSON;
  1810. NSString *queryFilledPathURI = [GTLRURITemplate expandTemplate:query.pathURITemplate
  1811. values:params];
  1812. // Per https://developers.google.com/discovery/v1/using#build-compose and
  1813. // https://developers.google.com/discovery/v1/using#discovery-doc-methods-mediadownload
  1814. // glue together the parts.
  1815. NSString *servicePath = self.servicePath ?: @"";
  1816. NSString *uploadPath = @"";
  1817. NSString *downloadPath = @"";
  1818. GTLR_DEBUG_ASSERT([rootURLString hasSuffix:@"/"],
  1819. @"rootURLString should end in a slash: %@", rootURLString);
  1820. GTLR_DEBUG_ASSERT(((servicePath.length == 0) ||
  1821. (![servicePath hasPrefix:@"/"] && [servicePath hasSuffix:@"/"])),
  1822. @"servicePath shouldn't start with a slash but should end with one: %@",
  1823. servicePath);
  1824. GTLR_DEBUG_ASSERT(![query.pathURITemplate hasPrefix:@"/"],
  1825. @"the queries's pathURITemplate should not start with a slash: %@",
  1826. query.pathURITemplate);
  1827. GTLRUploadParameters *uploadParameters = query.uploadParameters;
  1828. if (uploadParameters != nil) {
  1829. // If there is an override, clear all the parts and just use it with the
  1830. // the rootURLString.
  1831. NSString *override = (uploadParameters.shouldUploadWithSingleRequest
  1832. ? query.simpleUploadPathURITemplateOverride
  1833. : query.resumableUploadPathURITemplateOverride);
  1834. if (override.length > 0) {
  1835. GTLR_DEBUG_ASSERT(![override hasPrefix:@"/"],
  1836. @"The query's %@UploadPathURITemplateOverride should not start with a slash: %@",
  1837. (uploadParameters.shouldUploadWithSingleRequest ? @"Simple" : @"resumable"),
  1838. override);
  1839. queryFilledPathURI = [GTLRURITemplate expandTemplate:override
  1840. values:params];
  1841. servicePath = @"";
  1842. } else {
  1843. if (uploadParameters.shouldUploadWithSingleRequest) {
  1844. uploadPath = self.simpleUploadPath ?: @"";
  1845. } else {
  1846. uploadPath = self.resumableUploadPath ?: @"";
  1847. }
  1848. GTLR_DEBUG_ASSERT(((uploadPath.length == 0) ||
  1849. (![uploadPath hasPrefix:@"/"] &&
  1850. [uploadPath hasSuffix:@"/"])),
  1851. @"%@UploadPath shouldn't start with a slash but should end with one: %@",
  1852. (uploadParameters.shouldUploadWithSingleRequest ? @"Simple" : @"Redefine"),
  1853. uploadPath);
  1854. }
  1855. }
  1856. if (query.useMediaDownloadService &&
  1857. (query.downloadAsDataObjectType.length > 0)) {
  1858. downloadPath = @"download/";
  1859. GTLR_DEBUG_ASSERT(uploadPath.length == 0,
  1860. @"Uploading while also downloading via mediaDownService"
  1861. @" is not well defined.");
  1862. }
  1863. if (usePartialPaths) rootURLString = @"/";
  1864. NSString *urlString =
  1865. [NSString stringWithFormat:@"%@%@%@%@%@",
  1866. rootURLString, downloadPath, uploadPath, servicePath, queryFilledPathURI];
  1867. // Remove the path parameters from the dictionary.
  1868. NSMutableDictionary *workingQueryParams = [NSMutableDictionary dictionaryWithDictionary:params];
  1869. NSArray *pathParameterNames = query.pathParameterNames;
  1870. if (pathParameterNames.count > 0) {
  1871. [workingQueryParams removeObjectsForKeys:pathParameterNames];
  1872. }
  1873. // Note: A developer can override the uploadType and alt query parameters via
  1874. // query.additionalURLQueryParameters since those are added afterwards.
  1875. if (uploadParameters.shouldUploadWithSingleRequest) {
  1876. NSString *uploadType = uploadParameters.shouldSendUploadOnly ? @"media" : @"multipart";
  1877. [workingQueryParams setObject:uploadType forKey:@"uploadType"];
  1878. }
  1879. NSString *downloadAsDataObjectType = query.downloadAsDataObjectType;
  1880. if (downloadAsDataObjectType.length > 0) {
  1881. [workingQueryParams setObject:downloadAsDataObjectType
  1882. forKey:@"alt"];
  1883. }
  1884. // Add any parameters the user added directly to the query.
  1885. NSDictionary *mergedParams = MergeDictionaries(workingQueryParams,
  1886. query.additionalURLQueryParameters);
  1887. if (includeServiceURLQueryParams) {
  1888. // Query parameters override service parameters.
  1889. mergedParams = MergeDictionaries(self.additionalURLQueryParameters, mergedParams);
  1890. }
  1891. NSURL *result = [GTLRService URLWithString:urlString
  1892. queryParameters:mergedParams];
  1893. return result;
  1894. }
  1895. - (GTLRServiceTicket *)executeQuery:(id<GTLRQueryProtocol>)queryObj
  1896. delegate:(id)delegate
  1897. didFinishSelector:(SEL)finishedSelector {
  1898. GTMSessionFetcherAssertValidSelector(delegate, finishedSelector,
  1899. @encode(GTLRServiceTicket *), @encode(GTLRObject *), @encode(NSError *), 0);
  1900. GTLRServiceCompletionHandler completionHandler = ^(GTLRServiceTicket *ticket,
  1901. id object,
  1902. NSError *error) {
  1903. if (delegate && finishedSelector) {
  1904. NSMethodSignature *sig = [delegate methodSignatureForSelector:finishedSelector];
  1905. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  1906. [invocation setSelector:(SEL)finishedSelector];
  1907. [invocation setTarget:delegate];
  1908. [invocation setArgument:&ticket atIndex:2];
  1909. [invocation setArgument:&object atIndex:3];
  1910. [invocation setArgument:&error atIndex:4];
  1911. [invocation invoke];
  1912. }
  1913. };
  1914. return [self executeQuery:queryObj completionHandler:completionHandler];
  1915. }
  1916. - (GTLRServiceTicket *)executeQuery:(id<GTLRQueryProtocol>)queryObj
  1917. completionHandler:(void (^)(GTLRServiceTicket *ticket, id object,
  1918. NSError *error))handler {
  1919. if ([queryObj isBatchQuery]) {
  1920. GTLR_DEBUG_ASSERT([queryObj isKindOfClass:[GTLRBatchQuery class]],
  1921. @"GTLRBatchQuery required for batches (passed %@)",
  1922. [queryObj class]);
  1923. return [self executeBatchQuery:(GTLRBatchQuery *)queryObj
  1924. completionHandler:handler
  1925. ticket:nil];
  1926. }
  1927. GTLR_DEBUG_ASSERT([queryObj isKindOfClass:[GTLRQuery class]],
  1928. @"GTLRQuery required for single queries (passed %@)",
  1929. [queryObj class]);
  1930. // Copy the original query so our working query cannot be modified by the caller,
  1931. // and release the callback blocks from the supplied query object.
  1932. GTLRQuery *query = [(GTLRQuery *)queryObj copy];
  1933. GTLR_DEBUG_ASSERT(!query.queryInvalid, @"Query has already been executed: %@", query);
  1934. [queryObj invalidateQuery];
  1935. // For individual queries, we rely on the fetcher's log formatting so pretty-printing
  1936. // is not needed. Developers may override this in the query's additionalURLQueryParameters.
  1937. NSArray *prettyPrintNames = self.prettyPrintQueryParameterNames;
  1938. NSString *firstPrettyPrintName = prettyPrintNames.firstObject;
  1939. if (firstPrettyPrintName && (query.downloadAsDataObjectType.length == 0)
  1940. && ![query isKindOfClass:[GTLRResourceURLQuery class]]) {
  1941. NSDictionary *queryParams = query.additionalURLQueryParameters;
  1942. BOOL foundOne = NO;
  1943. for (NSString *name in prettyPrintNames) {
  1944. if ([queryParams objectForKey:name] != nil) {
  1945. foundOne = YES;
  1946. break;
  1947. }
  1948. }
  1949. if (!foundOne) {
  1950. NSMutableDictionary *worker =
  1951. [NSMutableDictionary dictionaryWithDictionary:queryParams];
  1952. [worker setObject:@"false" forKey:firstPrettyPrintName];
  1953. query.additionalURLQueryParameters = worker;
  1954. }
  1955. }
  1956. BOOL mayAuthorize = !query.shouldSkipAuthorization;
  1957. NSURL *url = [self URLFromQueryObject:query
  1958. usePartialPaths:NO
  1959. includeServiceURLQueryParams:YES];
  1960. return [self fetchObjectWithURL:url
  1961. objectClass:query.expectedObjectClass
  1962. bodyObject:query.bodyObject
  1963. ETag:nil
  1964. httpMethod:query.httpMethod
  1965. mayAuthorize:mayAuthorize
  1966. completionHandler:handler
  1967. executingQuery:query
  1968. ticket:nil];
  1969. }
  1970. - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)resourceURL
  1971. objectClass:(nullable Class)objectClass
  1972. executionParameters:(nullable GTLRServiceExecutionParameters *)executionParameters
  1973. completionHandler:(nullable GTLRServiceCompletionHandler)handler {
  1974. GTLRResourceURLQuery *query = [GTLRResourceURLQuery queryWithResourceURL:resourceURL
  1975. objectClass:objectClass];
  1976. query.executionParameters = executionParameters;
  1977. return [self executeQuery:query
  1978. completionHandler:handler];
  1979. }
  1980. #pragma mark -
  1981. - (NSString *)userAgent {
  1982. return _userAgent;
  1983. }
  1984. - (void)setExactUserAgent:(NSString *)userAgent {
  1985. _userAgent = [userAgent copy];
  1986. }
  1987. - (void)setUserAgent:(NSString *)userAgent {
  1988. // remove whitespace and unfriendly characters
  1989. NSString *str = GTMFetcherCleanedUserAgentString(userAgent);
  1990. [self setExactUserAgent:str];
  1991. }
  1992. - (void)overrideRequestUserAgent:(nullable NSString *)requestUserAgent {
  1993. _overrideUserAgent = [requestUserAgent copy];
  1994. }
  1995. #pragma mark -
  1996. + (NSDictionary<NSString *, Class> *)kindStringToClassMap {
  1997. // Generated services will provide custom ones.
  1998. return [NSDictionary dictionary];
  1999. }
  2000. #pragma mark -
  2001. // The service properties becomes the initial value for each future ticket's
  2002. // properties
  2003. - (void)setServiceProperties:(NSDictionary *)dict {
  2004. _serviceProperties = [dict copy];
  2005. }
  2006. - (NSDictionary *)serviceProperties {
  2007. // be sure the returned pointer has the life of the autorelease pool,
  2008. // in case self is released immediately
  2009. __autoreleasing id props = _serviceProperties;
  2010. return props;
  2011. }
  2012. #pragma clang diagnostic push
  2013. #pragma clang diagnostic ignored "-Wdeprecated"
  2014. - (void)setAuthorizer:(id <GTMFetcherAuthorizationProtocol>)authorizer {
  2015. self.fetcherService.authorizer = authorizer;
  2016. }
  2017. - (id <GTMFetcherAuthorizationProtocol>)authorizer {
  2018. return self.fetcherService.authorizer;
  2019. }
  2020. #pragma clang diagnostic pop
  2021. + (NSUInteger)defaultServiceUploadChunkSize {
  2022. // Subclasses may override this method.
  2023. // The upload server prefers multiples of 256K.
  2024. const NSUInteger kMegabyte = 4 * 256 * 1024;
  2025. #if TARGET_OS_IPHONE
  2026. // For iOS, we're balancing a large upload size with limiting the memory
  2027. // used for the upload data buffer.
  2028. return 4 * kMegabyte;
  2029. #else
  2030. // A large upload chunk size minimizes http overhead and server effort.
  2031. return 25 * kMegabyte;
  2032. #endif
  2033. }
  2034. - (NSUInteger)serviceUploadChunkSize {
  2035. if (_uploadChunkSize > 0) {
  2036. return _uploadChunkSize;
  2037. }
  2038. return [[self class] defaultServiceUploadChunkSize];
  2039. }
  2040. - (void)setServiceUploadChunkSize:(NSUInteger)val {
  2041. _uploadChunkSize = val;
  2042. }
  2043. - (void)setSurrogates:(NSDictionary <Class, Class>*)surrogates {
  2044. NSDictionary *kindMap = [[self class] kindStringToClassMap];
  2045. self.objectClassResolver = [GTLRObjectClassResolver resolverWithKindMap:kindMap
  2046. surrogates:surrogates];
  2047. }
  2048. #pragma mark - Internal helper
  2049. // If there are already query parameters on urlString, the new ones are simply
  2050. // appended after them.
  2051. + (NSURL *)URLWithString:(NSString *)urlString
  2052. queryParameters:(NSDictionary *)queryParameters {
  2053. if (urlString.length == 0) return nil;
  2054. NSString *fullURLString;
  2055. if (queryParameters.count > 0) {
  2056. // Use GTLRURITemplate by building up a template and then feeding in the
  2057. // values. The template is query expansion ('?'), and any key that is
  2058. // an array or dictionary gets tagged to explode them ('+').
  2059. NSArray *sortedQueryParamKeys =
  2060. [queryParameters.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  2061. NSMutableString *template = [@"{" mutableCopy];
  2062. char joiner = '?';
  2063. for (NSString *key in sortedQueryParamKeys) {
  2064. [template appendFormat:@"%c%@", joiner, key];
  2065. id value = [queryParameters objectForKey:key];
  2066. if ([value isKindOfClass:[NSArray class]] ||
  2067. [value isKindOfClass:[NSDictionary class]]) {
  2068. [template appendString:@"+"];
  2069. }
  2070. joiner = ',';
  2071. }
  2072. [template appendString:@"}"];
  2073. NSString *urlArgs =
  2074. [GTLRURITemplate expandTemplate:template
  2075. values:queryParameters];
  2076. urlArgs = [urlArgs substringFromIndex:1]; // Drop the '?' and use the joiner.
  2077. BOOL missingQMark = ([urlString rangeOfString:@"?"].location == NSNotFound);
  2078. joiner = missingQMark ? '?' : '&';
  2079. fullURLString =
  2080. [NSString stringWithFormat:@"%@%c%@", urlString, joiner, urlArgs];
  2081. } else {
  2082. fullURLString = urlString;
  2083. }
  2084. NSURL *result = [NSURL URLWithString:fullURLString];
  2085. return result;
  2086. }
  2087. @end
  2088. @implementation GTLRService (TestingSupport)
  2089. + (instancetype)mockServiceWithFakedObject:(id)objectOrNil
  2090. fakedError:(NSError *)errorOrNil {
  2091. GTLRService *service = [[GTLRService alloc] init];
  2092. service.rootURLString = @"https://example.invalid/";
  2093. service.testBlock = ^(GTLRServiceTicket *ticket, GTLRServiceTestResponse testResponse) {
  2094. testResponse(objectOrNil, errorOrNil);
  2095. };
  2096. return service;
  2097. }
  2098. - (BOOL)waitForTicket:(GTLRServiceTicket *)ticket
  2099. timeout:(NSTimeInterval)timeoutInSeconds {
  2100. // Loop until the fetch completes or is cancelled, or until the timeout has expired.
  2101. NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  2102. BOOL hasTimedOut = NO;
  2103. while (1) {
  2104. int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms
  2105. BOOL areCallbacksPending =
  2106. (dispatch_group_wait(ticket.callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta)) != 0);
  2107. if (!areCallbacksPending && (ticket.hasCalledCallback || ticket.cancelled)) break;
  2108. hasTimedOut = (giveUpDate.timeIntervalSinceNow <= 0);
  2109. if (hasTimedOut) {
  2110. if (areCallbacksPending) {
  2111. // A timeout while waiting for the dispatch group to finish is seriously unexpected.
  2112. GTLR_DEBUG_LOG(@"%s timed out while waiting for the dispatch group", __PRETTY_FUNCTION__);
  2113. } else {
  2114. GTLR_DEBUG_LOG(@"%s timed out without callbacks pending", __PRETTY_FUNCTION__);
  2115. }
  2116. break;
  2117. }
  2118. // Run the current run loop 1/1000 of a second to give the networking
  2119. // code a chance to work.
  2120. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
  2121. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  2122. }
  2123. return !hasTimedOut;
  2124. }
  2125. @end
  2126. @implementation GTLRServiceTicket {
  2127. GTLRService *_service;
  2128. NSDictionary *_ticketProperties;
  2129. GTLRServiceUploadProgressBlock _uploadProgressBlock;
  2130. BOOL _needsStopNotification;
  2131. }
  2132. @synthesize APIKey = _apiKey,
  2133. APIKeyRestrictionBundleID = _apiKeyRestrictionBundleID,
  2134. allowInsecureQueries = _allowInsecureQueries,
  2135. authorizer = _authorizer,
  2136. cancelled = _cancelled,
  2137. callbackGroup = _callbackGroup,
  2138. callbackQueue = _callbackQueue,
  2139. creationDate = _creationDate,
  2140. executingQuery = _executingQuery,
  2141. fetchedObject = _fetchedObject,
  2142. fetchError = _fetchError,
  2143. fetchRequest = _fetchRequest,
  2144. fetcherService = _fetcherService,
  2145. hasCalledCallback = _hasCalledCallback,
  2146. maxRetryInterval = _maxRetryInterval,
  2147. objectFetcher = _objectFetcher,
  2148. originalQuery = _originalQuery,
  2149. pagesFetchedCounter = _pagesFetchedCounter,
  2150. postedObject = _postedObject,
  2151. retryBlock = _retryBlock,
  2152. retryEnabled = _retryEnabled,
  2153. shouldFetchNextPages = _shouldFetchNextPages,
  2154. objectClassResolver = _objectClassResolver,
  2155. testBlock = _testBlock;
  2156. #if GTM_BACKGROUND_TASK_FETCHING
  2157. @synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier;
  2158. #endif
  2159. #if DEBUG
  2160. - (instancetype)init {
  2161. [self doesNotRecognizeSelector:_cmd];
  2162. self = nil;
  2163. return self;
  2164. }
  2165. #endif
  2166. #if GTM_BACKGROUND_TASK_FETCHING && DEBUG
  2167. - (void)dealloc {
  2168. GTLR_DEBUG_ASSERT(_backgroundTaskIdentifier == UIBackgroundTaskInvalid,
  2169. @"Background task not ended");
  2170. }
  2171. #endif // GTM_BACKGROUND_TASK_FETCHING && DEBUG
  2172. - (instancetype)initWithService:(GTLRService *)service
  2173. executionParameters:(GTLRServiceExecutionParameters *)params {
  2174. self = [super init];
  2175. if (self) {
  2176. // ivars set at init time and never changed are exposed as atomic readonly properties.
  2177. _service = service;
  2178. _fetcherService = service.fetcherService;
  2179. _authorizer = service.authorizer;
  2180. _ticketProperties = MergeDictionaries(service.serviceProperties, params.ticketProperties);
  2181. _objectClassResolver = params.objectClassResolver ?: service.objectClassResolver;
  2182. _retryEnabled = ((params.retryEnabled != nil) ? params.retryEnabled.boolValue : service.retryEnabled);
  2183. _maxRetryInterval = ((params.maxRetryInterval != nil) ?
  2184. params.maxRetryInterval.doubleValue : service.maxRetryInterval);
  2185. _shouldFetchNextPages = ((params.shouldFetchNextPages != nil)?
  2186. params.shouldFetchNextPages.boolValue : service.shouldFetchNextPages);
  2187. GTLRServiceUploadProgressBlock uploadProgressBlock =
  2188. params.uploadProgressBlock ?: service.uploadProgressBlock;
  2189. _uploadProgressBlock = [uploadProgressBlock copy];
  2190. GTLRServiceRetryBlock retryBlock = params.retryBlock ?: service.retryBlock;
  2191. _retryBlock = [retryBlock copy];
  2192. if (_retryBlock) {
  2193. _retryEnabled = YES;
  2194. }
  2195. _testBlock = params.testBlock ?: service.testBlock;
  2196. _callbackQueue = ((_Nonnull dispatch_queue_t)params.callbackQueue) ?: service.callbackQueue;
  2197. _callbackGroup = dispatch_group_create();
  2198. _apiKey = [service.APIKey copy];
  2199. _apiKeyRestrictionBundleID = [service.APIKeyRestrictionBundleID copy];
  2200. _allowInsecureQueries = service.allowInsecureQueries;
  2201. #if GTM_BACKGROUND_TASK_FETCHING
  2202. _backgroundTaskIdentifier = UIBackgroundTaskInvalid;
  2203. #endif
  2204. _creationDate = [NSDate date];
  2205. }
  2206. return self;
  2207. }
  2208. - (NSString *)description {
  2209. NSString *devKeyInfo = @"";
  2210. if (_apiKey != nil) {
  2211. devKeyInfo = [NSString stringWithFormat:@" devKey:%@", _apiKey];
  2212. }
  2213. NSString *keyRestrictionInfo = @"";
  2214. if (_apiKeyRestrictionBundleID != nil) {
  2215. keyRestrictionInfo = [NSString stringWithFormat:@" restriction:%@",
  2216. _apiKeyRestrictionBundleID];
  2217. }
  2218. NSString *authorizerInfo = @"";
  2219. #pragma clang diagnostic push
  2220. #pragma clang diagnostic ignored "-Wdeprecated"
  2221. id <GTMFetcherAuthorizationProtocol> authorizer = self.objectFetcher.authorizer;
  2222. #pragma clang diagnostic pop
  2223. if (authorizer != nil) {
  2224. authorizerInfo = [NSString stringWithFormat:@" authorizer:%@", authorizer];
  2225. }
  2226. return [NSString stringWithFormat:@"%@ %p: {service:%@%@%@%@ fetcher:%@ }",
  2227. [self class], self,
  2228. _service, devKeyInfo, keyRestrictionInfo, authorizerInfo, _objectFetcher];
  2229. }
  2230. - (void)postNotificationOnMainThreadWithName:(NSString *)name
  2231. object:(id)object
  2232. userInfo:(NSDictionary *)userInfo {
  2233. // We always post these async to ensure they remain in order.
  2234. dispatch_group_async(self.callbackGroup, dispatch_get_main_queue(), ^{
  2235. [[NSNotificationCenter defaultCenter] postNotificationName:name
  2236. object:object
  2237. userInfo:userInfo];
  2238. });
  2239. }
  2240. - (void)pauseUpload {
  2241. GTMSessionFetcher *fetcher = self.objectFetcher;
  2242. BOOL canPause = [fetcher respondsToSelector:@selector(pauseFetching)];
  2243. GTLR_DEBUG_ASSERT(canPause, @"tickets can be paused only for chunked resumable uploads");
  2244. if (canPause) {
  2245. [(GTMSessionUploadFetcher *)fetcher pauseFetching];
  2246. }
  2247. }
  2248. - (void)resumeUpload {
  2249. GTMSessionFetcher *fetcher = self.objectFetcher;
  2250. BOOL canResume = [fetcher respondsToSelector:@selector(resumeFetching)];
  2251. GTLR_DEBUG_ASSERT(canResume, @"tickets can be resumed only for chunked resumable uploads");
  2252. if (canResume) {
  2253. [(GTMSessionUploadFetcher *)fetcher resumeFetching];
  2254. }
  2255. }
  2256. - (BOOL)isUploadPaused {
  2257. BOOL isPausable = [_objectFetcher respondsToSelector:@selector(isPaused)];
  2258. GTLR_DEBUG_ASSERT(isPausable, @"tickets can be paused only for chunked resumable uploads");
  2259. if (isPausable) {
  2260. return [(GTMSessionUploadFetcher *)_objectFetcher isPaused];
  2261. }
  2262. return NO;
  2263. }
  2264. - (BOOL)isCancelled {
  2265. @synchronized(self) {
  2266. return _cancelled;
  2267. }
  2268. }
  2269. - (void)cancelTicket {
  2270. @synchronized(self) {
  2271. _cancelled = YES;
  2272. }
  2273. [_objectFetcher stopFetching];
  2274. self.objectFetcher = nil;
  2275. self.fetchRequest = nil;
  2276. _ticketProperties = nil;
  2277. [self releaseTicketCallbacks];
  2278. [self endBackgroundTask];
  2279. [self.executingQuery invalidateQuery];
  2280. id<GTLRQueryProtocol> originalQuery = self.originalQuery;
  2281. self.executingQuery = originalQuery;
  2282. [originalQuery invalidateQuery];
  2283. _service = nil;
  2284. _fetcherService = nil;
  2285. _authorizer = nil;
  2286. _testBlock = nil;
  2287. }
  2288. #if GTM_BACKGROUND_TASK_FETCHING
  2289. // When the fetcher's substitute UIApplication object is present, GTLRService
  2290. // will use that instead of UIApplication. This is just to reduce duplicating
  2291. // that plumbing for testing.
  2292. + (nullable id<GTMUIApplicationProtocol>)fetcherUIApplication {
  2293. id<GTMUIApplicationProtocol> app = [GTMSessionFetcher substituteUIApplication];
  2294. if (app) return app;
  2295. static Class applicationClass = nil;
  2296. static dispatch_once_t onceToken;
  2297. dispatch_once(&onceToken, ^{
  2298. BOOL isAppExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
  2299. if (!isAppExtension) {
  2300. Class cls = NSClassFromString(@"UIApplication");
  2301. if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) {
  2302. applicationClass = cls;
  2303. }
  2304. }
  2305. });
  2306. if (applicationClass) {
  2307. app = (id<GTMUIApplicationProtocol>)[applicationClass sharedApplication];
  2308. }
  2309. return app;
  2310. }
  2311. #endif // GTM_BACKGROUND_TASK_FETCHING
  2312. - (void)startBackgroundTask {
  2313. #if GTM_BACKGROUND_TASK_FETCHING
  2314. GTLR_DEBUG_ASSERT(self.backgroundTaskIdentifier == UIBackgroundTaskInvalid,
  2315. @"Redundant GTLRService background task: %lu",
  2316. (unsigned long)self.backgroundTaskIdentifier);
  2317. NSString *taskName = [[self.executingQuery class] description];
  2318. id<GTMUIApplicationProtocol> app = [[self class] fetcherUIApplication];
  2319. // We'll use a locally-scoped task ID variable so the expiration block is guaranteed
  2320. // to refer to this task rather than to whatever task the property has.
  2321. // Since a request can be started from any thread, we also have to ensure the
  2322. // variable for accessing it is safe across the initial thread and the handler
  2323. // (incase it gets failed immediately from the app already heading into the
  2324. // background).
  2325. __block UIBackgroundTaskIdentifier guardedTaskID = UIBackgroundTaskInvalid;
  2326. UIBackgroundTaskIdentifier returnedTaskID =
  2327. [app beginBackgroundTaskWithName:taskName
  2328. expirationHandler:^{
  2329. // Background task expiration callback. This block is always invoked by
  2330. // UIApplication on the main thread.
  2331. UIBackgroundTaskIdentifier localTaskID;
  2332. @synchronized(self) {
  2333. localTaskID = guardedTaskID;
  2334. }
  2335. if (localTaskID != UIBackgroundTaskInvalid) {
  2336. @synchronized(self) {
  2337. if (localTaskID == self.backgroundTaskIdentifier) {
  2338. self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
  2339. }
  2340. }
  2341. // This explicitly ends the captured localTaskID rather than the backgroundTaskIdentifier
  2342. // property to ensure expiration is handled even if the property has changed.
  2343. [app endBackgroundTask:localTaskID];
  2344. }
  2345. }];
  2346. @synchronized(self) {
  2347. guardedTaskID = returnedTaskID;
  2348. self.backgroundTaskIdentifier = returnedTaskID;
  2349. }
  2350. #endif // GTM_BACKGROUND_TASK_FETCHING
  2351. }
  2352. - (void)endBackgroundTask {
  2353. #if GTM_BACKGROUND_TASK_FETCHING
  2354. // Whenever the connection stops or a next page is about to be fetched,
  2355. // tell UIApplication we're done.
  2356. UIBackgroundTaskIdentifier bgTaskID;
  2357. @synchronized(self) {
  2358. bgTaskID = self.backgroundTaskIdentifier;
  2359. self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
  2360. }
  2361. if (bgTaskID != UIBackgroundTaskInvalid) {
  2362. [[[self class] fetcherUIApplication] endBackgroundTask:bgTaskID];
  2363. }
  2364. #endif // GTM_BACKGROUND_TASK_FETCHING
  2365. }
  2366. - (void)releaseTicketCallbacks {
  2367. self.uploadProgressBlock = nil;
  2368. self.retryBlock = nil;
  2369. }
  2370. - (void)notifyStarting:(BOOL)isStarting {
  2371. GTLR_DEBUG_ASSERT(!GTLR_AreBoolsEqual(isStarting, _needsStopNotification),
  2372. @"Notification mismatch (isStarting=%d)", isStarting);
  2373. if (GTLR_AreBoolsEqual(isStarting, _needsStopNotification)) return;
  2374. NSString *name;
  2375. if (isStarting) {
  2376. name = kGTLRServiceTicketStartedNotification;
  2377. _needsStopNotification = YES;
  2378. } else {
  2379. name = kGTLRServiceTicketStoppedNotification;
  2380. _needsStopNotification = NO;
  2381. }
  2382. [self postNotificationOnMainThreadWithName:name
  2383. object:self
  2384. userInfo:nil];
  2385. }
  2386. - (id)service {
  2387. return _service;
  2388. }
  2389. - (void)setObjectFetcher:(GTMSessionFetcher *)fetcher {
  2390. @synchronized(self) {
  2391. _objectFetcher = fetcher;
  2392. }
  2393. [self updateObjectFetcherProgressCallbacks];
  2394. }
  2395. - (GTMSessionFetcher *)objectFetcher {
  2396. @synchronized(self) {
  2397. return _objectFetcher;
  2398. }
  2399. }
  2400. - (NSDictionary *)ticketProperties {
  2401. // be sure the returned pointer has the life of the autorelease pool,
  2402. // in case self is released immediately
  2403. __autoreleasing id props = _ticketProperties;
  2404. return props;
  2405. }
  2406. - (GTLRServiceUploadProgressBlock)uploadProgressBlock {
  2407. return _uploadProgressBlock;
  2408. }
  2409. - (void)setUploadProgressBlock:(GTLRServiceUploadProgressBlock)block {
  2410. if (_uploadProgressBlock != block) {
  2411. _uploadProgressBlock = [block copy];
  2412. [self updateObjectFetcherProgressCallbacks];
  2413. }
  2414. }
  2415. - (void)updateObjectFetcherProgressCallbacks {
  2416. // Internal method. Do not override.
  2417. GTMSessionFetcher *fetcher = [self objectFetcher];
  2418. if (_uploadProgressBlock) {
  2419. // Use a local block variable to avoid a spurious retain cycle warning.
  2420. GTMSessionFetcherSendProgressBlock fetcherSentDataBlock = ^(int64_t bytesSent,
  2421. int64_t totalBytesSent,
  2422. int64_t totalBytesExpectedToSend) {
  2423. [self->_service invokeProgressCallbackForTicket:self
  2424. deliveredBytes:(unsigned long long)totalBytesSent
  2425. totalBytes:(unsigned long long)totalBytesExpectedToSend];
  2426. };
  2427. fetcher.sendProgressBlock = fetcherSentDataBlock;
  2428. } else {
  2429. fetcher.sendProgressBlock = nil;
  2430. }
  2431. }
  2432. - (NSInteger)statusCode {
  2433. return [_objectFetcher statusCode];
  2434. }
  2435. - (GTLRQuery *)queryForRequestID:(NSString *)requestID {
  2436. id<GTLRQueryProtocol> queryObj = self.executingQuery;
  2437. if ([queryObj isBatchQuery]) {
  2438. GTLRBatchQuery *batch = (GTLRBatchQuery *)queryObj;
  2439. GTLRQuery *result = [batch queryForRequestID:requestID];
  2440. return result;
  2441. } else {
  2442. GTLR_DEBUG_ASSERT(0, @"just use ticket.executingQuery");
  2443. return nil;
  2444. }
  2445. }
  2446. @end
  2447. @implementation GTLRServiceExecutionParameters
  2448. @synthesize maxRetryInterval = _maxRetryInterval,
  2449. retryEnabled = _retryEnabled,
  2450. retryBlock = _retryBlock,
  2451. shouldFetchNextPages = _shouldFetchNextPages,
  2452. objectClassResolver = _objectClassResolver,
  2453. testBlock = _testBlock,
  2454. ticketProperties = _ticketProperties,
  2455. uploadProgressBlock = _uploadProgressBlock,
  2456. callbackQueue = _callbackQueue;
  2457. - (id)copyWithZone:(NSZone *)zone {
  2458. GTLRServiceExecutionParameters *newObject = [[self class] allocWithZone:zone];
  2459. newObject.maxRetryInterval = self.maxRetryInterval;
  2460. newObject.retryEnabled = self.retryEnabled;
  2461. newObject.retryBlock = self.retryBlock;
  2462. newObject.shouldFetchNextPages = self.shouldFetchNextPages;
  2463. newObject.objectClassResolver = self.objectClassResolver;
  2464. newObject.testBlock = self.testBlock;
  2465. newObject.ticketProperties = self.ticketProperties;
  2466. newObject.uploadProgressBlock = self.uploadProgressBlock;
  2467. newObject.callbackQueue = self.callbackQueue;
  2468. return newObject;
  2469. }
  2470. - (BOOL)hasParameters {
  2471. if (self.maxRetryInterval != nil) return YES;
  2472. if (self.retryEnabled != nil) return YES;
  2473. if (self.retryBlock) return YES;
  2474. if (self.shouldFetchNextPages != nil) return YES;
  2475. if (self.objectClassResolver) return YES;
  2476. if (self.testBlock) return YES;
  2477. if (self.ticketProperties) return YES;
  2478. if (self.uploadProgressBlock) return YES;
  2479. if (self.callbackQueue) return YES;
  2480. return NO;
  2481. }
  2482. @end
  2483. @implementation GTLRResourceURLQuery
  2484. @synthesize resourceURL = _resourceURL;
  2485. + (instancetype)queryWithResourceURL:(NSURL *)resourceURL
  2486. objectClass:(Class)objectClass {
  2487. GTLRResourceURLQuery *query = [[self alloc] initWithPathURITemplate:@"_usingGTLRResourceURLQuery_"
  2488. HTTPMethod:nil
  2489. pathParameterNames:nil];
  2490. query.expectedObjectClass = objectClass;
  2491. query.resourceURL = resourceURL;
  2492. return query;
  2493. }
  2494. - (instancetype)copyWithZone:(NSZone *)zone {
  2495. GTLRResourceURLQuery *result = [super copyWithZone:zone];
  2496. result->_resourceURL = self->_resourceURL;
  2497. return result;
  2498. }
  2499. // TODO: description
  2500. @end
  2501. @implementation GTLRObjectCollectionImpl
  2502. @dynamic nextPageToken;
  2503. @end