Project import
diff --git a/Courier/CRSessionController.h b/Courier/CRSessionController.h
new file mode 100644
index 0000000..c82ac11
--- /dev/null
+++ b/Courier/CRSessionController.h
@@ -0,0 +1,204 @@
+//
+// CRSessionController.h
+// Courier
+//
+// Created by Andrew Smith on 9/22/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+#import <Reachability/Reachability.h>
+
+/**
+ The CRSessionController wraps around an NSURLSession. Use this class to create and managed tasks. For
+ instance, you can group tasks by name and bulk cancel said group.
+ */
+NS_CLASS_AVAILABLE(10_9, 7_0)
+@interface CRSessionController : NSObject
+
+/**
+ The internal NSURLSession.
+ */
+@property (nonatomic, readonly, strong) NSURLSession *session;
+
+/**
+ The NSOperationQueue on which the NSURLSessions delegate callbacks are run.
+ */
+@property (nonatomic, readonly, strong) NSOperationQueue *sessionDelegateOperationQueue;
+
+/**
+ A copy of the internal NSURLSessionConfiguration from the internal NSURLSession.
+ */
+@property (nonatomic, readonly) NSURLSessionConfiguration *configuration;
+
+/**
+ Create a session controller with the given NSURLSessionConfiguration. Note that once a configuration
+ is passed in, it is immutable.
+ */
+- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
+
+/**
+ Create a session controller with the given NSURLSessionConfiguration. Note that once a configuration
+ is passed in, it is immutable.
+ */
++ (instancetype)sessionControllerWithConfiguration:(NSURLSessionConfiguration *)configuration;
+
+/**
+ Create an NSURLSessionDataTask for the given request. The completion handler will run regardless of
+ whether or not the task succeeds or not.
+*/
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+ completionHandler:(void (^)(NSData *data,
+ NSURLResponse *response,
+ BOOL cachedResponse,
+ NSError *error))completionHandler;
+/**
+ Create an NSURLSessionDataTask for the given request and add it to the specified
+ non-nil task group. If task group is nil, task will be added to generic group.
+ */
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+ taskGroup:(NSString *)group
+ completionHandler:(void (^)(NSData *data,
+ NSURLResponse *response,
+ BOOL cachedResponse,
+ NSError *error))completionHandler;
+
+/**
+ Adds an NSURLSessionTaskDelegate to the given task. Right now, just the HTTPRedirect call and
+ authentication challenge is passed through to the delegate, but future work will pass through
+ all callbacks.
+
+ @param delegate The delegate that will respond to NSURLSessionTaskDelegate callbacks
+ @param task The task to use the delegate with.
+ */
+- (void)addNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate
+ forTask:(NSURLSessionTask *)task;
+
+/**
+ Removes the delegate. At this point, the delegate will no longer respond to callbacks.
+
+ @param delegate The delegate to remove.
+ */
+- (void)removeNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate;
+
+/**
+ @returns The delegate specified in addNSURLSessionTaskDelegate:forTask: by the task parameter.
+
+ @param task The NSURLSessionTask to retrieve the delegate for.
+ */
+- (id <NSURLSessionTaskDelegate>)NSURLSessionTaskDelegateForTask:(NSURLSessionTask *)task;
+
+@end
+
+@interface CRSessionController (TaskManagement)
+
+/**
+ * Returns the task, if any, associated with the task ID
+ *
+ * @param taskId The task ID, called from task.taskIdentifier.
+ *
+ * @return The task associated with the taskIdentifier
+ */
+- (NSURLSessionTask *)taskWithIdentifier:(NSUInteger)taskIdentifier;
+
+/**
+ * A quick way to find out if you have a task with the given identifier.
+ *
+ * @param taskIdentifier The task identifier, from task.taskIdentifier
+ *
+ * @return Returns YES if the controller has a task with the given taskIdentifier.
+ */
+- (BOOL)hasTaskWithIdentifier:(NSUInteger)taskIdentifier;
+
+/**
+ @returns YES if the task group has any task with the state.
+ @param group The name of the task group.
+ @param state The state of the NSURLSessionTask to test against.
+ */
+- (BOOL)hasTasksInGroup:(NSString *)group
+ withState:(NSURLSessionTaskState)state;
+
+/**
+ Calls -suspend on all tasks in a given group.
+ */
+- (void)suspendTasksInGroup:(NSString *)group;
+
+/**
+ Calls -resume on all tasks in a given group.
+ */
+- (void)resumeTasksInGroup:(NSString *)group;
+
+/**
+ Calls -cancel on all tasks in a given group.
+ */
+- (void)cancelTasksInGroup:(NSString *)group;
+
+/**
+ Calls -suspend on all tasks tracked by this controller
+ */
+- (void)suspendAllTasks;
+
+/**
+ Calls -resume on all tasks tracked by this controller
+ */
+- (void)resumeAllTasks;
+
+/**
+ Calls -cancel on all tasks tracked by this controller
+ */
+- (void)cancelAllTasks;
+
+@end
+
+@interface CRSessionController (Reachability)
+
+/**
+ Returns YES if any internet connection is reachable.
+ */
+- (BOOL)isInternetReachable;
+
+/**
+ Starts monitoring for reachability changes. If there is a change, the reachability status block will
+ be called. Alternatively, you can listen for the kReachabilityChangedNotification NSNotification.
+ */
+- (void)startReachabilityMonitoring;
+
+/**
+ Stops reachability monitoring. Changes in reachability will no longer call the reachability status
+ change block, or fire off the kReachabilityChangedNotification NSNotification.
+ */
+- (void)stopReachabilityMonitoring;
+
+/**
+ Sets a block that will run every time the network reachability status changes.
+ @param block The block to call when the network status changes.
+ */
+- (void)setReachabilityStatusChangeBlock:(void (^)(NetworkStatus status))block;
+
+@end
+
+@interface CRSessionController (Debug)
+
+/**
+ * Pretty prints all the current requests both queued and executing
+ */
+- (void)logRequests;
+
+@end
diff --git a/Courier/CRSessionController.m b/Courier/CRSessionController.m
new file mode 100644
index 0000000..298f681
--- /dev/null
+++ b/Courier/CRSessionController.m
@@ -0,0 +1,528 @@
+//
+// CRSessionController.m
+// Courier
+//
+// Created by Andrew Smith on 9/22/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "CRSessionController.h"
+
+#import <UIKit/UIKit.h>
+#import <Reachability/Reachability.h>
+
+#import "CourierLog.h"
+#import "NSURLResponse+Courier.h"
+#import "CRSessionDelegate.h"
+
+#define kCRSessionControllerGenericTaskGroup @"kCRSessionControllerGenericTaskGroup"
+
+@interface CRSessionController ()
+
+/**
+ * The keys are the group names, and the values are arrays of associated tasks
+ */
+@property (nonatomic, strong) NSMutableDictionary *groups;
+
+/**
+ * The keys are a unique string, the values are a dictionary with the task object
+ * and associated group.
+ */
+@property (nonatomic, strong) NSMutableDictionary *tasksByToken;
+
+/**
+ * The keys are the task IDs, and the values are the associated task object
+ */
+@property (nonatomic, strong) NSMutableDictionary *tasksByIdentifier;
+
+/**
+
+ */
+@property (nonatomic, strong) NSMutableDictionary *sessionDelegates;
+
+/**
+ Internal reachability object
+ */
+@property (nonatomic, strong) Reachability *reachabilityObject;
+
+/**
+ This is a serial queue used to manage NSURLSessionTasks. This queue is used to ensure that you
+ can interact with the CRSessionController in a thread safe way.
+ */
+@property (nonatomic, strong) NSOperationQueue *serialQueue;
+
+@end
+
+@implementation CRSessionController
+
+- (void)dealloc
+{
+ [self.session invalidateAndCancel];
+}
+
+- (instancetype)init
+{
+ return [self initWithSessionConfiguration:nil];
+}
+
+- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
+{
+ self = [super init];
+ if (self != nil) {
+ //
+ // Use default session configuration if none provided
+ //
+ if (configuration == nil) {
+ configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
+ }
+
+ //
+ // NSURLSession delegate queue
+ //
+ NSOperationQueue *delegateQueue = [NSOperationQueue new];
+ delegateQueue.maxConcurrentOperationCount = 1;
+ _sessionDelegateOperationQueue = delegateQueue;
+
+ //
+ // Build NSURLSession
+ //
+ CRSessionDelegate *sessionDelegate =
+ [CRSessionDelegate sessionDelegateWithSessionController:self];
+ _session = [NSURLSession sessionWithConfiguration:configuration
+ delegate:sessionDelegate
+ delegateQueue:_sessionDelegateOperationQueue];
+
+ //
+ // TODO
+ //
+ NSOperationQueue *serialQueue = [NSOperationQueue new];
+ serialQueue.maxConcurrentOperationCount = 1;
+ _serialQueue = serialQueue;
+
+ //
+ // Keep track of tasks
+ //
+ _groups = [NSMutableDictionary dictionary];
+ _tasksByToken = [NSMutableDictionary dictionary];
+ _tasksByIdentifier = [NSMutableDictionary dictionary];
+ _sessionDelegates = [NSMutableDictionary dictionary];
+
+ //
+ // Reachable
+ //
+ _reachabilityObject = [Reachability reachabilityForInternetConnection];
+ }
+ return self;
+}
+
++ (instancetype)sessionControllerWithConfiguration:(NSURLSessionConfiguration *)configuration
+{
+ return [[self alloc] initWithSessionConfiguration:configuration];
+}
+
+#pragma mark - Task creation
+
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+ completionHandler:(void (^)(NSData *data,
+ NSURLResponse *response,
+ BOOL cachedResponse,
+ NSError *error))completionHandler
+{
+ NSURLSessionDataTask *task = [self dataTaskForRequest:request
+ taskGroup:nil
+ completionHandler:completionHandler];
+ return task;
+}
+
+- (NSURLSessionDataTask *)dataTaskForRequest:(NSURLRequest *)request
+ taskGroup:(NSString *)group
+ completionHandler:(void (^)(NSData *data,
+ NSURLResponse *response,
+ BOOL cachedResponse,
+ NSError *error))completionHandler
+{
+ CourierLogInfo(@"Creating task for URL : %@", request.URL);
+
+ //
+ // Unique task token
+ //
+ NSString *token = [self uniqueToken];
+
+ //
+ // Check for cached response. Pass this into the completion and allow the task
+ // to pass through the normal delegate queue flow.
+ //
+ NSURLCache *urlCache = _session.configuration.URLCache;
+ BOOL cachedResponse = urlCache && [urlCache cachedResponseForRequest:request];
+
+ //
+ // Create Task
+ //
+ __weak typeof(self) weakSelf = self;
+ NSURLSessionDataTask *task = [_session dataTaskWithRequest:request
+ completionHandler:^(NSData *data,
+ NSURLResponse *response,
+ NSError *error) {
+ __strong typeof(self) strongSelf = weakSelf;
+ [strongSelf logResponse:response data:data error:error];
+ [strongSelf removeTaskWithToken:token];
+ if (completionHandler) completionHandler(data,
+ response,
+ cachedResponse,
+ error);
+ }];
+
+ //
+ // Keep track of the task
+ //
+ [self addTask:task
+ withToken:token
+ toGroup:group];
+
+ return task;
+}
+
+- (void)logResponse:(NSURLResponse *)response
+ data:(NSData *)data
+ error:(NSError *)error
+{
+ CourierLogInfo(@"Finishing task for URL : %@ status code: %li",
+ response.URL,
+ (long)response.cou_statusCode);
+
+#if DEBUG && COURIER_LOG
+ NSMutableString *logString = [NSMutableString string];
+
+ [logString appendString:@"\n########################################"];
+ [logString appendFormat:@"\nResponse: %@", response];
+
+ NSString *encodingName = response.textEncodingName;
+ NSString *dataString = nil;
+ if (encodingName) {
+ NSStringEncoding encodingType = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName));
+ dataString = [[NSString alloc] initWithData:data encoding:encodingType];
+ }
+
+ [logString appendFormat:@"\nData: %@", dataString];
+ [logString appendFormat:@"\nError: %@", error];
+ [logString appendString:@"\n########################################"];
+
+ NSLog(@"%@", logString);
+#endif
+}
+
+- (NSURLSessionConfiguration *)configuration
+{
+ return _session.configuration;
+}
+
+#pragma mark - Add/Remove Tasks
+
+- (NSString *)uniqueToken
+{
+ return [[NSProcessInfo processInfo] globallyUniqueString];
+}
+
+- (void)addTask:(NSURLSessionTask *)task
+ withToken:(NSString *)token
+ toGroup:(NSString *)group
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+
+ if (!task) return;
+ NSString *internalGroup = group;
+ if (!internalGroup || internalGroup.length == 0) {
+ internalGroup = kCRSessionControllerGenericTaskGroup;
+ }
+
+ CourierLogInfo(@"Adding task to group : %@", group);
+
+ //
+ // Add task to Groups
+ //
+ NSMutableArray *tasks = [strongSelf.groups objectForKey:internalGroup];
+ if (!tasks) {
+ tasks = [NSMutableArray array];
+ }
+ [tasks addObject:task];
+ [strongSelf.groups setObject:tasks
+ forKey:internalGroup];
+
+ //
+ // Add task to Tasks by token
+ //
+ NSMutableDictionary *taskDict = [strongSelf.tasksByToken objectForKey:internalGroup];
+ if (!taskDict) {
+ taskDict = [NSMutableDictionary dictionary];
+ }
+ taskDict[@"task"] = task;
+ taskDict[@"group"] = internalGroup;
+ [strongSelf.tasksByToken setObject:taskDict
+ forKey:token];
+
+ //
+ // Add task to tasks by id
+ //
+ [strongSelf.tasksByIdentifier setObject:task
+ forKey:@(task.taskIdentifier)];
+ }];
+}
+
+- (void)removeTaskWithToken:(NSString *)token
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+ NSDictionary *taskDict = [strongSelf.tasksByToken objectForKey:token];
+ NSString *group = taskDict[@"group"];
+ NSURLSessionTask *task = taskDict[@"task"];
+
+ if (!task) return;
+ if (!group || group.length == 0) {
+ group = kCRSessionControllerGenericTaskGroup;
+ }
+
+ //
+ // Remove task delegate
+ //
+ id delegate = [strongSelf NSURLSessionTaskDelegateForTask:task];
+ [strongSelf removeNSURLSessionTaskDelegate:delegate];
+
+ CourierLogInfo(@"Removing task for URL: %@ in group: %@",
+ task.currentRequest.URL,
+ group);
+
+ //
+ // Get the tasks
+ //
+ NSMutableArray *groupTasks = [strongSelf.groups objectForKey:group];
+
+ //
+ // Remove the task
+ //
+ [strongSelf.tasksByToken removeObjectForKey:token];
+ [strongSelf.tasksByIdentifier removeObjectForKey:@(task.taskIdentifier)];
+ [groupTasks removeObject:task];
+
+ //
+ // If empty, remove tasks array
+ //
+ if (groupTasks.count == 0) {
+ [strongSelf.groups removeObjectForKey:group];
+ }
+ }];
+}
+
+- (void)addNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate
+ forTask:(NSURLSessionTask *)task
+{
+ NSParameterAssert(delegate);
+ NSParameterAssert(task);
+ if (delegate == nil || task == nil) return;
+ @synchronized(_sessionDelegates) {
+ [_sessionDelegates setObject:delegate
+ forKey:[NSValue valueWithNonretainedObject:task]];
+ }
+}
+
+- (void)removeNSURLSessionTaskDelegate:(id <NSURLSessionTaskDelegate>)delegate
+{
+ if (delegate == nil) return;
+ @synchronized(_sessionDelegates) {
+ __block id taskKey;
+ [_sessionDelegates enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
+ if ([obj isEqual:delegate]) {
+ taskKey = key;
+ *stop = YES;
+ }
+ }];
+ [_sessionDelegates removeObjectForKey:taskKey];
+ }
+}
+
+- (id <NSURLSessionTaskDelegate>)NSURLSessionTaskDelegateForTask:(NSURLSessionTask *)task
+{
+ return [_sessionDelegates objectForKey:[NSValue valueWithNonretainedObject:task]];
+}
+
+@end
+
+@implementation CRSessionController (TaskManagement)
+
+- (NSURLSessionTask *)taskWithIdentifier:(NSUInteger)taskIdentifier
+{
+ return [_tasksByIdentifier objectForKey:@(taskIdentifier)];
+}
+
+- (BOOL)hasTaskWithIdentifier:(NSUInteger)taskIdentifier
+{
+ return [_tasksByIdentifier objectForKey:@(taskIdentifier)] != nil;
+}
+
+- (BOOL)hasTasksInGroup:(NSString *)group
+ withState:(NSURLSessionTaskState)state
+{
+ __block BOOL result = NO;
+ typeof(self) __weak weakSelf = self;
+ NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
+ NSArray *groupTasks = weakSelf.groups[group];
+ for (NSURLSessionTask *task in groupTasks) {
+ if (task.state == state) {
+ result = YES;
+ return;
+ }
+ }
+ }];
+
+ [self.serialQueue addOperations:@[op]
+ waitUntilFinished:YES];
+
+ return result;
+}
+
+- (void)suspendTasksInGroup:(NSString *)group
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+ CourierLogInfo(@"Suspend tasks in group : %@", group);
+ NSArray *tasks = [strongSelf.groups valueForKey:group];
+ for (NSURLSessionTask *task in tasks) {
+ [task suspend];
+ }
+ }];
+}
+
+- (void)resumeTasksInGroup:(NSString *)group
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+ CourierLogInfo(@"Resume tasks in group : %@", group);
+ NSArray *tasks = [strongSelf.groups valueForKey:group];
+ for (NSURLSessionTask *task in tasks) {
+ [task resume];
+ }
+ }];
+}
+
+- (void)cancelTasksInGroup:(NSString *)group
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+ CourierLogInfo(@"Canceling tasks in group : %@", group);
+ NSArray *tasks = [strongSelf.groups valueForKey:group];
+ for (NSURLSessionTask *task in tasks) {
+ [task cancel];
+ }
+ }];
+}
+
+- (void)suspendAllTasks
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+ CourierLogInfo(@"Suspend all tasks");
+ for (NSString *groupName in strongSelf.groups.allKeys) {
+ [strongSelf suspendTasksInGroup:groupName];
+ }
+ }];
+}
+
+- (void)resumeAllTasks
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+ CourierLogInfo(@"Resume all tasks");
+ for (NSString *groupName in strongSelf.groups.allKeys) {
+ [strongSelf resumeTasksInGroup:groupName];
+ }
+ }];
+}
+
+- (void)cancelAllTasks
+{
+ typeof(self) __weak weakSelf = self;
+ [self.serialQueue addOperationWithBlock:^{
+ typeof(self) __strong strongSelf = weakSelf;
+ CourierLogInfo(@"Cancel all tasks");
+ for (NSString *groupName in strongSelf.groups.allKeys) {
+ [strongSelf cancelTasksInGroup:groupName];
+ }
+ }];
+}
+
+@end
+
+@implementation CRSessionController (Reachability)
+
+- (BOOL)isInternetReachable
+{
+ return _reachabilityObject.isReachable;
+}
+
+- (void)startReachabilityMonitoring
+{
+ [_reachabilityObject startNotifier];
+}
+
+- (void)stopReachabilityMonitoring
+{
+ [_reachabilityObject stopNotifier];
+}
+
+- (void)setReachabilityStatusChangeBlock:(void (^)(NetworkStatus status))block
+{
+ _reachabilityObject.reachableBlock = ^(Reachability *reachability) {
+ if (block) {
+ block(reachability.currentReachabilityStatus);
+ }
+ };
+
+ _reachabilityObject.unreachableBlock = ^(Reachability *reachability) {
+ if (block) {
+ block(reachability.currentReachabilityStatus);
+ }
+ };
+}
+
+@end
+
+@implementation CRSessionController (Debug)
+
+- (void)logRequests
+{
+ CourierLogInfo(@"Log current tasks:");
+ NSArray *tasks = _tasksByIdentifier.allValues;
+ for (NSURLSessionTask *task in tasks) {
+ __unused NSString *description = [NSString stringWithFormat:@"task: %@\n URL: %@\nMethod: %@",
+ task,
+ task.currentRequest.URL,
+ task.currentRequest.HTTPMethod];
+ CourierLogInfo(@"%@", description);
+ }
+}
+
+@end
diff --git a/Courier/CRSessionDelegate.h b/Courier/CRSessionDelegate.h
new file mode 100644
index 0000000..184e42b
--- /dev/null
+++ b/Courier/CRSessionDelegate.h
@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+@class CRSessionController;
+
+@interface CRSessionDelegate : NSObject <NSURLSessionDelegate,
+ NSURLSessionTaskDelegate>
+
++ (instancetype)sessionDelegateWithSessionController:(CRSessionController *)sessionController;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
diff --git a/Courier/CRSessionDelegate.m b/Courier/CRSessionDelegate.m
new file mode 100644
index 0000000..072b572
--- /dev/null
+++ b/Courier/CRSessionDelegate.m
@@ -0,0 +1,67 @@
+#import "CRSessionDelegate.h"
+
+#import "CRSessionController.h"
+
+@interface CRSessionDelegate ()
+
+@property (nonatomic, weak) CRSessionController *sessionController;
+
+@end
+
+@implementation CRSessionDelegate
+
+- (instancetype)initWithSessionController:(CRSessionController *)sessionController
+{
+ self = [super init];
+ if (self) {
+ _sessionController = sessionController;
+ }
+ return self;
+}
+
++ (instancetype)sessionDelegateWithSessionController:(CRSessionController *)sessionController
+{
+ CRSessionDelegate *delegate = [[self alloc] initWithSessionController:sessionController];
+ return delegate;
+}
+
+#pragma mark - NSURLSessionTaskDelegate
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+willPerformHTTPRedirection:(NSHTTPURLResponse *)response
+ newRequest:(NSURLRequest *)request
+ completionHandler:(void (^)(NSURLRequest *))completionHandler
+{
+ id delegate = [self.sessionController NSURLSessionTaskDelegateForTask:task];
+ if ([delegate respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
+ [delegate URLSession:session
+ task:task
+ willPerformHTTPRedirection:response
+ newRequest:request
+ completionHandler:completionHandler];
+ } else {
+ if (completionHandler) completionHandler(request);
+ }
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
+ completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition,
+ NSURLCredential *))completionHandler
+{
+ id delegate = [self.sessionController NSURLSessionTaskDelegateForTask:task];
+ if ([delegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
+ [delegate URLSession:session
+ task:task
+ didReceiveChallenge:challenge
+ completionHandler:completionHandler];
+ } else {
+ if (completionHandler) {
+ completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
+ }
+ }
+}
+
+@end
diff --git a/Courier/Courier.h b/Courier/Courier.h
new file mode 100644
index 0000000..e7e4cef
--- /dev/null
+++ b/Courier/Courier.h
@@ -0,0 +1,37 @@
+//
+// CourierSession.h
+// Courier
+//
+// Created by Andrew Smith on 9/22/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+//
+
+#ifndef Courier_CourierSession_h
+#define Courier_CourierSession_h
+
+#import "NSString+Courier.h"
+#import "NSMutableURLRequest+Courier.h"
+#import "NSURLResponse+Courier.h"
+#import "NSDictionary+Courier.h"
+#import "CRSessionController.h"
+
+#endif
+
diff --git a/Courier/CourierLog.h b/Courier/CourierLog.h
new file mode 100644
index 0000000..8d5a63a
--- /dev/null
+++ b/Courier/CourierLog.h
@@ -0,0 +1,22 @@
+//
+// CourierLog.h
+// Courier
+//
+// Created by Andrew Smith on 2/3/14.
+// Copyright (c) 2014 Andrew B. Smith. All rights reserved.
+//
+
+#ifndef Courier_CourierLog_h
+#define Courier_CourierLog_h
+
+#define COURIER_LOG 0
+
+#if DEBUG && COURIER_LOG
+ #define CourierLogInfo(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
+ #define CourierLogWarning(...) NSLog(@"\n!!!!\n%s %@\n!!!!\n", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
+#else
+ #define CourierLogInfo(...) do { } while (0)
+ #define CourierLogWarning(...) do { } while (0)
+#endif
+
+#endif
diff --git a/Courier/NSDictionary+Courier.h b/Courier/NSDictionary+Courier.h
new file mode 100644
index 0000000..e0a06de
--- /dev/null
+++ b/Courier/NSDictionary+Courier.h
@@ -0,0 +1,50 @@
+//
+// NSDictionary+Courier.h
+// Courier
+//
+// Created by Andrew Smith on 2/28/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSDictionary (Courier)
+
+/**
+ * NSUTF8StringEncoding encoded JSON string
+ */
+- (NSString *)cou_asJSONString;
+
+/**
+ * NSJSONSerialization representation
+ */
+- (NSData *)cou_asJSONData;
+
+/**
+ * & joined form URL encoded string as NSData
+ */
+- (NSData *)cou_asFormURLEncodedData;
+
+/**
+ * Returns URL formated query string, "key1=value1&key2=value2..."
+ */
+- (NSString *)cou_asFormURLEncodedString;
+
+@end
diff --git a/Courier/NSDictionary+Courier.m b/Courier/NSDictionary+Courier.m
new file mode 100644
index 0000000..a4bc096
--- /dev/null
+++ b/Courier/NSDictionary+Courier.m
@@ -0,0 +1,76 @@
+//
+// NSDictionary+Courier.m
+// Courier
+//
+// Created by Andrew Smith on 2/28/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSDictionary+Courier.h"
+#import "NSString+Courier.h"
+
+@implementation NSDictionary (Courier)
+
+#pragma mark - application/json
+
+- (NSString *)cou_asJSONString
+{
+ NSData *jsonData = [self cou_asJSONData];
+ if (!jsonData) return nil;
+ return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+}
+
+- (NSData *)cou_asJSONData
+{
+ NSError *error;
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self
+ options:0
+ error:&error];
+
+ if (!jsonData) NSLog(@"JSON serialization error: %@", error);
+
+ return jsonData;
+}
+
+#pragma mark - application/x-www-form-urlencoded
+
+- (NSString *)cou_asFormURLEncodedString
+{
+ __block NSMutableArray *mutableParameterComponents = [NSMutableArray array];
+
+ [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
+ NSString *encodedKey = [[key description] cou_urlEncodedStringWithEncoding:NSUTF8StringEncoding];
+ NSString *encodedValue = [[obj description] cou_urlEncodedStringWithEncoding:NSUTF8StringEncoding];
+ NSString *component = [NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue];
+ [mutableParameterComponents addObject:component];
+ }];
+
+ NSString *andJoinedString = [mutableParameterComponents componentsJoinedByString:@"&"];
+
+ return andJoinedString;
+}
+
+- (NSData *)cou_asFormURLEncodedData
+{
+ NSString *andJoinedString = [self cou_asFormURLEncodedString];
+ return [andJoinedString dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+@end
diff --git a/Courier/NSMutableURLRequest+Courier.h b/Courier/NSMutableURLRequest+Courier.h
new file mode 100644
index 0000000..cd32168
--- /dev/null
+++ b/Courier/NSMutableURLRequest+Courier.h
@@ -0,0 +1,53 @@
+//
+// NSMutableURLRequest+Courier.h
+// Courier
+//
+// Created by Andrew Smith on 7/25/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef enum {
+ CRURLRequestEncodingUnknown,
+ CRURLFormURLParameterEncoding,
+ CRURLJSONParameterEncoding,
+ CRURLImageEncoding,
+} CRURLRequestEncoding;
+
+@interface NSMutableURLRequest (Courier)
+
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+ path:(NSString *)path;
+
+/**
+
+ @note This will set the Content-Type header to the appropriate type, unless you
+ specify your own.
+
+ */
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+ path:(NSString *)path
+ encoding:(CRURLRequestEncoding)encoding
+ URLParameters:(NSDictionary *)urlParameters
+ HTTPBodyParameters:(NSDictionary *)httpBodyParameters
+ header:(NSDictionary *)header;
+
+@end
diff --git a/Courier/NSMutableURLRequest+Courier.m b/Courier/NSMutableURLRequest+Courier.m
new file mode 100644
index 0000000..9648716
--- /dev/null
+++ b/Courier/NSMutableURLRequest+Courier.m
@@ -0,0 +1,132 @@
+//
+// NSMutableURLRequest+Courier.m
+// Courier
+//
+// Created by Andrew Smith on 7/25/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSMutableURLRequest+Courier.h"
+
+#import "NSDictionary+Courier.h"
+
+@implementation NSMutableURLRequest (Courier)
+
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+ path:(NSString *)path
+{
+ return [self cou_requestWithMethod:method
+ path:path
+ encoding:CRURLRequestEncodingUnknown
+ URLParameters:nil
+ HTTPBodyParameters:nil
+ header:nil];
+}
+
++ (NSMutableURLRequest *)cou_requestWithMethod:(NSString *)method
+ path:(NSString *)path
+ encoding:(CRURLRequestEncoding)encoding
+ URLParameters:(NSDictionary *)urlParameters
+ HTTPBodyParameters:(NSDictionary *)httpBodyParameters
+ header:(NSDictionary *)header
+{
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
+
+ [request setURLPath:path withParameters:urlParameters];
+ [request setHTTPMethod:method];
+ [request setHTTPBodyDataWithParameters:httpBodyParameters encoding:encoding];
+ [request setAllHTTPHeaderFields:header];
+
+ // Only add content type if there are body params. Play! filters seems to
+ // have an issue with setting Content-Type header but not provide a body.
+ if (httpBodyParameters && httpBodyParameters.count > 0) {
+ [request addHeaderContentTypeForEncoding:encoding];
+ }
+
+ return request;
+}
+
+#pragma mark - URL
+
+- (void)setURLPath:(NSString *)path
+ withParameters:(NSDictionary *)parameters
+{
+ //
+ // Append path with URL params, if present
+ //
+ if (parameters.count > 0) {
+ NSString *stringToAppend = [path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@";
+ NSString *newPath = [path stringByAppendingFormat:stringToAppend, [parameters cou_asFormURLEncodedString]];
+ path = newPath;
+ }
+
+ self.URL = [NSURL URLWithString:path];
+}
+
+#pragma mark - Header
+
+- (void)addHeaderContentTypeForEncoding:(CRURLRequestEncoding)encoding
+{
+ //
+ // Bail if content type is already set
+ //
+ if (self.allHTTPHeaderFields[@"Content-Type"]) return;
+
+ //
+ // Set the content type
+ //
+ if (encoding == CRURLFormURLParameterEncoding) {
+ // Form URL
+ NSString *charset = (__bridge NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
+ NSString *type = [NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset];
+ [self addValue:type forHTTPHeaderField:@"Content-Type"];
+ } else if (encoding == CRURLJSONParameterEncoding) {
+ // JSON
+ [self addValue:@"application/json,text/json,text/javascript" forHTTPHeaderField:@"Content-Type"];
+ } else if (encoding == CRURLImageEncoding) {
+ [self addValue:@"image/tiff,image/jpeg,image/gif,image/png,image/ico,image/x-icon,image/bmp,image/x-bmp,image/x-xbitmap,image/x-win-bitmap"
+ forHTTPHeaderField:@"Content-Type"];
+ }
+}
+
+#pragma mark - Body
+
+- (void)setHTTPBodyDataWithParameters:(NSDictionary *)parameters
+ encoding:(CRURLRequestEncoding)encoding
+{
+ if (!parameters) return;
+
+ NSData *bodyData = nil;
+
+ if (encoding == CRURLFormURLParameterEncoding) {
+ // Form URL
+ bodyData = [parameters cou_asFormURLEncodedData];
+ } else if (encoding == CRURLJSONParameterEncoding) {
+ // JSON
+ bodyData = [parameters cou_asJSONData];
+ } else {
+ // Unknown encoding. Pass it through.
+ bodyData = [NSKeyedArchiver archivedDataWithRootObject:parameters];
+ }
+
+ [self setHTTPBody:bodyData];
+}
+
+@end
diff --git a/Courier/NSString+Courier.h b/Courier/NSString+Courier.h
new file mode 100644
index 0000000..f6eca57
--- /dev/null
+++ b/Courier/NSString+Courier.h
@@ -0,0 +1,35 @@
+//
+// NSString+Courier.h
+// Courier
+//
+// Created by Andrew Smith on 10/19/11.
+// Copyright (c) 2011 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSString (Courier)
+
+- (NSString *)cou_urlEncodedString;
+
+- (NSString *)cou_urlEncodedStringWithEncoding:(NSStringEncoding)encoding;
+
+@end
diff --git a/Courier/NSString+Courier.m b/Courier/NSString+Courier.m
new file mode 100644
index 0000000..cfb3a0e
--- /dev/null
+++ b/Courier/NSString+Courier.m
@@ -0,0 +1,47 @@
+//
+// NSString+Courier.m
+// Courier
+//
+// Created by Andrew Smith on 10/19/11.
+// Copyright (c) 2011 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#import "NSString+Courier.h"
+
+@implementation NSString (Courier)
+
+- (NSString *)cou_urlEncodedString
+{
+ return [self cou_urlEncodedStringWithEncoding:NSUTF8StringEncoding];
+}
+
+// See http://github.com/pokeb/asi-http-request/raw/master/Classes/ASIFormDataRequest.m
+- (NSString *)cou_urlEncodedStringWithEncoding:(NSStringEncoding)encoding
+{
+ NSString *urlEncodedString = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
+ (__bridge CFStringRef)self,
+ NULL, (CFStringRef)@":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`",
+ CFStringConvertNSStringEncodingToEncoding(encoding));
+ return urlEncodedString ? urlEncodedString : @"";
+}
+
+@end
diff --git a/Courier/NSURLResponse+Courier.h b/Courier/NSURLResponse+Courier.h
new file mode 100644
index 0000000..a0dd9e4
--- /dev/null
+++ b/Courier/NSURLResponse+Courier.h
@@ -0,0 +1,41 @@
+//
+// NSURLResponse+Courier.h
+// Courier
+//
+// Created by Andrew Smith on 8/31/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSURLResponse (Courier)
+
+/**
+ @returns The HTTP status code for the response.
+ */
+@property (readonly) NSInteger cou_statusCode;
+
+/**
+ @returns YES if the response was successful, checking the HTTP status code for 2xx.
+ */
+- (BOOL)cou_success;
+
+@end
diff --git a/Courier/NSURLResponse+Courier.m b/Courier/NSURLResponse+Courier.m
new file mode 100644
index 0000000..6162da1
--- /dev/null
+++ b/Courier/NSURLResponse+Courier.m
@@ -0,0 +1,41 @@
+//
+// NSURLResponse+Courier.m
+// Courier
+//
+// Created by Andrew Smith on 8/31/13.
+// Copyright (c) 2013 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "NSURLResponse+Courier.h"
+
+@implementation NSURLResponse (Courier)
+
+- (NSInteger)cou_statusCode
+{
+ return [(NSHTTPURLResponse *)self statusCode];
+}
+
+- (BOOL)cou_success
+{
+ NSInteger statusCode = [(NSHTTPURLResponse *)self statusCode];
+ return (statusCode >= 200 && statusCode < 300);
+}
+
+@end
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6aaf5e1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Andrew B. Smith (http://github.com/Drewsmits)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..87c1302
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+# Courier
+
+Courier is a simple network layer built on NSURLSession and various categories of convenience.
+
+* Easy network activity display management
+* Respond to changes in reachability
+* Respond to 401 unauthorized errors
+* Request logging
+* Easily build NSURLRequests with proper encoding
+
+
+## Getting Started
+
+```objc
+#import <Courier/Courier.h>
+
+// Default config
+NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
+
+// Add additional headers
+config.HTTPAdditionalHeaders = @{@"Accept" : @"application/json"};
+
+// Be a good API citizen and limit to 1 HTTP connection per host
+config.HTTPMaximumConnectionsPerHost = 1;
+
+// Build session controller
+CRSessionController *sessionController = [CRSessionController sessionControllerWithConfiguration:config
+ delegate:controller];
+
+// Build a POST request
+NSMutableURLRequest *request = [NSMutableURLRequest requestWithMethod:@"POST"
+ path:@"https://service.com/api"
+ encoding:CR_URLJSONParameterEncoding
+ URLParameters:@{@"urlParam" : @"value"}
+ HTTPBodyParameters:@{@"bodyParam" : @"value"}
+ header:@{@"Header-Name" : @"value"}];
+
+
+// Build a task
+NSURLSessionDataTask *task = [sessionController dataTaskForRequest:request
+ completionHandler:^(NSData *data,
+ NSURLResponse *response,
+ NSError *error) {
+ if (response.success) {
+ // Hurrah!
+ }
+ }];
+
+// Start task
+[task resume];
+
+```
+
+## Externals
+
+* [Reachability library](https://github.com/tonymillion/Reachability) was created and maintained by [Tony Million](https://github.com/tonymillion).