You've already forked FinalYearProject-MyMind
mirror of
https://github.com/MrLyallCSIT/FinalYearProject-MyMind.git
synced 2026-01-18 07:09:41 +00:00
App Completed
Basic Prototype App Completed. Proposal, Literature Review and Final Report to Follow.
This commit is contained in:
202
My Mind/Pods/GTMSessionFetcher/LICENSE
generated
Normal file
202
My Mind/Pods/GTMSessionFetcher/LICENSE
generated
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
23
My Mind/Pods/GTMSessionFetcher/README.md
generated
Normal file
23
My Mind/Pods/GTMSessionFetcher/README.md
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
# Google Toolbox for Mac - Session Fetcher #
|
||||
|
||||
**Project site** <https://github.com/google/gtm-session-fetcher><br>
|
||||
**Discussion group** <http://groups.google.com/group/google-toolbox-for-mac>
|
||||
|
||||
[](https://travis-ci.org/google/gtm-session-fetcher)
|
||||
|
||||
`GTMSessionFetcher` makes it easy for Cocoa applications to perform http
|
||||
operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its
|
||||
behavior is asynchronous and uses operating-system settings on iOS and Mac OS X.
|
||||
|
||||
Features include:
|
||||
- Simple to build; only one source/header file pair is required
|
||||
- Simple to use: takes just two lines of code to fetch a request
|
||||
- Supports upload and download sessions
|
||||
- Flexible cookie storage
|
||||
- Automatic retry on errors, with exponential backoff
|
||||
- Support for generating multipart MIME upload streams
|
||||
- Easy, convenient logging of http requests and responses
|
||||
- Supports plug-in authentication such as with GTMAppAuth
|
||||
- Easily testable; self-mocking
|
||||
- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class
|
||||
- Fully independent of other projects
|
||||
1308
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h
generated
Normal file
1308
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h
generated
Normal file
File diff suppressed because it is too large
Load Diff
4549
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m
generated
Normal file
4549
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m
generated
Normal file
File diff suppressed because it is too large
Load Diff
107
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h
generated
Normal file
107
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
/* Copyright 2014 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "GTMSessionFetcher.h"
|
||||
|
||||
// GTM HTTP Logging
|
||||
//
|
||||
// All traffic using GTMSessionFetcher can be easily logged. Call
|
||||
//
|
||||
// [GTMSessionFetcher setLoggingEnabled:YES];
|
||||
//
|
||||
// to begin generating log files.
|
||||
//
|
||||
// Log files are put into a folder on the desktop called "GTMHTTPDebugLogs"
|
||||
// unless another directory is specified with +setLoggingDirectory.
|
||||
//
|
||||
// In the iPhone simulator, the default logs location is the user's home
|
||||
// directory in ~/Library/Application Support. On the iPhone device, the
|
||||
// default logs location is the application's documents directory on the device.
|
||||
//
|
||||
// Tip: use the Finder's "Sort By Date" to find the most recent logs.
|
||||
//
|
||||
// Each run of an application gets a separate set of log files. An html
|
||||
// file is generated to simplify browsing the run's http transactions.
|
||||
// The html file includes javascript links for inline viewing of uploaded
|
||||
// and downloaded data.
|
||||
//
|
||||
// A symlink is created in the logs folder to simplify finding the html file
|
||||
// for the latest run of the application; the symlink is called
|
||||
//
|
||||
// AppName_http_log_newest.html
|
||||
//
|
||||
// For better viewing of XML logs, use Camino or Firefox rather than Safari.
|
||||
//
|
||||
// Each fetcher may be given a comment to be inserted as a label in the logs,
|
||||
// such as
|
||||
// [fetcher setCommentWithFormat:@"retrieve item %@", itemName];
|
||||
//
|
||||
// Projects may define STRIP_GTM_FETCH_LOGGING to remove logging code.
|
||||
|
||||
#if !STRIP_GTM_FETCH_LOGGING
|
||||
|
||||
@interface GTMSessionFetcher (GTMSessionFetcherLogging)
|
||||
|
||||
// Note: the default logs directory is ~/Desktop/GTMHTTPDebugLogs; it will be
|
||||
// created as needed. If a custom directory is set, the directory should
|
||||
// already exist.
|
||||
+ (void)setLoggingDirectory:(NSString *)path;
|
||||
+ (NSString *)loggingDirectory;
|
||||
|
||||
// client apps can turn logging on and off
|
||||
+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled;
|
||||
+ (BOOL)isLoggingEnabled;
|
||||
|
||||
// client apps can turn off logging to a file if they want to only check
|
||||
// the fetcher's log property
|
||||
+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled;
|
||||
+ (BOOL)isLoggingToFileEnabled;
|
||||
|
||||
// client apps can optionally specify process name and date string used in
|
||||
// log file names
|
||||
+ (void)setLoggingProcessName:(NSString *)processName;
|
||||
+ (NSString *)loggingProcessName;
|
||||
|
||||
+ (void)setLoggingDateStamp:(NSString *)dateStamp;
|
||||
+ (NSString *)loggingDateStamp;
|
||||
|
||||
// client apps can specify the directory for the log for this specific run,
|
||||
// typically to match the directory used by another fetcher class, like:
|
||||
//
|
||||
// [GTMSessionFetcher setLogDirectoryForCurrentRun:[GTMHTTPFetcher logDirectoryForCurrentRun]];
|
||||
//
|
||||
// Setting this overrides the logging directory, process name, and date stamp when writing
|
||||
// the log file.
|
||||
+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun;
|
||||
+ (NSString *)logDirectoryForCurrentRun;
|
||||
|
||||
// Prunes old log directories that have not been modified since the provided date.
|
||||
// This will not delete the current run's log directory.
|
||||
+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)date;
|
||||
|
||||
// internal; called by fetcher
|
||||
- (void)logFetchWithError:(NSError *)error;
|
||||
- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream;
|
||||
- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider:
|
||||
(GTMSessionFetcherBodyStreamProvider)streamProvider;
|
||||
|
||||
// internal; accessors useful for viewing logs
|
||||
+ (NSString *)processNameLogPrefix;
|
||||
+ (NSString *)symlinkNameSuffix;
|
||||
+ (NSString *)htmlFileName;
|
||||
|
||||
@end
|
||||
|
||||
#endif // !STRIP_GTM_FETCH_LOGGING
|
||||
976
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m
generated
Normal file
976
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m
generated
Normal file
@@ -0,0 +1,976 @@
|
||||
/* Copyright 2014 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#if !defined(__has_feature) || !__has_feature(objc_arc)
|
||||
#error "This file requires ARC support."
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#import "GTMSessionFetcherLogging.h"
|
||||
|
||||
#ifndef STRIP_GTM_FETCH_LOGGING
|
||||
#error GTMSessionFetcher headers should have defaulted this if it wasn't already defined.
|
||||
#endif
|
||||
|
||||
#if !STRIP_GTM_FETCH_LOGGING
|
||||
|
||||
// Sensitive credential strings are replaced in logs with _snip_
|
||||
//
|
||||
// Apps that must see the contents of sensitive tokens can set this to 1
|
||||
#ifndef SKIP_GTM_FETCH_LOGGING_SNIPPING
|
||||
#define SKIP_GTM_FETCH_LOGGING_SNIPPING 0
|
||||
#endif
|
||||
|
||||
// If GTMReadMonitorInputStream is available, it can be used for
|
||||
// capturing uploaded streams of data
|
||||
//
|
||||
// We locally declare methods of GTMReadMonitorInputStream so we
|
||||
// do not need to import the header, as some projects may not have it available
|
||||
#if !GTMSESSION_BUILD_COMBINED_SOURCES
|
||||
@interface GTMReadMonitorInputStream : NSInputStream
|
||||
|
||||
+ (instancetype)inputStreamWithStream:(NSInputStream *)input;
|
||||
|
||||
@property (assign) id readDelegate;
|
||||
@property (assign) SEL readSelector;
|
||||
|
||||
@end
|
||||
#else
|
||||
@class GTMReadMonitorInputStream;
|
||||
#endif // !GTMSESSION_BUILD_COMBINED_SOURCES
|
||||
|
||||
@interface GTMSessionFetcher (GTMHTTPFetcherLoggingUtilities)
|
||||
|
||||
+ (NSString *)headersStringForDictionary:(NSDictionary *)dict;
|
||||
+ (NSString *)snipSubstringOfString:(NSString *)originalStr
|
||||
betweenStartString:(NSString *)startStr
|
||||
endString:(NSString *)endStr;
|
||||
- (void)inputStream:(GTMReadMonitorInputStream *)stream
|
||||
readIntoBuffer:(void *)buffer
|
||||
length:(int64_t)length;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GTMSessionFetcher (GTMSessionFetcherLogging)
|
||||
|
||||
// fetchers come and fetchers go, but statics are forever
|
||||
static BOOL gIsLoggingEnabled = NO;
|
||||
static BOOL gIsLoggingToFile = YES;
|
||||
static NSString *gLoggingDirectoryPath = nil;
|
||||
static NSString *gLogDirectoryForCurrentRun = nil;
|
||||
static NSString *gLoggingDateStamp = nil;
|
||||
static NSString *gLoggingProcessName = nil;
|
||||
|
||||
+ (void)setLoggingDirectory:(NSString *)path {
|
||||
gLoggingDirectoryPath = [path copy];
|
||||
}
|
||||
|
||||
+ (NSString *)loggingDirectory {
|
||||
if (!gLoggingDirectoryPath) {
|
||||
NSArray *paths = nil;
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
// default to a directory called GTMHTTPDebugLogs into a sandbox-safe
|
||||
// directory that a developer can find easily, the application home
|
||||
paths = @[ NSHomeDirectory() ];
|
||||
#elif TARGET_OS_IPHONE
|
||||
// Neither ~/Desktop nor ~/Home is writable on an actual iOS, watchOS, or tvOS device.
|
||||
// Put it in ~/Documents.
|
||||
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
#else
|
||||
// default to a directory called GTMHTTPDebugLogs in the desktop folder
|
||||
paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
|
||||
#endif
|
||||
|
||||
NSString *desktopPath = paths.firstObject;
|
||||
if (desktopPath) {
|
||||
NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs";
|
||||
NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName];
|
||||
|
||||
NSFileManager *fileMgr = [NSFileManager defaultManager];
|
||||
BOOL isDir;
|
||||
BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir];
|
||||
if (!doesFolderExist) {
|
||||
// make the directory
|
||||
doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:NULL];
|
||||
}
|
||||
if (doesFolderExist) {
|
||||
// it's there; store it in the global
|
||||
gLoggingDirectoryPath = [logsFolderPath copy];
|
||||
}
|
||||
}
|
||||
}
|
||||
return gLoggingDirectoryPath;
|
||||
}
|
||||
|
||||
+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun {
|
||||
// Set the path for this run's logs.
|
||||
gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy];
|
||||
}
|
||||
|
||||
+ (NSString *)logDirectoryForCurrentRun {
|
||||
// make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM
|
||||
if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun;
|
||||
|
||||
NSString *parentDir = [self loggingDirectory];
|
||||
NSString *logNamePrefix = [self processNameLogPrefix];
|
||||
NSString *dateStamp = [self loggingDateStamp];
|
||||
NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp];
|
||||
NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName];
|
||||
|
||||
if (gIsLoggingToFile) {
|
||||
NSFileManager *fileMgr = [NSFileManager defaultManager];
|
||||
// Be sure that the first time this app runs, it's not writing to a preexisting folder
|
||||
static BOOL gShouldReuseFolder = NO;
|
||||
if (!gShouldReuseFolder) {
|
||||
gShouldReuseFolder = YES;
|
||||
NSString *origLogDir = logDirectory;
|
||||
for (int ctr = 2; ctr < 20; ++ctr) {
|
||||
if (![fileMgr fileExistsAtPath:logDirectory]) break;
|
||||
|
||||
// append a digit
|
||||
logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr];
|
||||
}
|
||||
}
|
||||
if (![fileMgr createDirectoryAtPath:logDirectory
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:NULL]) return nil;
|
||||
}
|
||||
gLogDirectoryForCurrentRun = logDirectory;
|
||||
|
||||
return gLogDirectoryForCurrentRun;
|
||||
}
|
||||
|
||||
+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled {
|
||||
gIsLoggingEnabled = isLoggingEnabled;
|
||||
}
|
||||
|
||||
+ (BOOL)isLoggingEnabled {
|
||||
return gIsLoggingEnabled;
|
||||
}
|
||||
|
||||
+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled {
|
||||
gIsLoggingToFile = isLoggingToFileEnabled;
|
||||
}
|
||||
|
||||
+ (BOOL)isLoggingToFileEnabled {
|
||||
return gIsLoggingToFile;
|
||||
}
|
||||
|
||||
+ (void)setLoggingProcessName:(NSString *)processName {
|
||||
gLoggingProcessName = [processName copy];
|
||||
}
|
||||
|
||||
+ (NSString *)loggingProcessName {
|
||||
// get the process name (once per run) replacing spaces with underscores
|
||||
if (!gLoggingProcessName) {
|
||||
NSString *procName = [[NSProcessInfo processInfo] processName];
|
||||
gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"];
|
||||
}
|
||||
return gLoggingProcessName;
|
||||
}
|
||||
|
||||
+ (void)setLoggingDateStamp:(NSString *)dateStamp {
|
||||
gLoggingDateStamp = [dateStamp copy];
|
||||
}
|
||||
|
||||
+ (NSString *)loggingDateStamp {
|
||||
// We'll pick one date stamp per run, so a run that starts at a later second
|
||||
// will get a unique results html file
|
||||
if (!gLoggingDateStamp) {
|
||||
// produce a string like 08-21_01-41-23PM
|
||||
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||
[formatter setDateFormat:@"M-dd_hh-mm-ssa"];
|
||||
|
||||
gLoggingDateStamp = [formatter stringFromDate:[NSDate date]];
|
||||
}
|
||||
return gLoggingDateStamp;
|
||||
}
|
||||
|
||||
+ (NSString *)processNameLogPrefix {
|
||||
static NSString *gPrefix = nil;
|
||||
if (!gPrefix) {
|
||||
NSString *processName = [self loggingProcessName];
|
||||
gPrefix = [[NSString alloc] initWithFormat:@"%@_log_", processName];
|
||||
}
|
||||
return gPrefix;
|
||||
}
|
||||
|
||||
+ (NSString *)symlinkNameSuffix {
|
||||
return @"_log_newest.html";
|
||||
}
|
||||
|
||||
+ (NSString *)htmlFileName {
|
||||
return @"aperçu_http_log.html";
|
||||
}
|
||||
|
||||
+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)cutoffDate {
|
||||
NSFileManager *fileMgr = [NSFileManager defaultManager];
|
||||
NSURL *parentDir = [NSURL fileURLWithPath:[[self class] loggingDirectory]];
|
||||
NSURL *logDirectoryForCurrentRun =
|
||||
[NSURL fileURLWithPath:[[self class] logDirectoryForCurrentRun]];
|
||||
NSError *error;
|
||||
NSArray *contents = [fileMgr contentsOfDirectoryAtURL:parentDir
|
||||
includingPropertiesForKeys:@[ NSURLContentModificationDateKey ]
|
||||
options:0
|
||||
error:&error];
|
||||
for (NSURL *itemURL in contents) {
|
||||
if ([itemURL isEqual:logDirectoryForCurrentRun]) continue;
|
||||
|
||||
NSDate *modDate;
|
||||
if ([itemURL getResourceValue:&modDate
|
||||
forKey:NSURLContentModificationDateKey
|
||||
error:&error]) {
|
||||
if ([modDate compare:cutoffDate] == NSOrderedAscending) {
|
||||
if (![fileMgr removeItemAtURL:itemURL error:&error]) {
|
||||
NSLog(@"deleteLogDirectoriesOlderThanDate failed to delete %@: %@",
|
||||
itemURL.path, error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSLog(@"deleteLogDirectoriesOlderThanDate failed to get mod date of %@: %@",
|
||||
itemURL.path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formattedStringFromData returns a prettyprinted string for XML or JSON input,
|
||||
// and a plain string for other input data
|
||||
- (NSString *)formattedStringFromData:(NSData *)inputData
|
||||
contentType:(NSString *)contentType
|
||||
JSON:(NSDictionary **)outJSON {
|
||||
if (!inputData) return nil;
|
||||
|
||||
// if the content type is JSON and we have the parsing class available, use that
|
||||
if ([contentType hasPrefix:@"application/json"] && inputData.length > 5) {
|
||||
// convert from JSON string to NSObjects and back to a formatted string
|
||||
NSMutableDictionary *obj = [NSJSONSerialization JSONObjectWithData:inputData
|
||||
options:NSJSONReadingMutableContainers
|
||||
error:NULL];
|
||||
if (obj) {
|
||||
if (outJSON) *outJSON = obj;
|
||||
if ([obj isKindOfClass:[NSMutableDictionary class]]) {
|
||||
// for security and privacy, omit OAuth 2 response access and refresh tokens
|
||||
if ([obj valueForKey:@"refresh_token"] != nil) {
|
||||
[obj setObject:@"_snip_" forKey:@"refresh_token"];
|
||||
}
|
||||
if ([obj valueForKey:@"access_token"] != nil) {
|
||||
[obj setObject:@"_snip_" forKey:@"access_token"];
|
||||
}
|
||||
}
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:obj
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:NULL];
|
||||
if (data) {
|
||||
NSString *jsonStr = [[NSString alloc] initWithData:data
|
||||
encoding:NSUTF8StringEncoding];
|
||||
return jsonStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !TARGET_OS_IPHONE && !GTM_SKIP_LOG_XMLFORMAT
|
||||
// verify that this data starts with the bytes indicating XML
|
||||
|
||||
NSString *const kXMLLintPath = @"/usr/bin/xmllint";
|
||||
static BOOL gHasCheckedAvailability = NO;
|
||||
static BOOL gIsXMLLintAvailable = NO;
|
||||
|
||||
if (!gHasCheckedAvailability) {
|
||||
gIsXMLLintAvailable = [[NSFileManager defaultManager] fileExistsAtPath:kXMLLintPath];
|
||||
gHasCheckedAvailability = YES;
|
||||
}
|
||||
if (gIsXMLLintAvailable
|
||||
&& inputData.length > 5
|
||||
&& strncmp(inputData.bytes, "<?xml", 5) == 0) {
|
||||
|
||||
// call xmllint to format the data
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
[task setLaunchPath:kXMLLintPath];
|
||||
|
||||
// use the dash argument to specify stdin as the source file
|
||||
[task setArguments:@[ @"--format", @"-" ]];
|
||||
[task setEnvironment:@{}];
|
||||
|
||||
NSPipe *inputPipe = [NSPipe pipe];
|
||||
NSPipe *outputPipe = [NSPipe pipe];
|
||||
[task setStandardInput:inputPipe];
|
||||
[task setStandardOutput:outputPipe];
|
||||
|
||||
[task launch];
|
||||
|
||||
[[inputPipe fileHandleForWriting] writeData:inputData];
|
||||
[[inputPipe fileHandleForWriting] closeFile];
|
||||
|
||||
// drain the stdout before waiting for the task to exit
|
||||
NSData *formattedData = [[outputPipe fileHandleForReading] readDataToEndOfFile];
|
||||
|
||||
[task waitUntilExit];
|
||||
|
||||
int status = [task terminationStatus];
|
||||
if (status == 0 && formattedData.length > 0) {
|
||||
// success
|
||||
inputData = formattedData;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// we can't call external tasks on the iPhone; leave the XML unformatted
|
||||
#endif
|
||||
|
||||
NSString *dataStr = [[NSString alloc] initWithData:inputData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
return dataStr;
|
||||
}
|
||||
|
||||
// stringFromStreamData creates a string given the supplied data
|
||||
//
|
||||
// If NSString can create a UTF-8 string from the data, then that is returned.
|
||||
//
|
||||
// Otherwise, this routine tries to find a MIME boundary at the beginning of the data block, and
|
||||
// uses that to break up the data into parts. Each part will be used to try to make a UTF-8 string.
|
||||
// For parts that fail, a replacement string showing the part header and <<n bytes>> is supplied
|
||||
// in place of the binary data.
|
||||
|
||||
- (NSString *)stringFromStreamData:(NSData *)data
|
||||
contentType:(NSString *)contentType {
|
||||
|
||||
if (!data) return nil;
|
||||
|
||||
// optimistically, see if the whole data block is UTF-8
|
||||
NSString *streamDataStr = [self formattedStringFromData:data
|
||||
contentType:contentType
|
||||
JSON:NULL];
|
||||
if (streamDataStr) return streamDataStr;
|
||||
|
||||
// Munge a buffer by replacing non-ASCII bytes with underscores, and turn that munged buffer an
|
||||
// NSString. That gives us a string we can use with NSScanner.
|
||||
NSMutableData *mutableData = [NSMutableData dataWithData:data];
|
||||
unsigned char *bytes = (unsigned char *)mutableData.mutableBytes;
|
||||
|
||||
for (unsigned int idx = 0; idx < mutableData.length; ++idx) {
|
||||
if (bytes[idx] > 0x7F || bytes[idx] == 0) {
|
||||
bytes[idx] = '_';
|
||||
}
|
||||
}
|
||||
|
||||
NSString *mungedStr = [[NSString alloc] initWithData:mutableData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if (mungedStr) {
|
||||
|
||||
// scan for the boundary string
|
||||
NSString *boundary = nil;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:mungedStr];
|
||||
|
||||
if ([scanner scanUpToString:@"\r\n" intoString:&boundary]
|
||||
&& [boundary hasPrefix:@"--"]) {
|
||||
|
||||
// we found a boundary string; use it to divide the string into parts
|
||||
NSArray *mungedParts = [mungedStr componentsSeparatedByString:boundary];
|
||||
|
||||
// look at each munged part in the original string, and try to convert those into UTF-8
|
||||
NSMutableArray *origParts = [NSMutableArray array];
|
||||
NSUInteger offset = 0;
|
||||
for (NSString *mungedPart in mungedParts) {
|
||||
NSUInteger partSize = mungedPart.length;
|
||||
NSData *origPartData = [data subdataWithRange:NSMakeRange(offset, partSize)];
|
||||
NSString *origPartStr = [[NSString alloc] initWithData:origPartData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if (origPartStr) {
|
||||
// we could make this original part into UTF-8; use the string
|
||||
[origParts addObject:origPartStr];
|
||||
} else {
|
||||
// this part can't be made into UTF-8; scan the header, if we can
|
||||
NSString *header = nil;
|
||||
NSScanner *headerScanner = [NSScanner scannerWithString:mungedPart];
|
||||
if (![headerScanner scanUpToString:@"\r\n\r\n" intoString:&header]) {
|
||||
// we couldn't find a header
|
||||
header = @"";
|
||||
}
|
||||
// make a part string with the header and <<n bytes>>
|
||||
NSString *binStr = [NSString stringWithFormat:@"\r%@\r<<%lu bytes>>\r",
|
||||
header, (long)(partSize - header.length)];
|
||||
[origParts addObject:binStr];
|
||||
}
|
||||
offset += partSize + boundary.length;
|
||||
}
|
||||
// rejoin the original parts
|
||||
streamDataStr = [origParts componentsJoinedByString:boundary];
|
||||
}
|
||||
}
|
||||
if (!streamDataStr) {
|
||||
// give up; just make a string showing the uploaded bytes
|
||||
streamDataStr = [NSString stringWithFormat:@"<<%u bytes>>", (unsigned int)data.length];
|
||||
}
|
||||
return streamDataStr;
|
||||
}
|
||||
|
||||
// logFetchWithError is called following a successful or failed fetch attempt
|
||||
//
|
||||
// This method does all the work for appending to and creating log files
|
||||
|
||||
- (void)logFetchWithError:(NSError *)error {
|
||||
if (![[self class] isLoggingEnabled]) return;
|
||||
NSString *logDirectory = [[self class] logDirectoryForCurrentRun];
|
||||
if (!logDirectory) return;
|
||||
NSString *processName = [[self class] loggingProcessName];
|
||||
|
||||
// TODO: add Javascript to display response data formatted in hex
|
||||
|
||||
// each response's NSData goes into its own xml or txt file, though all responses for this run of
|
||||
// the app share a main html file. This counter tracks all fetch responses for this app run.
|
||||
//
|
||||
// we'll use a local variable since this routine may be reentered while waiting for XML formatting
|
||||
// to be completed by an external task
|
||||
static int gResponseCounter = 0;
|
||||
int responseCounter = ++gResponseCounter;
|
||||
|
||||
NSURLResponse *response = [self response];
|
||||
NSDictionary *responseHeaders = [self responseHeaders];
|
||||
NSString *responseDataStr = nil;
|
||||
NSDictionary *responseJSON = nil;
|
||||
|
||||
// if there's response data, decide what kind of file to put it in based on the first bytes of the
|
||||
// file or on the mime type supplied by the server
|
||||
NSString *responseMIMEType = [response MIMEType];
|
||||
BOOL isResponseImage = NO;
|
||||
|
||||
// file name for an image data file
|
||||
NSString *responseDataFileName = nil;
|
||||
|
||||
int64_t responseDataLength = self.downloadedLength;
|
||||
if (responseDataLength > 0) {
|
||||
NSData *downloadedData = self.downloadedData;
|
||||
if (downloadedData == nil
|
||||
&& responseDataLength > 0
|
||||
&& responseDataLength < 20000
|
||||
&& self.destinationFileURL) {
|
||||
// There's a download file that's not too big, so get the data to display from the downloaded
|
||||
// file.
|
||||
NSURL *destinationURL = self.destinationFileURL;
|
||||
downloadedData = [NSData dataWithContentsOfURL:destinationURL];
|
||||
}
|
||||
NSString *responseType = [responseHeaders valueForKey:@"Content-Type"];
|
||||
responseDataStr = [self formattedStringFromData:downloadedData
|
||||
contentType:responseType
|
||||
JSON:&responseJSON];
|
||||
NSString *responseDataExtn = nil;
|
||||
NSData *dataToWrite = nil;
|
||||
if (responseDataStr) {
|
||||
// we were able to make a UTF-8 string from the response data
|
||||
if ([responseMIMEType isEqual:@"application/atom+xml"]
|
||||
|| [responseMIMEType hasSuffix:@"/xml"]) {
|
||||
responseDataExtn = @"xml";
|
||||
dataToWrite = [responseDataStr dataUsingEncoding:NSUTF8StringEncoding];
|
||||
}
|
||||
} else if ([responseMIMEType isEqual:@"image/jpeg"]) {
|
||||
responseDataExtn = @"jpg";
|
||||
dataToWrite = downloadedData;
|
||||
isResponseImage = YES;
|
||||
} else if ([responseMIMEType isEqual:@"image/gif"]) {
|
||||
responseDataExtn = @"gif";
|
||||
dataToWrite = downloadedData;
|
||||
isResponseImage = YES;
|
||||
} else if ([responseMIMEType isEqual:@"image/png"]) {
|
||||
responseDataExtn = @"png";
|
||||
dataToWrite = downloadedData;
|
||||
isResponseImage = YES;
|
||||
} else {
|
||||
// add more non-text types here
|
||||
}
|
||||
// if we have an extension, save the raw data in a file with that extension
|
||||
if (responseDataExtn && dataToWrite) {
|
||||
// generate a response file base name like
|
||||
NSString *responseBaseName = [NSString stringWithFormat:@"fetch_%d_response", responseCounter];
|
||||
responseDataFileName = [responseBaseName stringByAppendingPathExtension:responseDataExtn];
|
||||
NSString *responseDataFilePath = [logDirectory stringByAppendingPathComponent:responseDataFileName];
|
||||
|
||||
NSError *downloadedError = nil;
|
||||
if (gIsLoggingToFile && ![dataToWrite writeToFile:responseDataFilePath
|
||||
options:0
|
||||
error:&downloadedError]) {
|
||||
NSLog(@"%@ logging write error:%@ (%@)", [self class], downloadedError, responseDataFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we'll have one main html file per run of the app
|
||||
NSString *htmlName = [[self class] htmlFileName];
|
||||
NSString *htmlPath =[logDirectory stringByAppendingPathComponent:htmlName];
|
||||
|
||||
// if the html file exists (from logging previous fetches) we don't need
|
||||
// to re-write the header or the scripts
|
||||
NSFileManager *fileMgr = [NSFileManager defaultManager];
|
||||
BOOL didFileExist = [fileMgr fileExistsAtPath:htmlPath];
|
||||
|
||||
NSMutableString* outputHTML = [NSMutableString string];
|
||||
|
||||
// we need a header to say we'll have UTF-8 text
|
||||
if (!didFileExist) {
|
||||
[outputHTML appendFormat:@"<html><head><meta http-equiv=\"content-type\" "
|
||||
"content=\"text/html; charset=UTF-8\"><title>%@ HTTP fetch log %@</title>",
|
||||
processName, [[self class] loggingDateStamp]];
|
||||
}
|
||||
// now write the visible html elements
|
||||
NSString *copyableFileName = [NSString stringWithFormat:@"fetch_%d.txt", responseCounter];
|
||||
|
||||
NSDate *now = [NSDate date];
|
||||
// write the date & time, the comment, and the link to the plain-text (copyable) log
|
||||
[outputHTML appendFormat:@"<b>%@ ", now];
|
||||
|
||||
NSString *comment = [self comment];
|
||||
if (comment.length > 0) {
|
||||
[outputHTML appendFormat:@"%@ ", comment];
|
||||
}
|
||||
[outputHTML appendFormat:@"</b><a href='%@'><i>request/response log</i></a><br>", copyableFileName];
|
||||
NSTimeInterval elapsed = -self.initialBeginFetchDate.timeIntervalSinceNow;
|
||||
[outputHTML appendFormat:@"elapsed: %5.3fsec<br>", elapsed];
|
||||
|
||||
// write the request URL
|
||||
NSURLRequest *request = self.request;
|
||||
NSString *requestMethod = request.HTTPMethod;
|
||||
NSURL *requestURL = request.URL;
|
||||
|
||||
// Save the request URL for next time in case this redirects.
|
||||
NSString *redirectedFromURLString = [self.redirectedFromURL absoluteString];
|
||||
self.redirectedFromURL = [requestURL copy];
|
||||
if (redirectedFromURLString) {
|
||||
[outputHTML appendFormat:@"<FONT COLOR='#990066'><i>redirected from %@</i></FONT><br>",
|
||||
redirectedFromURLString];
|
||||
}
|
||||
[outputHTML appendFormat:@"<b>request:</b> %@ <code>%@</code><br>\n", requestMethod, requestURL];
|
||||
|
||||
// write the request headers
|
||||
NSDictionary *requestHeaders = request.allHTTPHeaderFields;
|
||||
NSUInteger numberOfRequestHeaders = requestHeaders.count;
|
||||
if (numberOfRequestHeaders > 0) {
|
||||
// Indicate if the request is authorized; warn if the request is authorized but non-SSL
|
||||
NSString *auth = [requestHeaders objectForKey:@"Authorization"];
|
||||
NSString *headerDetails = @"";
|
||||
if (auth) {
|
||||
BOOL isInsecure = [[requestURL scheme] isEqual:@"http"];
|
||||
if (isInsecure) {
|
||||
// 26A0 = ⚠
|
||||
headerDetails =
|
||||
@" <i>authorized, non-SSL</i><FONT COLOR='#FF00FF'> ⚠</FONT> ";
|
||||
} else {
|
||||
headerDetails = @" <i>authorized</i>";
|
||||
}
|
||||
}
|
||||
NSString *cookiesHdr = [requestHeaders objectForKey:@"Cookie"];
|
||||
if (cookiesHdr) {
|
||||
headerDetails = [headerDetails stringByAppendingString:@" <i>cookies</i>"];
|
||||
}
|
||||
NSString *matchHdr = [requestHeaders objectForKey:@"If-Match"];
|
||||
if (matchHdr) {
|
||||
headerDetails = [headerDetails stringByAppendingString:@" <i>if-match</i>"];
|
||||
}
|
||||
matchHdr = [requestHeaders objectForKey:@"If-None-Match"];
|
||||
if (matchHdr) {
|
||||
headerDetails = [headerDetails stringByAppendingString:@" <i>if-none-match</i>"];
|
||||
}
|
||||
[outputHTML appendFormat:@" headers: %d %@<br>",
|
||||
(int)numberOfRequestHeaders, headerDetails];
|
||||
} else {
|
||||
[outputHTML appendFormat:@" headers: none<br>"];
|
||||
}
|
||||
// write the request post data
|
||||
NSData *bodyData = nil;
|
||||
NSData *loggedStreamData = self.loggedStreamData;
|
||||
if (loggedStreamData) {
|
||||
bodyData = loggedStreamData;
|
||||
} else {
|
||||
bodyData = self.bodyData;
|
||||
if (bodyData == nil) {
|
||||
bodyData = self.request.HTTPBody;
|
||||
}
|
||||
}
|
||||
uint64_t bodyDataLength = bodyData.length;
|
||||
|
||||
if (bodyData.length == 0) {
|
||||
// If the data is in a body upload file URL, read that in if it's not huge.
|
||||
NSURL *bodyFileURL = self.bodyFileURL;
|
||||
if (bodyFileURL) {
|
||||
NSNumber *fileSizeNum = nil;
|
||||
NSError *fileSizeError = nil;
|
||||
if ([bodyFileURL getResourceValue:&fileSizeNum
|
||||
forKey:NSURLFileSizeKey
|
||||
error:&fileSizeError]) {
|
||||
bodyDataLength = [fileSizeNum unsignedLongLongValue];
|
||||
if (bodyDataLength > 0 && bodyDataLength < 50000) {
|
||||
bodyData = [NSData dataWithContentsOfURL:bodyFileURL
|
||||
options:NSDataReadingUncached
|
||||
error:&fileSizeError];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NSString *bodyDataStr = nil;
|
||||
NSString *postType = [requestHeaders valueForKey:@"Content-Type"];
|
||||
|
||||
if (bodyDataLength > 0) {
|
||||
[outputHTML appendFormat:@" data: %llu bytes, <code>%@</code><br>\n",
|
||||
bodyDataLength, postType ? postType : @"(no type)"];
|
||||
NSString *logRequestBody = self.logRequestBody;
|
||||
if (logRequestBody) {
|
||||
bodyDataStr = [logRequestBody copy];
|
||||
self.logRequestBody = nil;
|
||||
} else {
|
||||
bodyDataStr = [self stringFromStreamData:bodyData
|
||||
contentType:postType];
|
||||
if (bodyDataStr) {
|
||||
// remove OAuth 2 client secret and refresh token
|
||||
bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr
|
||||
betweenStartString:@"client_secret="
|
||||
endString:@"&"];
|
||||
bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr
|
||||
betweenStartString:@"refresh_token="
|
||||
endString:@"&"];
|
||||
// remove ClientLogin password
|
||||
bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr
|
||||
betweenStartString:@"&Passwd="
|
||||
endString:@"&"];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no post data
|
||||
}
|
||||
// write the response status, MIME type, URL
|
||||
NSInteger status = [self statusCode];
|
||||
if (response) {
|
||||
NSString *statusString = @"";
|
||||
if (status != 0) {
|
||||
if (status == 200 || status == 201) {
|
||||
statusString = [NSString stringWithFormat:@"%ld", (long)status];
|
||||
|
||||
// report any JSON-RPC error
|
||||
if ([responseJSON isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *jsonError = [responseJSON objectForKey:@"error"];
|
||||
if ([jsonError isKindOfClass:[NSDictionary class]]) {
|
||||
NSString *jsonCode = [[jsonError valueForKey:@"code"] description];
|
||||
NSString *jsonMessage = [jsonError valueForKey:@"message"];
|
||||
if (jsonCode || jsonMessage) {
|
||||
// 2691 = ⚑
|
||||
NSString *const jsonErrFmt =
|
||||
@" <i>JSON error:</i> <FONT COLOR='#FF00FF'>%@ %@ ⚑</FONT>";
|
||||
statusString = [statusString stringByAppendingFormat:jsonErrFmt,
|
||||
jsonCode ? jsonCode : @"",
|
||||
jsonMessage ? jsonMessage : @""];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// purple for anything other than 200 or 201
|
||||
NSString *flag = status >= 400 ? @" ⚑" : @""; // 2691 = ⚑
|
||||
NSString *explanation = [NSHTTPURLResponse localizedStringForStatusCode:status];
|
||||
NSString *const statusFormat = @"<FONT COLOR='#FF00FF'>%ld %@ %@</FONT>";
|
||||
statusString = [NSString stringWithFormat:statusFormat, (long)status, explanation, flag];
|
||||
}
|
||||
}
|
||||
// show the response URL only if it's different from the request URL
|
||||
NSString *responseURLStr = @"";
|
||||
NSURL *responseURL = response.URL;
|
||||
|
||||
if (responseURL && ![responseURL isEqual:request.URL]) {
|
||||
NSString *const responseURLFormat =
|
||||
@"<FONT COLOR='#FF00FF'>response URL:</FONT> <code>%@</code><br>\n";
|
||||
responseURLStr = [NSString stringWithFormat:responseURLFormat, [responseURL absoluteString]];
|
||||
}
|
||||
[outputHTML appendFormat:@"<b>response:</b> status %@<br>\n%@",
|
||||
statusString, responseURLStr];
|
||||
// Write the response headers
|
||||
NSUInteger numberOfResponseHeaders = responseHeaders.count;
|
||||
if (numberOfResponseHeaders > 0) {
|
||||
// Indicate if the server is setting cookies
|
||||
NSString *cookiesSet = [responseHeaders valueForKey:@"Set-Cookie"];
|
||||
NSString *cookiesStr =
|
||||
cookiesSet ? @" <FONT COLOR='#990066'><i>sets cookies</i></FONT>" : @"";
|
||||
// Indicate if the server is redirecting
|
||||
NSString *location = [responseHeaders valueForKey:@"Location"];
|
||||
BOOL isRedirect = status >= 300 && status <= 399 && location != nil;
|
||||
NSString *redirectsStr =
|
||||
isRedirect ? @" <FONT COLOR='#990066'><i>redirects</i></FONT>" : @"";
|
||||
[outputHTML appendFormat:@" headers: %d %@ %@<br>\n",
|
||||
(int)numberOfResponseHeaders, cookiesStr, redirectsStr];
|
||||
} else {
|
||||
[outputHTML appendString:@" headers: none<br>\n"];
|
||||
}
|
||||
}
|
||||
// error
|
||||
if (error) {
|
||||
[outputHTML appendFormat:@"<b>Error:</b> %@ <br>\n", error.description];
|
||||
}
|
||||
// Write the response data
|
||||
if (responseDataFileName) {
|
||||
if (isResponseImage) {
|
||||
// Make a small inline image that links to the full image file
|
||||
[outputHTML appendFormat:@" data: %lld bytes, <code>%@</code><br>",
|
||||
responseDataLength, responseMIMEType];
|
||||
NSString *const fmt =
|
||||
@"<a href=\"%@\"><img src='%@' alt='image' style='border:solid thin;max-height:32'></a>\n";
|
||||
[outputHTML appendFormat:fmt, responseDataFileName, responseDataFileName];
|
||||
} else {
|
||||
// The response data was XML; link to the xml file
|
||||
NSString *const fmt =
|
||||
@" data: %lld bytes, <code>%@</code> <i><a href=\"%@\">%@</a></i>\n";
|
||||
[outputHTML appendFormat:fmt, responseDataLength, responseMIMEType,
|
||||
responseDataFileName, [responseDataFileName pathExtension]];
|
||||
}
|
||||
} else {
|
||||
// The response data was not an image; just show the length and MIME type
|
||||
[outputHTML appendFormat:@" data: %lld bytes, <code>%@</code>\n",
|
||||
responseDataLength, responseMIMEType ? responseMIMEType : @"(no response type)"];
|
||||
}
|
||||
// Make a single string of the request and response, suitable for copying
|
||||
// to the clipboard and pasting into a bug report
|
||||
NSMutableString *copyable = [NSMutableString string];
|
||||
if (comment) {
|
||||
[copyable appendFormat:@"%@\n\n", comment];
|
||||
}
|
||||
[copyable appendFormat:@"%@ elapsed: %5.3fsec\n", now, elapsed];
|
||||
if (redirectedFromURLString) {
|
||||
[copyable appendFormat:@"Redirected from %@\n", redirectedFromURLString];
|
||||
}
|
||||
[copyable appendFormat:@"Request: %@ %@\n", requestMethod, requestURL];
|
||||
if (requestHeaders.count > 0) {
|
||||
[copyable appendFormat:@"Request headers:\n%@\n",
|
||||
[[self class] headersStringForDictionary:requestHeaders]];
|
||||
}
|
||||
if (bodyDataLength > 0) {
|
||||
[copyable appendFormat:@"Request body: (%llu bytes)\n", bodyDataLength];
|
||||
if (bodyDataStr) {
|
||||
[copyable appendFormat:@"%@\n", bodyDataStr];
|
||||
}
|
||||
[copyable appendString:@"\n"];
|
||||
}
|
||||
if (response) {
|
||||
[copyable appendFormat:@"Response: status %d\n", (int) status];
|
||||
[copyable appendFormat:@"Response headers:\n%@\n",
|
||||
[[self class] headersStringForDictionary:responseHeaders]];
|
||||
[copyable appendFormat:@"Response body: (%lld bytes)\n", responseDataLength];
|
||||
if (responseDataLength > 0) {
|
||||
NSString *logResponseBody = self.logResponseBody;
|
||||
if (logResponseBody) {
|
||||
// The user has provided the response body text.
|
||||
responseDataStr = [logResponseBody copy];
|
||||
self.logResponseBody = nil;
|
||||
}
|
||||
if (responseDataStr != nil) {
|
||||
[copyable appendFormat:@"%@\n", responseDataStr];
|
||||
} else {
|
||||
// Even though it's redundant, we'll put in text to indicate that all the bytes are binary.
|
||||
if (self.destinationFileURL) {
|
||||
[copyable appendFormat:@"<<%lld bytes>> to file %@\n",
|
||||
responseDataLength, self.destinationFileURL.path];
|
||||
} else {
|
||||
[copyable appendFormat:@"<<%lld bytes>>\n", responseDataLength];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
[copyable appendFormat:@"Error: %@\n", error];
|
||||
}
|
||||
// Save to log property before adding the separator
|
||||
self.log = copyable;
|
||||
|
||||
[copyable appendString:@"-----------------------------------------------------------\n"];
|
||||
|
||||
// Write the copyable version to another file (linked to at the top of the html file, above)
|
||||
//
|
||||
// Ideally, something to just copy this to the clipboard like
|
||||
// <span onCopy='window.event.clipboardData.setData(\"Text\",
|
||||
// \"copyable stuff\");return false;'>Copy here.</span>"
|
||||
// would work everywhere, but it only works in Safari as of 8/2010
|
||||
if (gIsLoggingToFile) {
|
||||
NSString *parentDir = [[self class] loggingDirectory];
|
||||
NSString *copyablePath = [logDirectory stringByAppendingPathComponent:copyableFileName];
|
||||
NSError *copyableError = nil;
|
||||
if (![copyable writeToFile:copyablePath
|
||||
atomically:NO
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:©ableError]) {
|
||||
// Error writing to file
|
||||
NSLog(@"%@ logging write error:%@ (%@)", [self class], copyableError, copyablePath);
|
||||
}
|
||||
[outputHTML appendString:@"<br><hr><p>"];
|
||||
|
||||
// Append the HTML to the main output file
|
||||
const char* htmlBytes = outputHTML.UTF8String;
|
||||
NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:htmlPath
|
||||
append:YES];
|
||||
[stream open];
|
||||
[stream write:(const uint8_t *) htmlBytes maxLength:strlen(htmlBytes)];
|
||||
[stream close];
|
||||
|
||||
// Make a symlink to the latest html
|
||||
NSString *const symlinkNameSuffix = [[self class] symlinkNameSuffix];
|
||||
NSString *symlinkName = [processName stringByAppendingString:symlinkNameSuffix];
|
||||
NSString *symlinkPath = [parentDir stringByAppendingPathComponent:symlinkName];
|
||||
|
||||
[fileMgr removeItemAtPath:symlinkPath error:NULL];
|
||||
[fileMgr createSymbolicLinkAtPath:symlinkPath
|
||||
withDestinationPath:htmlPath
|
||||
error:NULL];
|
||||
#if TARGET_OS_IPHONE
|
||||
static BOOL gReportedLoggingPath = NO;
|
||||
if (!gReportedLoggingPath) {
|
||||
gReportedLoggingPath = YES;
|
||||
NSLog(@"GTMSessionFetcher logging to \"%@\"", parentDir);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream {
|
||||
if (!inputStream) return nil;
|
||||
if (![GTMSessionFetcher isLoggingEnabled]) return inputStream;
|
||||
|
||||
[self clearLoggedStreamData]; // Clear any previous data.
|
||||
Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream");
|
||||
if (!monitorClass) {
|
||||
NSString const *str = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>";
|
||||
NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[self appendLoggedStreamData:stringData];
|
||||
return inputStream;
|
||||
}
|
||||
inputStream = [monitorClass inputStreamWithStream:inputStream];
|
||||
|
||||
GTMReadMonitorInputStream *readMonitorInputStream = (GTMReadMonitorInputStream *)inputStream;
|
||||
[readMonitorInputStream setReadDelegate:self];
|
||||
SEL readSel = @selector(inputStream:readIntoBuffer:length:);
|
||||
[readMonitorInputStream setReadSelector:readSel];
|
||||
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider:
|
||||
(GTMSessionFetcherBodyStreamProvider)streamProvider {
|
||||
if (!streamProvider) return nil;
|
||||
if (![GTMSessionFetcher isLoggingEnabled]) return streamProvider;
|
||||
|
||||
[self clearLoggedStreamData]; // Clear any previous data.
|
||||
Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream");
|
||||
if (!monitorClass) {
|
||||
NSString const *str = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>";
|
||||
NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[self appendLoggedStreamData:stringData];
|
||||
return streamProvider;
|
||||
}
|
||||
GTMSessionFetcherBodyStreamProvider loggedStreamProvider =
|
||||
^(GTMSessionFetcherBodyStreamProviderResponse response) {
|
||||
streamProvider(^(NSInputStream *bodyStream) {
|
||||
bodyStream = [self loggedInputStreamForInputStream:bodyStream];
|
||||
response(bodyStream);
|
||||
});
|
||||
};
|
||||
return loggedStreamProvider;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GTMSessionFetcher (GTMSessionFetcherLoggingUtilities)
|
||||
|
||||
- (void)inputStream:(GTMReadMonitorInputStream *)stream
|
||||
readIntoBuffer:(void *)buffer
|
||||
length:(int64_t)length {
|
||||
// append the captured data
|
||||
NSData *data = [NSData dataWithBytesNoCopy:buffer
|
||||
length:(NSUInteger)length
|
||||
freeWhenDone:NO];
|
||||
[self appendLoggedStreamData:data];
|
||||
}
|
||||
|
||||
#pragma mark Fomatting Utilities
|
||||
|
||||
+ (NSString *)snipSubstringOfString:(NSString *)originalStr
|
||||
betweenStartString:(NSString *)startStr
|
||||
endString:(NSString *)endStr {
|
||||
#if SKIP_GTM_FETCH_LOGGING_SNIPPING
|
||||
return originalStr;
|
||||
#else
|
||||
if (!originalStr) return nil;
|
||||
|
||||
// Find the start string, and replace everything between it
|
||||
// and the end string (or the end of the original string) with "_snip_"
|
||||
NSRange startRange = [originalStr rangeOfString:startStr];
|
||||
if (startRange.location == NSNotFound) return originalStr;
|
||||
|
||||
// We found the start string
|
||||
NSUInteger originalLength = originalStr.length;
|
||||
NSUInteger startOfTarget = NSMaxRange(startRange);
|
||||
NSRange targetAndRest = NSMakeRange(startOfTarget, originalLength - startOfTarget);
|
||||
NSRange endRange = [originalStr rangeOfString:endStr
|
||||
options:0
|
||||
range:targetAndRest];
|
||||
NSRange replaceRange;
|
||||
if (endRange.location == NSNotFound) {
|
||||
// Found no end marker so replace to end of string
|
||||
replaceRange = targetAndRest;
|
||||
} else {
|
||||
// Replace up to the endStr
|
||||
replaceRange = NSMakeRange(startOfTarget, endRange.location - startOfTarget);
|
||||
}
|
||||
NSString *result = [originalStr stringByReplacingCharactersInRange:replaceRange
|
||||
withString:@"_snip_"];
|
||||
return result;
|
||||
#endif // SKIP_GTM_FETCH_LOGGING_SNIPPING
|
||||
}
|
||||
|
||||
+ (NSString *)headersStringForDictionary:(NSDictionary *)dict {
|
||||
// Format the dictionary in http header style, like
|
||||
// Accept: application/json
|
||||
// Cache-Control: no-cache
|
||||
// Content-Type: application/json; charset=utf-8
|
||||
//
|
||||
// Pad the key names, but not beyond 16 chars, since long custom header
|
||||
// keys just create too much whitespace
|
||||
NSArray *keys = [dict.allKeys sortedArrayUsingSelector:@selector(compare:)];
|
||||
|
||||
NSMutableString *str = [NSMutableString string];
|
||||
for (NSString *key in keys) {
|
||||
NSString *value = [dict valueForKey:key];
|
||||
if ([key isEqual:@"Authorization"]) {
|
||||
// Remove OAuth 1 token
|
||||
value = [[self class] snipSubstringOfString:value
|
||||
betweenStartString:@"oauth_token=\""
|
||||
endString:@"\""];
|
||||
|
||||
// Remove OAuth 2 bearer token (draft 16, and older form)
|
||||
value = [[self class] snipSubstringOfString:value
|
||||
betweenStartString:@"Bearer "
|
||||
endString:@"\n"];
|
||||
value = [[self class] snipSubstringOfString:value
|
||||
betweenStartString:@"OAuth "
|
||||
endString:@"\n"];
|
||||
|
||||
// Remove Google ClientLogin
|
||||
value = [[self class] snipSubstringOfString:value
|
||||
betweenStartString:@"GoogleLogin auth="
|
||||
endString:@"\n"];
|
||||
}
|
||||
[str appendFormat:@" %@: %@\n", key, value];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif // !STRIP_GTM_FETCH_LOGGING
|
||||
190
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h
generated
Normal file
190
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h
generated
Normal file
@@ -0,0 +1,190 @@
|
||||
/* Copyright 2014 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// For best performance and convenient usage, fetchers should be generated by a common
|
||||
// GTMSessionFetcherService instance, like
|
||||
//
|
||||
// _fetcherService = [[GTMSessionFetcherService alloc] init];
|
||||
// GTMSessionFetcher* myFirstFetcher = [_fetcherService fetcherWithRequest:request1];
|
||||
// GTMSessionFetcher* mySecondFetcher = [_fetcherService fetcherWithRequest:request2];
|
||||
|
||||
#import "GTMSessionFetcher.h"
|
||||
|
||||
GTM_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Notifications.
|
||||
|
||||
// This notification indicates a reusable session has become invalid. It is intended mainly for the
|
||||
// service's unit tests.
|
||||
//
|
||||
// The notification object is the fetcher service.
|
||||
// The invalid session is provided via the userInfo kGTMSessionFetcherServiceSessionKey key.
|
||||
extern NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification;
|
||||
extern NSString *const kGTMSessionFetcherServiceSessionKey;
|
||||
|
||||
@interface GTMSessionFetcherService : NSObject<GTMSessionFetcherServiceProtocol>
|
||||
|
||||
// Queues of delayed and running fetchers. Each dictionary contains arrays
|
||||
// of GTMSessionFetcher *fetchers, keyed by NSString *host
|
||||
@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *delayedFetchersByHost;
|
||||
@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *runningFetchersByHost;
|
||||
|
||||
// A max value of 0 means no fetchers should be delayed.
|
||||
// The default limit is 10 simultaneous fetchers targeting each host.
|
||||
// This does not apply to fetchers whose useBackgroundSession property is YES. Since services are
|
||||
// not resurrected on an app relaunch, delayed fetchers would effectively be abandoned.
|
||||
@property(atomic, assign) NSUInteger maxRunningFetchersPerHost;
|
||||
|
||||
// Properties to be applied to each fetcher; see GTMSessionFetcher.h for descriptions
|
||||
@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration;
|
||||
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock;
|
||||
@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage;
|
||||
@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue;
|
||||
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock;
|
||||
@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential;
|
||||
@property(atomic, strong) NSURLCredential *proxyCredential;
|
||||
@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes;
|
||||
@property(atomic, assign) BOOL allowLocalhostRequest;
|
||||
@property(atomic, assign) BOOL allowInvalidServerCertificates;
|
||||
@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled;
|
||||
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock;
|
||||
@property(atomic, assign) NSTimeInterval maxRetryInterval;
|
||||
@property(atomic, assign) NSTimeInterval minRetryInterval;
|
||||
@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties;
|
||||
|
||||
#if GTM_BACKGROUND_TASK_FETCHING
|
||||
@property(atomic, assign) BOOL skipBackgroundTask;
|
||||
#endif
|
||||
|
||||
// A default useragent of GTMFetcherStandardUserAgentString(nil) will be given to each fetcher
|
||||
// created by this service unless the request already has a user-agent header set.
|
||||
// This default will be added starting with builds with the SDKs for OS X 10.11 and iOS 9.
|
||||
//
|
||||
// To use the configuration's default user agent, set this property to nil.
|
||||
@property(atomic, copy, GTM_NULLABLE) NSString *userAgent;
|
||||
|
||||
// The authorizer to attach to the created fetchers. If a specific fetcher should
|
||||
// not authorize its requests, the fetcher's authorizer property may be set to nil
|
||||
// before the fetch begins.
|
||||
@property(atomic, strong, GTM_NULLABLE) id<GTMFetcherAuthorizationProtocol> authorizer;
|
||||
|
||||
// Delegate queue used by the session when calling back to the fetcher. The default
|
||||
// is the main queue. Changing this does not affect the queue used to call back to the
|
||||
// application; that is specified by the callbackQueue property above.
|
||||
@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue;
|
||||
|
||||
// When enabled, indicates the same session should be used by subsequent fetchers.
|
||||
//
|
||||
// This is enabled by default.
|
||||
@property(atomic, assign) BOOL reuseSession;
|
||||
|
||||
// Sets the delay until an unused session is invalidated.
|
||||
// The default interval is 60 seconds.
|
||||
//
|
||||
// If the interval is set to 0, then any reused session is not invalidated except by
|
||||
// explicitly invoking -resetSession. Be aware that setting the interval to 0 thus
|
||||
// causes the session's delegate to be retained until the session is explicitly reset.
|
||||
@property(atomic, assign) NSTimeInterval unusedSessionTimeout;
|
||||
|
||||
// If shouldReuseSession is enabled, this will force creation of a new session when future
|
||||
// fetchers begin.
|
||||
- (void)resetSession;
|
||||
|
||||
// Create a fetcher
|
||||
//
|
||||
// These methods will return a fetcher. If successfully created, the connection
|
||||
// will hold a strong reference to it for the life of the connection as well.
|
||||
// So the caller doesn't have to hold onto the fetcher explicitly unless they
|
||||
// want to be able to monitor or cancel it.
|
||||
- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request;
|
||||
- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL;
|
||||
- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString;
|
||||
|
||||
// Common method for fetcher creation.
|
||||
//
|
||||
// -fetcherWithRequest:fetcherClass: may be overridden to customize creation of
|
||||
// fetchers. This is the ONLY method in the GTMSessionFetcher library intended to
|
||||
// be overridden.
|
||||
- (id)fetcherWithRequest:(NSURLRequest *)request
|
||||
fetcherClass:(Class)fetcherClass;
|
||||
|
||||
- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher;
|
||||
|
||||
- (NSUInteger)numberOfFetchers; // running + delayed fetchers
|
||||
- (NSUInteger)numberOfRunningFetchers;
|
||||
- (NSUInteger)numberOfDelayedFetchers;
|
||||
|
||||
// Return a list of all running or delayed fetchers. This includes fetchers created
|
||||
// by the service which have been started and have not yet stopped.
|
||||
//
|
||||
// Returns an array of fetcher objects, or nil if none.
|
||||
- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchers;
|
||||
|
||||
// Search for running or delayed fetchers with the specified URL.
|
||||
//
|
||||
// Returns an array of fetcher objects found, or nil if none found.
|
||||
- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchersWithRequestURL:(NSURL *)requestURL;
|
||||
|
||||
- (void)stopAllFetchers;
|
||||
|
||||
// Methods for use by the fetcher class only.
|
||||
- (GTM_NULLABLE NSURLSession *)session;
|
||||
- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation;
|
||||
- (GTM_NULLABLE id<NSURLSessionDelegate>)sessionDelegate;
|
||||
- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate;
|
||||
|
||||
// The testBlock can inspect its fetcher parameter's request property to
|
||||
// determine which fetcher is being faked.
|
||||
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock;
|
||||
|
||||
@end
|
||||
|
||||
@interface GTMSessionFetcherService (TestingSupport)
|
||||
|
||||
// Convenience method to create a fetcher service for testing.
|
||||
//
|
||||
// Fetchers generated by this mock fetcher service will not perform any
|
||||
// network operation, but will invoke callbacks and provide the supplied data
|
||||
// or error to the completion handler.
|
||||
//
|
||||
// You can make more customized mocks by setting the test block property of the service
|
||||
// or fetcher; the test block can inspect the fetcher's request or other properties.
|
||||
//
|
||||
// See the description of the testBlock property below.
|
||||
+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil
|
||||
fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil;
|
||||
|
||||
// Spin the run loop and discard events (or, if not on the main thread, just sleep the thread)
|
||||
// until all running and delayed fetchers have completed.
|
||||
//
|
||||
// This is only for use in testing or in tools without a user interface.
|
||||
//
|
||||
// Synchronous fetches should never be done by shipping apps; they are
|
||||
// sufficient reason for rejection from the app store.
|
||||
//
|
||||
// Returns NO if timed out.
|
||||
- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds;
|
||||
|
||||
@end
|
||||
|
||||
@interface GTMSessionFetcherService (BackwardsCompatibilityOnly)
|
||||
|
||||
// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves.
|
||||
// This method is just for compatibility with the old fetcher.
|
||||
@property(atomic, assign) NSInteger cookieStorageMethod;
|
||||
|
||||
@end
|
||||
|
||||
GTM_ASSUME_NONNULL_END
|
||||
1352
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m
generated
Normal file
1352
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m
generated
Normal file
File diff suppressed because it is too large
Load Diff
128
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h
generated
Normal file
128
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h
generated
Normal file
@@ -0,0 +1,128 @@
|
||||
/* Copyright 2014 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// GTMSessionUploadFetcher implements Google's resumable upload protocol.
|
||||
|
||||
//
|
||||
// This subclass of GTMSessionFetcher simulates the series of fetches
|
||||
// needed for chunked upload as a single fetch operation.
|
||||
//
|
||||
// Protocol document: TBD
|
||||
//
|
||||
// To the client, the only fetcher that exists is this class; the subsidiary
|
||||
// fetchers needed for uploading chunks are not visible (though the most recent
|
||||
// chunk fetcher may be accessed via the -activeFetcher or -chunkFetcher methods, and
|
||||
// -responseHeaders and -statusCode reflect results from the most recent chunk
|
||||
// fetcher.)
|
||||
//
|
||||
// Chunk fetchers are discarded as soon as they have completed.
|
||||
//
|
||||
|
||||
// Note: Unlike the fetcher superclass, the methods of GTMSessionUploadFetcher should
|
||||
// only be used from the main thread until further work is done to make this subclass
|
||||
// thread-safe.
|
||||
|
||||
#import "GTMSessionFetcher.h"
|
||||
#import "GTMSessionFetcherService.h"
|
||||
|
||||
GTM_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Unless an application knows it needs a smaller chunk size, it should use the standard
|
||||
// chunk size, which sends the entire file as a single chunk to minimize upload overhead.
|
||||
extern int64_t const kGTMSessionUploadFetcherStandardChunkSize;
|
||||
|
||||
// When uploading requires data buffer allocations (such as uploading from an NSData or
|
||||
// an NSFileHandle) this is the maximum buffer size that will be created by the fetcher.
|
||||
extern int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize;
|
||||
|
||||
// Notification that the upload location URL was provided by the server.
|
||||
extern NSString *const kGTMSessionFetcherUploadLocationObtainedNotification;
|
||||
|
||||
// Block to provide data during uploads.
|
||||
//
|
||||
// Response data may be allocated with dataWithBytesNoCopy:length:freeWhenDone: for efficiency,
|
||||
// and released after the response block returns.
|
||||
//
|
||||
// Pass nil as the data (and optionally an NSError) for a failure.
|
||||
typedef void (^GTMSessionUploadFetcherDataProviderResponse)(NSData * GTM_NULLABLE_TYPE data,
|
||||
NSError * GTM_NULLABLE_TYPE error);
|
||||
typedef void (^GTMSessionUploadFetcherDataProvider)(int64_t offset, int64_t length,
|
||||
GTMSessionUploadFetcherDataProviderResponse response);
|
||||
|
||||
@interface GTMSessionUploadFetcher : GTMSessionFetcher
|
||||
|
||||
// Create an upload fetcher specifying either the request or the resume location URL,
|
||||
// then set an upload data source using one of these:
|
||||
//
|
||||
// setUploadFileURL:
|
||||
// setUploadDataLength:provider:
|
||||
// setUploadFileHandle:
|
||||
// setUploadData:
|
||||
|
||||
+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request
|
||||
uploadMIMEType:(NSString *)uploadMIMEType
|
||||
chunkSize:(int64_t)chunkSize
|
||||
fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil;
|
||||
|
||||
+ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL
|
||||
uploadMIMEType:(NSString *)uploadMIMEType
|
||||
chunkSize:(int64_t)chunkSize
|
||||
fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil;
|
||||
|
||||
- (void)setUploadDataLength:(int64_t)fullLength
|
||||
provider:(GTM_NULLABLE GTMSessionUploadFetcherDataProvider)block;
|
||||
|
||||
+ (NSArray *)uploadFetchersForBackgroundSessions;
|
||||
+ (GTM_NULLABLE instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier;
|
||||
|
||||
- (void)pauseFetching;
|
||||
- (void)resumeFetching;
|
||||
- (BOOL)isPaused;
|
||||
|
||||
@property(atomic, strong, GTM_NULLABLE) NSURL *uploadLocationURL;
|
||||
@property(atomic, strong, GTM_NULLABLE) NSData *uploadData;
|
||||
@property(atomic, strong, GTM_NULLABLE) NSURL *uploadFileURL;
|
||||
@property(atomic, strong, GTM_NULLABLE) NSFileHandle *uploadFileHandle;
|
||||
@property(atomic, copy, readonly, GTM_NULLABLE) GTMSessionUploadFetcherDataProvider uploadDataProvider;
|
||||
@property(atomic, copy) NSString *uploadMIMEType;
|
||||
@property(atomic, assign) int64_t chunkSize;
|
||||
@property(atomic, readonly, assign) int64_t currentOffset;
|
||||
|
||||
// The fetcher for the current data chunk, if any
|
||||
@property(atomic, strong, GTM_NULLABLE) GTMSessionFetcher *chunkFetcher;
|
||||
|
||||
// The active fetcher is the current chunk fetcher, or the upload fetcher itself
|
||||
// if no chunk fetcher has yet been created.
|
||||
@property(atomic, readonly) GTMSessionFetcher *activeFetcher;
|
||||
|
||||
// The last request made by an active fetcher. Useful for testing.
|
||||
@property(atomic, readonly, GTM_NULLABLE) NSURLRequest *lastChunkRequest;
|
||||
|
||||
// The status code from the most recently-completed fetch.
|
||||
@property(atomic, assign) NSInteger statusCode;
|
||||
|
||||
// Exposed for testing only.
|
||||
@property(atomic, readonly, GTM_NULLABLE) dispatch_queue_t delegateCallbackQueue;
|
||||
@property(atomic, readonly, GTM_NULLABLE) GTMSessionFetcherCompletionHandler delegateCompletionHandler;
|
||||
|
||||
@end
|
||||
|
||||
@interface GTMSessionFetcher (GTMSessionUploadFetcherMethods)
|
||||
|
||||
@property(readonly, GTM_NULLABLE) GTMSessionUploadFetcher *parentUploadFetcher;
|
||||
|
||||
@end
|
||||
|
||||
GTM_ASSUME_NONNULL_END
|
||||
1804
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m
generated
Normal file
1804
My Mind/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user