NewsBlur/clients/ios/Other Sources/Overshare Kit/OSKAppDotNetUtility.m
2014-05-19 13:33:42 -07:00

306 lines
16 KiB
Objective-C
Executable file

//
// OSKAppDotNetUtility.m
// Overshare
//
// Created by Jared Sinclair on 10/10/13.
// Copyright (c) 2013 Overshare Kit. All rights reserved.
//
#import "OSKAppDotNetUtility.h"
#import "OSKActivity.h"
#import "OSKManagedAccountCredential.h"
#import "OSKShareableContentItem.h"
#import "OSKApplicationCredential.h"
#import "OSKLogger.h"
#import "NSDictionary+OSKModel.h"
#import "OSKManagedAccount.h"
#import "NSMutableURLRequest+OSKUtilities.h"
#import "NSHTTPURLResponse+OSKUtilities.h"
#import "UIImage+OSKUtilities.h"
static NSString * OSKAppDotNetUtility_BaseURL = @"https://alpha-api.app.net/";
static NSString * OSKAppDotNetUtility_URL_FetchMe = @"stream/0/users/me?access_token=%@";
static NSString * OSKAppDotNetUtility_URL_WriteNewPost = @"stream/0/posts?access_token=%@&include_post_annotations=1";
#define kAccountID @"id"
#define kUsername @"username"
#define kName @"name"
#define kAvatarObject @"avatar_image"
#define kAvatarURL @"url"
@implementation OSKAppDotNetUtility
#pragma mark - Write Post
+ (void)postContentItem:(OSKMicroblogPostContentItem *)item withCredential:(OSKManagedAccountCredential *)credential appCredential:(OSKApplicationCredential *)appCredential completion:(void(^)(BOOL success, NSError *error))completion {
UIBackgroundTaskIdentifier backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
if(completion) {
completion(NO, nil);
}
}];
if (item.images.count) {
[self uploadImages:item.images accountCredential:credential appCredential:appCredential completion:^(NSArray *fileDictionaries, NSError *error) {
[self _postContentItem:item fileAPIDictionaries:fileDictionaries withCredential:credential appCredential:appCredential completion:^(BOOL success, NSError *error) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(success, nil);
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier];
});
}
}];
}];
} else {
[self _postContentItem:item fileAPIDictionaries:nil withCredential:credential appCredential:appCredential completion:^(BOOL success, NSError *error) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(success, nil);
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier];
});
}
}];
}
}
+ (void)_postContentItem:(OSKMicroblogPostContentItem *)item fileAPIDictionaries:(NSArray *)dictionaries withCredential:(OSKManagedAccountCredential *)credential appCredential:(OSKApplicationCredential *)appCredential completion:(void(^)(BOOL success, NSError *error))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *subPath = [NSString stringWithFormat:OSKAppDotNetUtility_URL_WriteNewPost, credential.token];
NSString *fullPath = [NSString stringWithFormat:@"%@%@", OSKAppDotNetUtility_BaseURL, subPath];
NSDictionary *dictionaryRep = [self _dictionaryRepresentationForContentItem:item attachedImageDictionaries:dictionaries];
NSURLSession *sesh = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest osk_requestWithMethod:@"POST" URLString:fullPath parameters:dictionaryRep serialization:OSKParameterSerializationType_HTTPBody_JSON];
[[sesh dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *theError = error;
if ([NSHTTPURLResponse statusCodeAcceptableForResponse:response] == NO && error == nil) {
theError = [NSError errorWithDomain:@"OSKAppDotNetUtility" code:400 userInfo:@{NSLocalizedFailureReasonErrorKey:[NSString stringWithFormat:@"Request failed: %@", response.description]}];
}
if (theError) {
OSKLog(@"Failed to send App.net post: %@", error);
}
completion((theError == nil), theError);
});
}
}] resume];
});
}
+ (NSDictionary *)_dictionaryRepresentationForContentItem:(OSKMicroblogPostContentItem *)item attachedImageDictionaries:(NSArray *)dictionaries {
NSMutableDictionary *rep = [[NSMutableDictionary alloc] init];
if (dictionaries.count) {
NSArray *fileReps = [self _annotationDictionariesForFileAPIDictionaries:dictionaries];
if (fileReps.count) {
[rep setObject:fileReps forKey:@"annotations"];
}
}
[rep setObject:item.text forKey:@"text"];
return rep;
}
+ (NSArray *)_annotationDictionariesForFileAPIDictionaries:(NSArray *)fileDictionaries {
NSMutableArray *annotations = [[NSMutableArray alloc] initWithCapacity:fileDictionaries.count];
for (NSDictionary *aFileDictionary in fileDictionaries) {
NSString *fileID = [aFileDictionary osk_nonNullStringIDForKey:@"id"];
NSString *fileToken = [aFileDictionary objectForKey:@"file_token"];
if (fileToken.length && fileID.length) { // Prevent crash if fileID or fileToken are nil
NSMutableDictionary *valueDictionary = [[NSMutableDictionary alloc] init];
NSMutableDictionary *fileDict = [[NSMutableDictionary alloc] init];
[fileDict setValue:fileID forKey:@"file_id"];
[fileDict setValue:fileToken forKey:@"file_token"];
[fileDict setValue:@"oembed" forKey:@"format"];
[valueDictionary setValue:fileDict forKey:@"+net.app.core.file"];
NSDictionary *annotation = @{@"type":@"net.app.core.oembed",
@"value":valueDictionary};
[annotations addObject:annotation];
}
}
return annotations;
}
#pragma mark - Users & Accounts
+ (void)fetchUserDataWithCredential:(OSKManagedAccountCredential *)credential
appCredential:(OSKApplicationCredential *)appCredential
completion:(void(^)(NSDictionary *userDictionary, NSError *error))completion {
NSString *path = [NSString stringWithFormat:OSKAppDotNetUtility_URL_FetchMe, credential.token];
NSString *fullPath = [NSString stringWithFormat:@"%@%@", OSKAppDotNetUtility_BaseURL, path];
NSURLSession *sesh = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest osk_requestWithMethod:@"GET" URLString:fullPath parameters:nil serialization:OSKParameterSerializationType_HTTPBody_FormData];
[[sesh dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *theError = error;
if ([NSHTTPURLResponse statusCodeAcceptableForResponse:response] == NO && error == nil) {
theError = [NSError errorWithDomain:@"OSKAppDotNetUtility" code:400 userInfo:@{NSLocalizedFailureReasonErrorKey:[NSString stringWithFormat:@"Request failed: %@", response.description]}];
}
if (theError == nil) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSDictionary *userDictionary = nil;
if (data) {
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
userDictionary = [responseDictionary objectForKey:@"data"];
}
//There are times when the values aren't present in the dictionary
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
if ([userDictionary objectForKey:kAccountID]) {
userInfo[OSKAppDotNetUtility_UserInfoKey_accountID] = [userDictionary osk_nonNullStringIDForKey:kAccountID];
}
if ([userDictionary objectForKey:kUsername]) {
userInfo[OSKAppDotNetUtility_UserInfoKey_username] = [userDictionary osk_nonNullObjectForKey:kUsername];
}
if ([userDictionary objectForKey:kName]) {
userInfo[OSKAppDotNetUtility_UserInfoKey_name] = [userDictionary osk_nonNullObjectForKey:kName];
}
if ([userDictionary objectForKey:kAvatarObject])
{
NSDictionary *avatarObject = [userDictionary osk_nonNullObjectForKey:kAvatarObject];
if ([avatarObject objectForKey:kAvatarURL]) {
userInfo[OSKAppDotNetUtility_UserInfoKey_avatarURL] = [avatarObject osk_nonNullObjectForKey:kAvatarURL];
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(userInfo, nil);
});
}
});
} else {
completion(nil, theError);
}
}] resume];
}
+ (void)createNewUserWithAccessToken:(NSString *)token appCredential:(OSKApplicationCredential *)appCredential completion:(void(^)(OSKManagedAccount *account, NSError *error))completion {
NSString *identifier = [OSKManagedAccount generateNewOvershareAccountIdentifier];
OSKManagedAccountCredential *accountCredential = nil;
accountCredential = [[OSKManagedAccountCredential alloc] initWithOvershareAccountIdentifier:identifier accessToken:token];
[self fetchUserDataWithCredential:accountCredential appCredential:appCredential completion:^(NSDictionary *userDictionary, NSError *error) {
OSKManagedAccount *account = nil;
if (userDictionary) {
account = [self _createAccountWithUserInfo:userDictionary
accountCredential:accountCredential
accountIdentifier:identifier];
}
if (account) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(account,nil);
});
}
else {
OSKLog(@"Unable to create account for App.net, error fetching user info: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
}
}];
}
+ (OSKManagedAccount *)_createAccountWithUserInfo:(NSDictionary *)userInfo accountCredential:(OSKManagedAccountCredential *)credential accountIdentifier:(NSString *)identifier {
OSKManagedAccount *account = nil;
account = [[OSKManagedAccount alloc] initWithOvershareAccountIdentifier:identifier
activityType:OSKActivityType_API_AppDotNet
credential:credential];
[account setUsername:userInfo[OSKAppDotNetUtility_UserInfoKey_username]];
[account setFullName:userInfo[OSKAppDotNetUtility_UserInfoKey_name]];
[account setAccountID:userInfo[OSKAppDotNetUtility_UserInfoKey_accountID]];
return account;
}
#pragma mark - File Upload
+ (void)uploadImages:(NSArray *)images accountCredential:(OSKManagedAccountCredential *)credential appCredential:(OSKApplicationCredential *)appCredential completion:(void (^)(NSArray *fileDictionaries, NSError *error))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableArray *fileDictionaries = [[NSMutableArray alloc] initWithCapacity:images.count];
__block NSError *firstError = nil;
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_group_t group = dispatch_group_create();
for (UIImage *image in images) {
dispatch_group_async(group,queue,^{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self uploadImage:image accountCredential:credential appCredential:appCredential completion:^(NSDictionary *fileDictionary, NSError *uploadError) {
if (fileDictionary && [fileDictionary isKindOfClass:[NSDictionary class]]) {
[fileDictionaries addObject:fileDictionary];
} else {
if (firstError == nil) {
firstError = uploadError;
}
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
});
}
dispatch_group_notify(group,queue,^{
if (completion) {
completion(fileDictionaries, firstError);
}
});
});
}
+ (void)uploadImage:(UIImage *)image accountCredential:(OSKManagedAccountCredential *)credential appCredential:(OSKApplicationCredential *)appCredential completion:(void (^)(NSDictionary *fileDictionary, NSError *error))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSDictionary *parameters = @{@"type":@"net.overshare.image",
@"mime_type":@"image/jpeg",
@"kind":@"image",
@"public":@(YES)};
parameters = [[NSDictionary alloc] initWithDictionary:parameters];
NSString *path = [NSString stringWithFormat:@"stream/0/files?access_token=%@", credential.token];
NSString *fullPath = [NSString stringWithFormat:@"%@%@", OSKAppDotNetUtility_BaseURL, path];
NSString *mimeType = @"image/jpeg";
NSString *suffix = @"jpg";
NSString *dateSuffix = [self _todaysDateSuffix];
CGFloat quality = [UIImage osk_recommendedUploadQuality:image];
NSData *imageData = UIImageJPEGRepresentation(image, quality);
NSString *appName = appCredential.appName.copy;
NSString *filename = [NSString stringWithFormat:@"Image_from_%@_%@.%@", appName, dateSuffix, suffix];
NSURLSession *sesh = [NSURLSession sharedSession];
NSMutableURLRequest *request = nil;
NSData *requestData = nil;
request = [NSMutableURLRequest osk_MultipartFormUploadRequestWithMethod:@"POST" URLString:fullPath parameters:parameters uploadData:imageData filename:filename formName:@"content" mimeType:mimeType serialization:OSKParameterSerializationType_HTTPBody_JSON bodyData:&requestData];
[[sesh uploadTaskWithRequest:request fromData:requestData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error == nil) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error = nil;
NSDictionary *responseDictionary = nil;
if (data) {
responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion([responseDictionary objectForKey:@"data"], error);
});
}
});
} else {
OSKLog(@"OSKAppDotNetUtility: Image upload failed: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
completion(nil, error);
});
}
}] resume];
});
}
+ (NSString *)_todaysDateSuffix {
NSDate *today = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
NSString *suffix = [formatter stringFromDate:today];
return suffix;
}
@end