{"action":"created","comment":{"url":"https://api.github.com/repos/box/box-ios-sdk/pulls/comments/105067257","pull_request_review_id":25928948,"id":105067257,"diff_hunk":"@@ -0,0 +1,378 @@\n+//\n+//  BOXURLSessionCacheClient.m\n+//  BoxContentSDK\n+//\n+//  Created by Thuy Nguyen on 3/3/17.\n+//  Copyright © 2017 Box. All rights reserved.\n+//\n+\n+#import \"BOXURLSessionCacheClient.h\"\n+#import \"BOXContentSDKConstants.h\"\n+#import \"BOXLog.h\"\n+#import \"BOXContentSDKErrors.h\"\n+\n+@interface BOXURLBackgroundSessionIdAndSessionTaskId : NSObject\n+\n+@property (nonatomic, copy, readwrite) NSString *backgroundSessionId;\n+@property (nonatomic, assign, readwrite) NSUInteger sessionTaskId;\n+\n+@end\n+\n+@interface BOXURLSessionCacheClient()\n+\n+@property (nonatomic, copy, readwrite) NSString *cacheDir;\n+\n+@end\n+\n+@implementation BOXURLSessionTaskCachedInfo\n+\n+@end\n+\n+@implementation BOXURLSessionCacheClient\n+\n+- (id)initWithCacheRootDir:(NSString *)cacheRootDir\n+{\n+    self = [super init];\n+    if (self != nil) {\n+        BOOL isDir = NO;\n+        if (cacheRootDir != nil) {\n+            NSString *cacheDir = [cacheRootDir stringByAppendingPathComponent:BOXURLSessionTaskCacheDirectoryName];\n+            if ([[NSFileManager defaultManager] fileExistsAtPath:cacheDir isDirectory:&isDir] == NO || isDir == NO) {\n+                NSError *error = nil;\n+                [[NSFileManager defaultManager] createDirectoryAtPath:cacheDir withIntermediateDirectories:YES attributes:nil error:&error];\n+                BOXAssert(error != nil, @\"Failed to create cacheDir %@ with error %@\", cacheDir, error);\n+            }\n+            self.cacheDir = cacheDir;\n+        }\n+    }\n+    return self;\n+}\n+\n+- (BOOL)cacheUserId:(NSString *)userId associateId:(NSString *)associateId backgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId error:(NSError **)error\n+{\n+    if (userId == nil || associateId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidUserIdOrAssociateId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+    if (backgroundSessionId == nil || sessionTaskId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidBackgroundSessionIdOrSessionTaskId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+    //persist users/$userId/$associateId/$backgroundSessionId-$sessionTaskId\n+    BOOL success = [self createFileForUserId:userId associateId:associateId backgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId error:error];\n+\n+    if (success == YES) {\n+        //persist sessions/$backgroundSessionId/$sessionTaskId\n+        success = [self createDirForBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId error:error];\n+    }\n+\n+    return success;\n+}\n+\n+- (BOOL)cacheBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId destinationFilePath:(NSString *)destinationFilePath error:(NSError **)error\n+{\n+    if (backgroundSessionId == nil || sessionTaskId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidBackgroundSessionIdOrSessionTaskId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+    NSData *data = [destinationFilePath dataUsingEncoding:NSUTF8StringEncoding];\n+    return [self cacheBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId data:data type:BOXURLSessionTaskCacheFileTypeDestinationFilePath error:error];\n+}\n+\n+- (BOOL)cacheBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId responseData:(NSData *)responseData error:(NSError **)error\n+{\n+    if (backgroundSessionId == nil || sessionTaskId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidBackgroundSessionIdOrSessionTaskId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+    return [self cacheBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId data:responseData type:BOXURLSessionTaskCacheFileTypeResponseData error:error];\n+}\n+\n+- (BOOL)cacheBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId resumeData:(NSData *)resumeData error:(NSError **)error\n+{\n+    if (backgroundSessionId == nil || sessionTaskId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidBackgroundSessionIdOrSessionTaskId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+    return [self cacheBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId data:resumeData type:BOXURLSessionTaskCacheFileTypeResumeData error:error];\n+}\n+\n+- (BOOL)cacheBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId response:(NSURLResponse *)response error:(NSError **)error\n+{\n+    if (backgroundSessionId == nil || sessionTaskId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidBackgroundSessionIdOrSessionTaskId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:response];\n+    return [self cacheBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId data:data type:BOXURLSessionTaskCacheFileTypeResponse error:error];\n+}\n+\n+- (BOOL)cacheBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId taskError:(NSData *)taskError error:(NSError **)error\n+{\n+    if (backgroundSessionId == nil || sessionTaskId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidBackgroundSessionIdOrSessionTaskId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:taskError];\n+    return [self cacheBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId data:data type:BOXURLSessionTaskCacheFileTypeError error:error];\n+}\n+\n+- (BOXURLSessionTaskCachedInfo *)cachedInfoForUserId:(NSString *)userId associateId:(NSString *)associateId error:(NSError **)error\n+{\n+    if (userId == nil || associateId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidUserIdOrAssociateId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+\n+    NSError *err;\n+    BOXURLBackgroundSessionIdAndSessionTaskId *backgroundSessionIdAndSessionTaskId = [self backgroundSessionIdAndSessionTaskIdGivenUserId:userId associateId:associateId error:&err];\n+\n+    if (backgroundSessionIdAndSessionTaskId == nil) {\n+        if (error != nil) {\n+            if (err != nil) {\n+                *error = err;\n+            } else {\n+                *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidSessionForUserId userInfo:nil];\n+            }\n+        }\n+        return nil;\n+    }\n+\n+    NSString *backgroundSessionId = backgroundSessionIdAndSessionTaskId.backgroundSessionId;\n+    NSUInteger sessionTaskId = backgroundSessionIdAndSessionTaskId.sessionTaskId;\n+\n+    NSString *dir = [self dirPathOfSessionTaskWithBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId];\n+\n+    BOXURLSessionTaskCachedInfo *cachedInfo = [[BOXURLSessionTaskCachedInfo alloc] init];\n+\n+    //get all files under sessions/$backgroundSessionId/$sessionTaskId\n+    NSArray *filePaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:error];\n+    for (int i = 0; i < filePaths.count; i++) {\n+        NSString *fileName = filePaths[i];\n+        NSString *filePath = [dir stringByAppendingPathComponent:fileName];\n+\n+        //decrypt data found at filePath\n+        NSData *data = [NSData dataWithContentsOfFile:filePath];\n+        if ([self.delegate respondsToSelector:@selector(decryptData:)]) {\n+            data = [self.delegate decryptData:data];\n+        }\n+\n+        //parse decrypted data based on its file name\n+        if ([fileName isEqualToString:BOXURLSessionTaskCacheDestinationFilePath]) {\n+            cachedInfo.destinationFilePath = [NSKeyedUnarchiver unarchiveObjectWithData:data];\n+        } else if ([fileName isEqualToString:BOXURLSessionTaskCacheResumeData]) {\n+            cachedInfo.resumeData = data;\n+        } else if ([fileName isEqualToString:BOXURLSessionTaskCacheResponse]) {\n+            cachedInfo.response = [NSKeyedUnarchiver unarchiveObjectWithData:data];\n+        } else if ([fileName isEqualToString:BOXURLSessionTaskCacheResponseData]) {\n+            cachedInfo.responseData = data;\n+        } else if ([fileName isEqualToString:BOXURLSessionTaskCacheError]) {\n+            cachedInfo.error = [NSKeyedUnarchiver unarchiveObjectWithData:data];\n+        }\n+    }\n+    return cachedInfo;\n+}\n+\n+- (BOOL)deleteCachedInfoForUserId:(NSString *)userId associateId:(NSString *)associateId error:(NSError **)error\n+{\n+    if (userId == nil || associateId == nil) {\n+        if (error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorNoValidUserIdOrAssociateId userInfo:nil];\n+        }\n+        return NO;\n+    }\n+\n+    NSError *err = nil;\n+    BOXURLBackgroundSessionIdAndSessionTaskId *backgroundSessionIdAndSessionTaskId = [self backgroundSessionIdAndSessionTaskIdGivenUserId:userId associateId:associateId error:&err];\n+    if (backgroundSessionIdAndSessionTaskId == nil) {\n+        if (err != nil) {\n+            if (error != nil) {\n+                *error = err;\n+            }\n+            return NO;\n+        }\n+        return YES;\n+    }\n+\n+    NSString *backgroundSessionId = backgroundSessionIdAndSessionTaskId.backgroundSessionId;\n+    NSUInteger sessionTaskId = backgroundSessionIdAndSessionTaskId.sessionTaskId;\n+\n+    //clean up sessions/$backgroundSessionId/$sessionTaskId/*\n+    NSString *dir = [self dirPathOfSessionTaskWithBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId];\n+    BOOL success = [self deleteDirectory:dir error:error];\n+\n+    if (success == YES) {\n+        //clean up users/$userId/$associateId/*\n+        dir = [self dirPathOfUserSessionTaskGivenUserId:userId associateId:associateId];\n+        success = [self deleteDirectory:dir error:error];\n+    }\n+    return success;\n+}\n+\n+#pragma mark - private helpers\n+\n+- (BOOL)cacheBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId data:(NSData *)data type:(BOXURLSessionTaskCacheFileType)type error:(NSError **)outError\n+{\n+    NSError *error;\n+\n+    //persist sessions/$backgroundSessionId/$sessionTaskId\n+    BOOL success = [self createDirForBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId error:&error];\n+\n+    if (success == YES && error == nil) {\n+        NSString *path = [self dirPathOfSessionTaskWithBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId];\n+        //persist data to sessions/$backgroundSessionId/$sessionTaskId/$fileType\n+\n+        switch (type) {\n+            case BOXURLSessionTaskCacheFileTypeDestinationFilePath:\n+                path = [path stringByAppendingPathComponent:BOXURLSessionTaskCacheDestinationFilePath];\n+                break;\n+            case BOXURLSessionTaskCacheFileTypeResumeData:\n+                path = [path stringByAppendingPathComponent:BOXURLSessionTaskCacheResumeData];\n+                break;\n+            case BOXURLSessionTaskCacheFileTypeResponse:\n+                path = [path stringByAppendingPathComponent:BOXURLSessionTaskCacheResponse];\n+                break;\n+            case BOXURLSessionTaskCacheFileTypeResponseData:\n+                path = [path stringByAppendingPathComponent:BOXURLSessionTaskCacheResponseData];\n+                break;\n+            case BOXURLSessionTaskCacheFileTypeError:\n+                path = [path stringByAppendingPathComponent:BOXURLSessionTaskCacheError];\n+                break;\n+        }\n+\n+        success = [self cacheAndAttemptToEncryptData:data atPath:path error:&error];\n+    }\n+\n+    if (outError != nil) {\n+        (*outError) = error;\n+    }\n+    return success;\n+}\n+\n+- (BOOL)cacheAndAttemptToEncryptData:(NSData *)data atPath:(NSString *)path error:(NSError **)outError\n+{\n+    NSData *finalData = data;\n+    if ([self.delegate respondsToSelector:@selector(encryptData:)]) {\n+        finalData = [self.delegate encryptData:data];\n+    }\n+    return [finalData writeToFile:path options:NSDataWritingWithoutOverwriting error:outError];\n+}\n+\n+// Create dir if not exists, users/$userId/$associateId/$backgroundSessionId-$sessionTaskId\n+- (BOOL)createFileForUserId:(NSString *)userId associateId:(NSString *)associateId backgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId error:(NSError **)error\n+{\n+    NSString *path = [self filePathOfUserSessionTaskGivenUserId:userId associateId:associateId backgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId];\n+    return [self createFile:path error:error];\n+}\n+\n+// Create dir if not exists, sessions/$backgroundSessionId/$sessionTaskId\n+- (BOOL)createDirForBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId error:(NSError **)error\n+{\n+    NSString *path = [self dirPathOfSessionTaskWithBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId];\n+    return [self createDirectory:path error:error];\n+}\n+\n+- (BOOL)createDirectory:(NSString *)path error:(NSError **)error\n+{\n+    BOOL isDir = NO;\n+    BOOL success = YES;\n+    if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir] == NO || isDir == NO) {\n+        success = [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:error];\n+    }\n+    return success;\n+}\n+\n+- (BOOL)deleteDirectory:(NSString *)directoryPath error:(NSError **)error\n+{\n+    BOOL isDir = NO;\n+    BOOL success = YES;\n+    if ([[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:&isDir] == YES && isDir == YES) {\n+        success = [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:error];\n+    }\n+    return success;\n+}\n+\n+- (BOOL)createFile:(NSString *)path error:(NSError **)error\n+{\n+    NSString *dir = [path stringByDeletingLastPathComponent];\n+    BOOL success = [self createDirectory:dir error:error];\n+    if (success == YES) {\n+        success = [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];\n+        if (success == NO && error != nil) {\n+            *error = [[NSError alloc] initWithDomain:BOXURLSessionTaskCacheError code:BOXContentSDKURLSessionCacheErrorFileCreateFailed userInfo:nil];\n+        }\n+    }\n+    return success;\n+}\n+\n+// Return dir path sessions/$backgroundSessionId/$sessionTaskId\n+- (NSString *)dirPathOfSessionTaskWithBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId\n+{\n+    return [[[self.cacheDir stringByAppendingPathComponent:BOXURLSessionTaskCacheSessionsDirectoryName] stringByAppendingPathComponent:backgroundSessionId] stringByAppendingPathComponent:[NSString stringWithFormat:@\"%lu\", (unsigned long)sessionTaskId]];\n+}\n+\n+// Return dir path users/$userId/$associateId/$backgroundSessionId-$sessionTaskId\n+- (NSString *)dirPathOfUserSessionTaskGivenUserId:(NSString *)userId associateId:(NSString *)associateId\n+{\n+    return [[[self.cacheDir stringByAppendingPathComponent:BOXURLSessionTaskCacheSessionsDirectoryName] stringByAppendingPathComponent:userId] stringByAppendingPathComponent:associateId];\n+}\n+\n+- (NSString *)filePathOfUserSessionTaskGivenUserId:(NSString *)userId associateId:(NSString *)associateId backgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId\n+{\n+    NSString *path = [self dirPathOfUserSessionTaskGivenUserId:userId associateId:associateId];\n+    NSString *fileName = [self fileNameGivenBackgroundSessionId:backgroundSessionId sessionTaskId:sessionTaskId];\n+    return [path stringByAppendingPathComponent:fileName];\n+}\n+\n+- (NSString *)fileNameGivenBackgroundSessionId:(NSString *)backgroundSessionId sessionTaskId:(NSUInteger)sessionTaskId\n+{\n+    return [NSString stringWithFormat:@\"%@-%lu\", backgroundSessionId, (unsigned long)sessionTaskId];\n+}\n+\n+- (BOXURLBackgroundSessionIdAndSessionTaskId *)parseBackgroundSessionIdAndSessionTaskIdFileName:(NSString *)name\n+{\n+    NSArray *arr = [name componentsSeparatedByString:@\"-\"];\n+    BOXURLBackgroundSessionIdAndSessionTaskId *res = nil;\n+    if (arr.count == 2) {\n+        res.backgroundSessionId = arr[0];\n+        res.sessionTaskId = [arr[1] unsignedIntegerValue];\n+    }\n+    return res;\n+}\n+\n+- (BOXURLBackgroundSessionIdAndSessionTaskId *)backgroundSessionIdAndSessionTaskIdGivenUserId:(NSString *)userId associateId:(NSString *)associateId error:(NSError **)error\n+{\n+    NSString *dir = [self dirPathOfUserSessionTaskGivenUserId:userId associateId:associateId];\n+    NSError *err;","path":"BoxContentSDK/BoxContentSDK/BOXURLSessionCacheClient.m","position":362,"original_position":362,"commit_id":"bedc0c105bad8ebeeba719a0aac0e15e33a89526","original_commit_id":"bedc0c105bad8ebeeba719a0aac0e15e33a89526","user":{"login":"boxtcarpel","id":1891864,"avatar_url":"https://avatars3.githubusercontent.com/u/1891864?v=3","gravatar_id":"","url":"https://api.github.com/users/boxtcarpel","html_url":"https://github.com/boxtcarpel","followers_url":"https://api.github.com/users/boxtcarpel/followers","following_url":"https://api.github.com/users/boxtcarpel/following{/other_user}","gists_url":"https://api.github.com/users/boxtcarpel/gists{/gist_id}","starred_url":"https://api.github.com/users/boxtcarpel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boxtcarpel/subscriptions","organizations_url":"https://api.github.com/users/boxtcarpel/orgs","repos_url":"https://api.github.com/users/boxtcarpel/repos","events_url":"https://api.github.com/users/boxtcarpel/events{/privacy}","received_events_url":"https://api.github.com/users/boxtcarpel/received_events","type":"User","site_admin":false},"body":"minor: initialize value","created_at":"2017-03-09T01:18:51Z","updated_at":"2017-03-09T01:18:51Z","html_url":"https://github.com/box/box-ios-sdk/pull/339#discussion_r105067257","pull_request_url":"https://api.github.com/repos/box/box-ios-sdk/pulls/339","_links":{"self":{"href":"https://api.github.com/repos/box/box-ios-sdk/pulls/comments/105067257"},"html":{"href":"https://github.com/box/box-ios-sdk/pull/339#discussion_r105067257"},"pull_request":{"href":"https://api.github.com/repos/box/box-ios-sdk/pulls/339"}}},"pull_request":{"url":"https://api.github.com/repos/box/box-ios-sdk/pulls/339","id":109636072,"html_url":"https://github.com/box/box-ios-sdk/pull/339","diff_url":"https://github.com/box/box-ios-sdk/pull/339.diff","patch_url":"https://github.com/box/box-ios-sdk/pull/339.patch","issue_url":"https://api.github.com/repos/box/box-ios-sdk/issues/339","number":339,"state":"open","locked":false,"title":"add BOXURLSessionCacheClient to persist data for background download/…","user":{"login":"thuyBox","id":10490103,"avatar_url":"https://avatars1.githubusercontent.com/u/10490103?v=3","gravatar_id":"","url":"https://api.github.com/users/thuyBox","html_url":"https://github.com/thuyBox","followers_url":"https://api.github.com/users/thuyBox/followers","following_url":"https://api.github.com/users/thuyBox/following{/other_user}","gists_url":"https://api.github.com/users/thuyBox/gists{/gist_id}","starred_url":"https://api.github.com/users/thuyBox/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/thuyBox/subscriptions","organizations_url":"https://api.github.com/users/thuyBox/orgs","repos_url":"https://api.github.com/users/thuyBox/repos","events_url":"https://api.github.com/users/thuyBox/events{/privacy}","received_events_url":"https://api.github.com/users/thuyBox/received_events","type":"User","site_admin":false},"body":"…upload tasks to assist reconnection","created_at":"2017-03-08T06:27:35Z","updated_at":"2017-03-09T01:18:51Z","closed_at":null,"merged_at":null,"merge_commit_sha":"23d0bca0db46d82f785ff7efc7f233a9a5d6b0e9","assignee":{"login":"irvingruan","id":335173,"avatar_url":"https://avatars2.githubusercontent.com/u/335173?v=3","gravatar_id":"","url":"https://api.github.com/users/irvingruan","html_url":"https://github.com/irvingruan","followers_url":"https://api.github.com/users/irvingruan/followers","following_url":"https://api.github.com/users/irvingruan/following{/other_user}","gists_url":"https://api.github.com/users/irvingruan/gists{/gist_id}","starred_url":"https://api.github.com/users/irvingruan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/irvingruan/subscriptions","organizations_url":"https://api.github.com/users/irvingruan/orgs","repos_url":"https://api.github.com/users/irvingruan/repos","events_url":"https://api.github.com/users/irvingruan/events{/privacy}","received_events_url":"https://api.github.com/users/irvingruan/received_events","type":"User","site_admin":false},"assignees":[{"login":"irvingruan","id":335173,"avatar_url":"https://avatars2.githubusercontent.com/u/335173?v=3","gravatar_id":"","url":"https://api.github.com/users/irvingruan","html_url":"https://github.com/irvingruan","followers_url":"https://api.github.com/users/irvingruan/followers","following_url":"https://api.github.com/users/irvingruan/following{/other_user}","gists_url":"https://api.github.com/users/irvingruan/gists{/gist_id}","starred_url":"https://api.github.com/users/irvingruan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/irvingruan/subscriptions","organizations_url":"https://api.github.com/users/irvingruan/orgs","repos_url":"https://api.github.com/users/irvingruan/repos","events_url":"https://api.github.com/users/irvingruan/events{/privacy}","received_events_url":"https://api.github.com/users/irvingruan/received_events","type":"User","site_admin":false},{"login":"boxtcarpel","id":1891864,"avatar_url":"https://avatars3.githubusercontent.com/u/1891864?v=3","gravatar_id":"","url":"https://api.github.com/users/boxtcarpel","html_url":"https://github.com/boxtcarpel","followers_url":"https://api.github.com/users/boxtcarpel/followers","following_url":"https://api.github.com/users/boxtcarpel/following{/other_user}","gists_url":"https://api.github.com/users/boxtcarpel/gists{/gist_id}","starred_url":"https://api.github.com/users/boxtcarpel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/boxtcarpel/subscriptions","organizations_url":"https://api.github.com/users/boxtcarpel/orgs","repos_url":"https://api.github.com/users/boxtcarpel/repos","events_url":"https://api.github.com/users/boxtcarpel/events{/privacy}","received_events_url":"https://api.github.com/users/boxtcarpel/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/box/box-ios-sdk/pulls/339/commits","review_comments_url":"https://api.github.com/repos/box/box-ios-sdk/pulls/339/comments","review_comment_url":"https://api.github.com/repos/box/box-ios-sdk/pulls/comments{/number}","comments_url":"https://api.github.com/repos/box/box-ios-sdk/issues/339/comments","statuses_url":"https://api.github.com/repos/box/box-ios-sdk/statuses/bedc0c105bad8ebeeba719a0aac0e15e33a89526","head":{"label":"box:nsurlsession.addCacheClient","ref":"nsurlsession.addCacheClient","sha":"bedc0c105bad8ebeeba719a0aac0e15e33a89526","user":{"login":"box","id":23900,"avatar_url":"https://avatars3.githubusercontent.com/u/23900?v=3","gravatar_id":"","url":"https://api.github.com/users/box","html_url":"https://github.com/box","followers_url":"https://api.github.com/users/box/followers","following_url":"https://api.github.com/users/box/following{/other_user}","gists_url":"https://api.github.com/users/box/gists{/gist_id}","starred_url":"https://api.github.com/users/box/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/box/subscriptions","organizations_url":"https://api.github.com/users/box/orgs","repos_url":"https://api.github.com/users/box/repos","events_url":"https://api.github.com/users/box/events{/privacy}","received_events_url":"https://api.github.com/users/box/received_events","type":"Organization","site_admin":false},"repo":{"id":33955225,"name":"box-ios-sdk","full_name":"box/box-ios-sdk","owner":{"login":"box","id":23900,"avatar_url":"https://avatars3.githubusercontent.com/u/23900?v=3","gravatar_id":"","url":"https://api.github.com/users/box","html_url":"https://github.com/box","followers_url":"https://api.github.com/users/box/followers","following_url":"https://api.github.com/users/box/following{/other_user}","gists_url":"https://api.github.com/users/box/gists{/gist_id}","starred_url":"https://api.github.com/users/box/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/box/subscriptions","organizations_url":"https://api.github.com/users/box/orgs","repos_url":"https://api.github.com/users/box/repos","events_url":"https://api.github.com/users/box/events{/privacy}","received_events_url":"https://api.github.com/users/box/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/box/box-ios-sdk","description":"iOS SDK for the Box Content API","fork":false,"url":"https://api.github.com/repos/box/box-ios-sdk","forks_url":"https://api.github.com/repos/box/box-ios-sdk/forks","keys_url":"https://api.github.com/repos/box/box-ios-sdk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/box/box-ios-sdk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/box/box-ios-sdk/teams","hooks_url":"https://api.github.com/repos/box/box-ios-sdk/hooks","issue_events_url":"https://api.github.com/repos/box/box-ios-sdk/issues/events{/number}","events_url":"https://api.github.com/repos/box/box-ios-sdk/events","assignees_url":"https://api.github.com/repos/box/box-ios-sdk/assignees{/user}","branches_url":"https://api.github.com/repos/box/box-ios-sdk/branches{/branch}","tags_url":"https://api.github.com/repos/box/box-ios-sdk/tags","blobs_url":"https://api.github.com/repos/box/box-ios-sdk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/box/box-ios-sdk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/box/box-ios-sdk/git/refs{/sha}","trees_url":"https://api.github.com/repos/box/box-ios-sdk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/box/box-ios-sdk/statuses/{sha}","languages_url":"https://api.github.com/repos/box/box-ios-sdk/languages","stargazers_url":"https://api.github.com/repos/box/box-ios-sdk/stargazers","contributors_url":"https://api.github.com/repos/box/box-ios-sdk/contributors","subscribers_url":"https://api.github.com/repos/box/box-ios-sdk/subscribers","subscription_url":"https://api.github.com/repos/box/box-ios-sdk/subscription","commits_url":"https://api.github.com/repos/box/box-ios-sdk/commits{/sha}","git_commits_url":"https://api.github.com/repos/box/box-ios-sdk/git/commits{/sha}","comments_url":"https://api.github.com/repos/box/box-ios-sdk/comments{/number}","issue_comment_url":"https://api.github.com/repos/box/box-ios-sdk/issues/comments{/number}","contents_url":"https://api.github.com/repos/box/box-ios-sdk/contents/{+path}","compare_url":"https://api.github.com/repos/box/box-ios-sdk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/box/box-ios-sdk/merges","archive_url":"https://api.github.com/repos/box/box-ios-sdk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/box/box-ios-sdk/downloads","issues_url":"https://api.github.com/repos/box/box-ios-sdk/issues{/number}","pulls_url":"https://api.github.com/repos/box/box-ios-sdk/pulls{/number}","milestones_url":"https://api.github.com/repos/box/box-ios-sdk/milestones{/number}","notifications_url":"https://api.github.com/repos/box/box-ios-sdk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/box/box-ios-sdk/labels{/name}","releases_url":"https://api.github.com/repos/box/box-ios-sdk/releases{/id}","deployments_url":"https://api.github.com/repos/box/box-ios-sdk/deployments","created_at":"2015-04-14T20:26:08Z","updated_at":"2017-03-03T06:58:30Z","pushed_at":"2017-03-09T00:55:28Z","git_url":"git://github.com/box/box-ios-sdk.git","ssh_url":"git@github.com:box/box-ios-sdk.git","clone_url":"https://github.com/box/box-ios-sdk.git","svn_url":"https://github.com/box/box-ios-sdk","homepage":"","size":13896,"stargazers_count":41,"watchers_count":41,"language":"Objective-C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":50,"mirror_url":null,"open_issues_count":10,"forks":50,"open_issues":10,"watchers":41,"default_branch":"master"}},"base":{"label":"box:nsurlsession","ref":"nsurlsession","sha":"912da4029a465b05aa3ae092f582102ba6cce627","user":{"login":"box","id":23900,"avatar_url":"https://avatars3.githubusercontent.com/u/23900?v=3","gravatar_id":"","url":"https://api.github.com/users/box","html_url":"https://github.com/box","followers_url":"https://api.github.com/users/box/followers","following_url":"https://api.github.com/users/box/following{/other_user}","gists_url":"https://api.github.com/users/box/gists{/gist_id}","starred_url":"https://api.github.com/users/box/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/box/subscriptions","organizations_url":"https://api.github.com/users/box/orgs","repos_url":"https://api.github.com/users/box/repos","events_url":"https://api.github.com/users/box/events{/privacy}","received_events_url":"https://api.github.com/users/box/received_events","type":"Organization","site_admin":false},"repo":{"id":33955225,"name":"box-ios-sdk","full_name":"box/box-ios-sdk","owner":{"login":"box","id":23900,"avatar_url":"https://avatars3.githubusercontent.com/u/23900?v=3","gravatar_id":"","url":"https://api.github.com/users/box","html_url":"https://github.com/box","followers_url":"https://api.github.com/users/box/followers","following_url":"https://api.github.com/users/box/following{/other_user}","gists_url":"https://api.github.com/users/box/gists{/gist_id}","starred_url":"https://api.github.com/users/box/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/box/subscriptions","organizations_url":"https://api.github.com/users/box/orgs","repos_url":"https://api.github.com/users/box/repos","events_url":"https://api.github.com/users/box/events{/privacy}","received_events_url":"https://api.github.com/users/box/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/box/box-ios-sdk","description":"iOS SDK for the Box Content API","fork":false,"url":"https://api.github.com/repos/box/box-ios-sdk","forks_url":"https://api.github.com/repos/box/box-ios-sdk/forks","keys_url":"https://api.github.com/repos/box/box-ios-sdk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/box/box-ios-sdk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/box/box-ios-sdk/teams","hooks_url":"https://api.github.com/repos/box/box-ios-sdk/hooks","issue_events_url":"https://api.github.com/repos/box/box-ios-sdk/issues/events{/number}","events_url":"https://api.github.com/repos/box/box-ios-sdk/events","assignees_url":"https://api.github.com/repos/box/box-ios-sdk/assignees{/user}","branches_url":"https://api.github.com/repos/box/box-ios-sdk/branches{/branch}","tags_url":"https://api.github.com/repos/box/box-ios-sdk/tags","blobs_url":"https://api.github.com/repos/box/box-ios-sdk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/box/box-ios-sdk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/box/box-ios-sdk/git/refs{/sha}","trees_url":"https://api.github.com/repos/box/box-ios-sdk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/box/box-ios-sdk/statuses/{sha}","languages_url":"https://api.github.com/repos/box/box-ios-sdk/languages","stargazers_url":"https://api.github.com/repos/box/box-ios-sdk/stargazers","contributors_url":"https://api.github.com/repos/box/box-ios-sdk/contributors","subscribers_url":"https://api.github.com/repos/box/box-ios-sdk/subscribers","subscription_url":"https://api.github.com/repos/box/box-ios-sdk/subscription","commits_url":"https://api.github.com/repos/box/box-ios-sdk/commits{/sha}","git_commits_url":"https://api.github.com/repos/box/box-ios-sdk/git/commits{/sha}","comments_url":"https://api.github.com/repos/box/box-ios-sdk/comments{/number}","issue_comment_url":"https://api.github.com/repos/box/box-ios-sdk/issues/comments{/number}","contents_url":"https://api.github.com/repos/box/box-ios-sdk/contents/{+path}","compare_url":"https://api.github.com/repos/box/box-ios-sdk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/box/box-ios-sdk/merges","archive_url":"https://api.github.com/repos/box/box-ios-sdk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/box/box-ios-sdk/downloads","issues_url":"https://api.github.com/repos/box/box-ios-sdk/issues{/number}","pulls_url":"https://api.github.com/repos/box/box-ios-sdk/pulls{/number}","milestones_url":"https://api.github.com/repos/box/box-ios-sdk/milestones{/number}","notifications_url":"https://api.github.com/repos/box/box-ios-sdk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/box/box-ios-sdk/labels{/name}","releases_url":"https://api.github.com/repos/box/box-ios-sdk/releases{/id}","deployments_url":"https://api.github.com/repos/box/box-ios-sdk/deployments","created_at":"2015-04-14T20:26:08Z","updated_at":"2017-03-03T06:58:30Z","pushed_at":"2017-03-09T00:55:28Z","git_url":"git://github.com/box/box-ios-sdk.git","ssh_url":"git@github.com:box/box-ios-sdk.git","clone_url":"https://github.com/box/box-ios-sdk.git","svn_url":"https://github.com/box/box-ios-sdk","homepage":"","size":13896,"stargazers_count":41,"watchers_count":41,"language":"Objective-C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":50,"mirror_url":null,"open_issues_count":10,"forks":50,"open_issues":10,"watchers":41,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/box/box-ios-sdk/pulls/339"},"html":{"href":"https://github.com/box/box-ios-sdk/pull/339"},"issue":{"href":"https://api.github.com/repos/box/box-ios-sdk/issues/339"},"comments":{"href":"https://api.github.com/repos/box/box-ios-sdk/issues/339/comments"},"review_comments":{"href":"https://api.github.com/repos/box/box-ios-sdk/pulls/339/comments"},"review_comment":{"href":"https://api.github.com/repos/box/box-ios-sdk/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/box/box-ios-sdk/pulls/339/commits"},"statuses":{"href":"https://api.github.com/repos/box/box-ios-sdk/statuses/bedc0c105bad8ebeeba719a0aac0e15e33a89526"}}}}
{"actor":{"display_login":"boxtcarpel"}}