http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClient.h ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGClient.h b/sdks/other/ios/UGAPI/UGClient.h new file mode 100755 index 0000000..4e6a31f --- /dev/null +++ b/sdks/other/ios/UGAPI/UGClient.h @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> +#import "UGClientResponse.h" +#import "UGQuery.h" +#import "UGActivity.h" +#import "UGUser.h" + +/******************** A WORD ON NETWORK COMMUNICATION CALLS **************** +Some calls require network communication with UserGrid. Therefore, +they all have the option of being synchronous (blocking) or asynchronous. + +You may specify an asynchronous delegate with the call setDelegate. If you +do, all calls will be asynchronous and responses will be sent to that delegate. +The immediate return value (a UGClientResponse *) from any call will have its +transactionState set to kUGClientResponsePending, and the transactionID will be +properly set (allowing you to identify the specific call in your callback if you +wish.) + +The delegate must support the following message: +-(void)ugClientResponse:(UGClientResponse *)response + +If you do not set a delegate, all functions will run synchronously, blocking +until a response has been received or an error detected. +****************************************************************************/ + + +/**************************** A WORD ON UGQUERY ***************************** +Some calls take a UGQuery *. These are functions that return a lot of data, as +opposed to a simple answer. You may use the UGQuery to control the data with filters +and response limits. See UGQuery.h for more information. + +In all cases, where a UGQuery is one of the parameters, you may send nil. If you +do, the query will be completely unfiltered, and you will receive back *all* the data +associated with the operation, up to the response limit, which is 10. You can +set the response limit in UGQuery as well. +****************************************************************************/ + + +@interface UGClient : NSObject + ++(NSString *) version; + +/********************* INIT AND SETUP *********************/ +// init with an app ID +-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID; + +// init with an app ID and a base UserGrid URL. This is useful if you +// are running a local UG server or your company has its own public +// UG server. The default URL is http://api.usergrid.com. The base URL +// must be a fully formated http link, including the "http://" at the +// beginning. +-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID baseURL:(NSString *)baseURL; + +// set the delegate. See "A WORD ON NETWORK COMMUNICATION CALLS" +// at the top of the file for a detailed explanation. The function +// will return NO if the delegate is rejected. This means the delegate +// does not support the required delegation function "ugClientResponse". +// +// This is the formal declaration of ugClientResponse: +// -(void)ugClientResponse:(UGClientResponse *)response +// +// You may change the delegate at any time, but be forewarned that any +// pending transactions in progress will be abandoned. Changing the delegate +// (especially setting it to nil) ensures that hte previous delegate will +// receive no further messages from this instance of UGClient. +// +// Setting the delegate to nil puts the API in to synchronous mode. +-(BOOL) setDelegate:(id)delegate; + + +/********************* LOGIN / LOGOUT *********************/ +// log in with the given username and password +-(UGClientResponse *)logInUser: (NSString *)userName password:(NSString *)password; + +// log in with the given username and PIN value +-(UGClientResponse *)logInUserWithPin: (NSString *)userName pin:(NSString *)pin; + +// log in user with Facebook token +// +// //sample usage: +// NSString * facebookToken = @"your-facebook-token"; +// UGClientResponse *response = [usergridClient logInUserWithFacebook:facebookToken]; +// user = [usergridClient getLoggedInUser]; +// if (user.username){ +// return true; +// } else { +// return false; +// } +// +-(UGClientResponse *)logInUserWithFacebook: (NSString *)facebookToken; + +// log in as the administrator of the application. Generally used for applications +// that have an "administrator" feature. Not the sort of thing you want normal +// users doing. +-(UGClientResponse *)logInAdmin: (NSString *)adminUserName secret:(NSString *)adminSecret; + +// log out the current user. The Client only supports one user logged in at a time. +// You can have multiple instances of UGClient if you want multiple +// users doing transactions simultaneously. This does not require network communication, +// so it has no return. It doesn't actually "log out" from the server. It simply clears +// the locally stored auth information +-(void)logOut; + + + +/********************* USER MANAGEMENT *********************/ +//adds a new user +-(UGClientResponse *)addUser:(NSString *)username email:(NSString *)email name:(NSString *)name password:(NSString *)password; + +// updates a user's password +-(UGClientResponse *)updateUserPassword:(NSString *)usernameOrEmail oldPassword:(NSString *)oldPassword newPassword:(NSString *)newPassword; + +// get all the groups this user is in +-(UGClientResponse *)getGroupsForUser: (NSString *)userID; + +// get users in this app. Definitely want to consider sending a Query along +// with this call +-(UGClientResponse *)getUsers: (UGQuery *)query; + +/********************* ACTIVITY MANAGEMENT *********************/ +// create a new activity. +// Note that there is a class, UGActivity, which can help +// you create and validate an Activity, and will generate the needed +// NSDictionary for you. +-(UGClientResponse *)createActivity: (NSDictionary *)activity; + +// create an activity and post it to a user in a single step. See comment +// above createActivity for information on making Activity creation easier +-(UGClientResponse *)postUserActivity: (NSString *)userID activity:(NSDictionary *)activity; + +// post an already-created activity to a user +-(UGClientResponse *)postUserActivityByUUID: (NSString *)userID activity:(NSString *)activityUUID; + +// create an activity and post it to a group in a single step. See comment +// above createActivity for information on making Activity creation easier +-(UGClientResponse *)postGroupActivity: (NSString *)groupID activity:(NSDictionary *)activity; + +// post an already-created activity to a group +-(UGClientResponse *)postGroupActivityByUUID: (NSString *)groupID activity:(NSString *)activityUUID; + +// get the activities this user is in +-(UGClientResponse *)getActivitiesForUser: (NSString *)userID query:(UGQuery *)query; + +// get the activities this group is in +-(UGClientResponse *)getActivitiesForGroup: (NSString *)groupID query:(UGQuery *)query; + +// get the activity feed for a user +-(UGClientResponse *)getActivityFeedForUser: (NSString *)userID query:(UGQuery *)query; + +// get the activity feed for a group +-(UGClientResponse *)getActivityFeedForGroup: (NSString *)groupID query:(UGQuery *)query; + +// remove an activity +-(UGClientResponse *)removeActivity:(NSString *)activityUUID; + +/********************* GROUP MANAGEMENT *********************/ +// create a new group. The groupPath can be a path with slashes to make for +// a hierarchical structure of your own design (if you want). groupTitle is +// optional, you can send nil if you don't want to provide one. +-(UGClientResponse *)createGroup:(NSString *)groupPath groupTitle:(NSString *)groupTitle; + +// add a user to a group +-(UGClientResponse *)addUserToGroup:(NSString *)userID group:(NSString *)groupID; + +// remove a user from a group +-(UGClientResponse *)removeUserFromGroup:(NSString *)userID group:(NSString *)groupID; + +// get all the users in this group +-(UGClientResponse *)getUsersForGroup:(NSString *)groupID query:(UGQuery *)query; + + + +/******************** ENTITY MANAGEMENT ********************/ +// adds an entity to the specified collection. +-(UGClientResponse *)createEntity: (NSDictionary *)newEntity; + +// get a list of entities that meet the specified query. +-(UGClientResponse *)getEntities: (NSString *)type query:(UGQuery *)query; + +// updates an entity (it knows the type from the entity data) +-(UGClientResponse *)updateEntity: (NSString *)entityID entity:(NSDictionary *)updatedEntity; + +// removes an entity of the specified type +-(UGClientResponse *)removeEntity: (NSString *)type entityID:(NSString *)entityID; + +// Directionally connect two entities. For instance, user "Bob" might like Lyons Restaurant. +// connectorType would be "users" (because Bob is a user) +// connectorID would be Bob's userID +// connectionType would be "like" +// connecteeID would be the UUID of Lyons Restaurant +-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID; + +// Directionally connect two entities. For instance, user "Bob" might follow user "Mary". +// connectorType would be "users" (because Bob is a user) +// connectorID would be Bob's userID +// connectionType would be "like" +// connecteeType would be "users" (because Mary is a user) +// connecteeID would be Mary's userID +-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType connecteeType:(NSString *)connecteeType connecteeID:(NSString *)connecteeID; + +// disconnect two entities. It uses the same parameters and calling rules as connectEntities +-(UGClientResponse *)disconnectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID; + +// get entity connections +-(UGClientResponse *)getEntityConnections: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType query:(UGQuery *)query; + + + +/********************* MESSAGE MANAGEMENT *********************/ +// post a message to a given queue +-(UGClientResponse *)postMessage: (NSString *)queuePath message:(NSDictionary *)message; + +// get all messages from the queue path +-(UGClientResponse *)getMessages: (NSString *)queuePath query:(UGQuery *)query; + +// add a subscriber to a queue +-(UGClientResponse *)addSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath; + +// remove a subscriber from a queue +-(UGClientResponse *)removeSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath; + + +/********************* SERVER-SIDE STORAGE *********************/ +// these functions refer to data that can be put in a special place +// specific to this device. Every call to remoteStorage replaces whatever +// was there before + +// put the data in to the remote storage +-(UGClientResponse *)setRemoteStorage: (NSDictionary *)data; + +// get the data from remote storage +-(UGClientResponse *)getRemoteStorage; + +// a class function that returns a uuid for this +// device. It will be globally unique, and will always +// return the same value for the same handset. +// NOTE - This value will change if the operating +// system is reinstalled. This function is used internally, but +// is also handy for clients, so it is part of the interface. ++(NSString *)getUniqueDeviceID; + +/***************** REMOTE PUSH NOTIFICATIONS *****************/ + +// call from application:didRegisterForRemoteNotificationsWithDeviceToken: callback +// will automatically register the passed deviceToken with the usergrid system +// using the getUniqueDeviceID method to associate this device on the server +- (UGClientResponse *)setDevicePushToken:(NSData *)newDeviceToken forNotifier:(NSString *)notifier; + +// push an "alert" type notification to the remote group, user, or device specified +// in the path argument. the notifer may be a name or UUID of an apns notifier +// that has been set up on the usergrid server. +- (UGClientResponse *)pushAlert:(NSString *)message + withSound:(NSString *)sound + to:(NSString *)path + usingNotifier:(NSString *)notifier; + +/*********************** ACCESSORS ************************/ +// if a user is logged in, this returns the OAuth token for this session. +// UGClient manages this internally, so you never really need it. But if you +// want it for other reasons, this accessor gives it to you. If you have not +// successfully logged in, this will return nil +-(NSString *)getAccessToken; + +// returns information about the logged in user +-(UGUser *)getLoggedInUser; + +// returns the delegate that is currently being used for asynch +// calls. Returns nil if there is no delegate (synch mode) +-(id) getDelegate; + +/*********************** OBLIQUE USAGE ************************/ +// This is a general purpose function for directly accessing the +// UserGrid service. This is useful if the service has new features +// that the API has not yet supported, or if you are using an older +// version of the API and don't want to upgrade. +// +// url: The full URL that you are accessing. You are responsible for +// assembling it, including the appID and all sub-sections down the line +// +// op: The HttpMethod being invoked. Examples: @"POST", @"PUT", etc. You may +// send nil. If you do, the operation is GET. There is one specially supported +// method called "POSTFORM". This will post with the data type set to +// application/x-www-form-urlencoded instead of the more likely needed +// application/json. This is necessary if you are doing authentication +// or if you are sending form data up. +// +// opData: The data sent along with the operation. You may send nil. If the +// operation is GET, this value is ignored. Usually, this would be +// expected to be in json format. With this oblique approach, it is +// your responsibility to format the data correctly for whatever you're +// doing. Bear in mind that this api comes with SBJson, which provides +// some very simple ways to assemble json formatted strings. See SBJsonWriter. +// +// NOTE - This function will be synchronous or asynchronous the same as any +// other function in the API. It is based on the value sent to setDelegate. +-(UGClientResponse *)apiRequest: (NSString *)url operation:(NSString *)op data:(NSString *)opData; + +/*********************** DEBUGGING ASSISTANCE ************************/ +// when logging is on, all outgoing URLs are logged via NSLog, and all +// incoming data from the service is also logged. Additionally, any errors +// encountered internally are logged. This can be helpful to see the actual +// service communication in progress and help debug problems you may be having. +-(void)setLogging: (BOOL)loggingState; + +/*********************** VERSION CHECKING ************************/ +#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) +#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) +#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) +#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) + +@end
http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClient.m ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGClient.m b/sdks/other/ios/UGAPI/UGClient.m new file mode 100755 index 0000000..9d6407a --- /dev/null +++ b/sdks/other/ios/UGAPI/UGClient.m @@ -0,0 +1,1252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "UGClient.h" +#import "UGHTTPManager.h" +#import "SBJson.h" +#import "UGMultiStepAction.h" +#import "SSKeychain.h" + +NSString *g_deviceUUID = nil; + +@implementation UGClient +{ + // the delegate for asynch callbacks + id m_delegate; + + // the mutex to protect the delegate variable + NSRecursiveLock *m_delegateLock; + + // a growing array of UGHTTPManager instances. See + // "HTTPMANAGER POOLING" further down in this file. + NSMutableArray *m_httpManagerPool; + + // the base URL for the service + NSString *m_baseURL; + + // the appID for the specific app + NSString *m_appID; + + // the appID for the specific app + NSString *m_orgID; + + // the cached auth token + UGUser *m_loggedInUser; + + // the auth code + NSString *m_auth; + + // the list of currently pending multi-step actions + NSMutableArray *m_pendingMultiStepActions; + + // logging state + BOOL m_bLogging; +} + +/************************** ACCESSORS *******************************/ +/************************** ACCESSORS *******************************/ +/************************** ACCESSORS *******************************/ ++(NSString *) version +{ + return @"0.1.1"; +} + +-(NSString *)getAccessToken +{ + return m_auth; +} + +-(UGUser *)getLoggedInUser +{ + return m_loggedInUser; +} + +-(id) getDelegate +{ + return m_delegate; +} + +/******************************* INIT *************************************/ +/******************************* INIT *************************************/ +/******************************* INIT *************************************/ +-(id)init +{ + // you are not allowed to init without an organization id and application id + // you can't init with [UGClient new]. You must call + // [[UGClient alloc] initWithOrganizationId: <your UG org id> withApplicationId:<your UG app id>] + assert(0); + return nil; +} + +-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID +{ + self = [super init]; + if ( self ) + { + m_delegate = nil; + m_httpManagerPool = [NSMutableArray new]; + m_delegateLock = [NSRecursiveLock new]; + m_appID = applicationID; + m_orgID = organizationID; + m_baseURL = @"http://api.usergrid.com"; + m_pendingMultiStepActions = [NSMutableArray new]; + m_loggedInUser = nil; + m_bLogging = NO; + } + return self; +} + +-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID baseURL:(NSString *)baseURL +{ + self = [super init]; + if ( self ) + { + m_delegate = nil; + m_httpManagerPool = [NSMutableArray new]; + m_delegateLock = [NSRecursiveLock new]; + m_appID = applicationID; + m_orgID = organizationID; + m_baseURL = baseURL; + } + return self; +} + +-(BOOL) setDelegate:(id)delegate +{ + // first off, clear any pending transactions + for ( int i=0 ; i<[m_httpManagerPool count] ; i++ ) + { + UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i]; + + // it's safe to call cancel at all times. + [mgr cancel]; + } + + // nil is a valid answer. It means we're synchronous now. + if ( delegate == nil ) + { + [m_delegateLock lock]; + m_delegate = nil; + [m_delegateLock unlock]; + return YES; + } + + // if it's not nil, it has to have the delegation function + if ( ![delegate respondsToSelector:@selector(ugClientResponse:)] ) + { + return NO; + } + + // if we're here, it means the delegate is valid + [m_delegateLock lock]; + m_delegate = delegate; + [m_delegateLock unlock]; + return YES; +} + +/************************* HTTPMANAGER POOLING *******************************/ +/************************* HTTPMANAGER POOLING *******************************/ +/************************* HTTPMANAGER POOLING *******************************/ + +// any given instance of UGHTTPManager can only manage one transaction at a time, +// but we want the client to be able to have as many going at once as he likes. +// so we have a pool of UGHTTPManagers as needed. +-(UGHTTPManager *)getHTTPManager; +{ + // find the first unused HTTPManager + for ( int i=0 ; i<[m_httpManagerPool count] ; i++ ) + { + UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i]; + if ( [mgr isAvailable] ) + { + // tag this guy as available + [mgr setAvailable:NO]; + + // return him + return mgr; + } + } + + // if we're here, we didn't find any available managers + // so we'll need to make a new one + UGHTTPManager *newMgr = [UGHTTPManager new]; + + // mark it as in-use (we're about to return it) + [newMgr setAvailable:NO]; + + // tell it the auth to use + [newMgr setAuth:m_auth]; + + // add it to the array + [m_httpManagerPool addObject:newMgr]; + + // return it + return newMgr; +} + +-(void)releaseHTTPManager:(UGHTTPManager *)toRelease +{ + [toRelease setAvailable:YES]; +} + +-(void)setAuth:(NSString *)auth +{ + // note the auth for ourselves + m_auth = auth; + + // update all our managers + for ( int i=0 ; i<[m_httpManagerPool count] ; i++ ) + { + UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i]; + [mgr setAuth:m_auth]; + } +} + +/************************* GENERAL WORKHORSES *******************************/ +/************************* GENERAL WORKHORSES *******************************/ +/************************* GENERAL WORKHORSES *******************************/ +// url: the URL to hit +// op: a kUGHTTP constant. Example: kUGHTTPPost +// opData: The data to send along with the operation. Can be nil +-(UGClientResponse *)httpTransaction:(NSString *)url op:(int)op opData:(NSString *)opData +{ + // get an http manager to do this transaction + UGHTTPManager *mgr = [self getHTTPManager]; + + if ( m_delegate ) + { + if ( m_bLogging ) + { + NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + NSLog(@"Asynch outgoing call: '%@'", url); + } + + // asynch transaction + int transactionID = [mgr asyncTransaction:url operation:op operationData:opData delegate:self]; + + if ( m_bLogging ) + { + NSLog(@"Transaction ID:%d", transactionID); + NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); + } + + if ( transactionID == -1 ) + { + if ( m_bLogging ) + { + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + NSLog(@"Response: ERROR: %@", [mgr getLastError]); + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); + } + + // there was an immediate failure in the transaction + UGClientResponse *response = [UGClientResponse new]; + [response setTransactionID:-1]; + [response setTransactionState:kUGClientResponseFailure]; + [response setResponse:[mgr getLastError]]; + [response setRawResponse:nil]; + return response; + } + else + { + // the transaction is in progress and pending + UGClientResponse *response = [UGClientResponse new]; + [response setTransactionID:transactionID]; + [response setTransactionState:kUGClientResponsePending]; + [response setResponse:nil]; + [response setRawResponse:nil]; + return response; + } + } + else + { + if ( m_bLogging ) + { + NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + NSLog(@"Synch outgoing call: '%@'", url); + NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n"); + } + + // synch transaction + NSString *result = [mgr syncTransaction:url operation:op operationData:opData]; + + if ( m_bLogging ) + { + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + if ( result ) + { + NSLog(@"Response:\n%@", result); + } + else + { + NSLog(@"Response: ERROR: %@", [mgr getLastError]); + } + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); + } + + // since we're doing a synch transaction, we are now done with this manager. + [self releaseHTTPManager:mgr]; + + if ( result ) + { + // got a valid result + UGClientResponse *response = [self createResponse:-1 jsonStr:result]; + return response; + } + else + { + // there was an error. Note the failure state, set the response to + // be the error string + UGClientResponse *response = [UGClientResponse new]; + [response setTransactionID:-1]; + [response setTransactionState:kUGClientResponseFailure]; + [response setResponse:[mgr getLastError]]; + [response setRawResponse:nil]; + return response; + } + } +} + +-(UGClientResponse *)createResponse:(int)transactionID jsonStr:(NSString *)jsonStr +{ + UGClientResponse *response = [UGClientResponse new]; + + // set the raw response and transaction id + [response setRawResponse:jsonStr]; + [response setTransactionID:transactionID]; + + // parse the json + SBJsonParser *parser = [SBJsonParser new]; + NSError *error; + id result = [parser objectWithString:jsonStr error:&error]; + + if ( result ) + { + // first off, if the result is NOT an NSDictionary, something went wrong. + // there should never be an array response + if ( ![result isKindOfClass:[NSDictionary class]] ) + { + [response setTransactionState:kUGClientResponseFailure]; + [response setResponse:@"Internal error: Response parsed to something other than NSDictionary"]; + return response; + } + + // it successfully parsed. Though the result might still be an error. + // it could be the server returning an error in perfectly formated json. + NSString *err = [result valueForKey:@"error"]; + if ( err ) + { + // there was an error. See if there's a more detailed description. + // if there is, we'll use that. If not, we'll use the error value + // itself. + NSString *errDesc = [result valueForKey:@"error_description"]; + NSString *toReport = errDesc; + if ( !toReport ) toReport = err; + + [response setTransactionState:kUGClientResponseFailure]; + [response setResponse:toReport]; + return response; + } + + // if we're here we have a good auth. make note of it + NSString *auth = [result valueForKey:@"access_token"]; + if ( auth ) + { + [self setAuth: auth]; + + // if there's an access token, there might be a user + NSDictionary *dict = [result objectForKey:@"user"]; + if ( dict ) + { + // get the fields for the user + m_loggedInUser = [UGUser new]; + [m_loggedInUser setUsername:[dict valueForKey:@"username"]]; + [m_loggedInUser setUuid:[dict valueForKey:@"uuid"]]; + [m_loggedInUser setEmail:[dict valueForKey:@"email"]]; + [m_loggedInUser setPicture:[dict valueForKey:@"picture"]]; + } + } + + [response setTransactionState:kUGClientResponseSuccess]; + [response setResponse:result]; + return response; + } + else + { + // there was an error during json parsing. + [response setTransactionState:kUGClientResponseFailure]; + [response setResponse:[error localizedDescription]]; + return response; + } +} + +// basic URL assembly functions. For convenience +-(NSMutableString *)createURL:(NSString *)append1 +{ + NSMutableString *ret = [NSMutableString new]; + [ret appendFormat:@"%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1]; + return ret; +} + +-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 +{ + NSMutableString *ret = [NSMutableString new]; + [ret appendFormat:@"%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2]; + return ret; +} + +-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 +{ + NSMutableString *ret = [NSMutableString new]; + [ret appendFormat:@"%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3]; + return ret; +} + +-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 append4:(NSString *)append4 +{ + NSMutableString *ret = [NSMutableString new]; + [ret appendFormat:@"%@/%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3, append4]; + return ret; +} + +-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 append4:(NSString *)append4 append5:(NSString *)append5 +{ + NSMutableString *ret = [NSMutableString new]; + [ret appendFormat:@"%@/%@/%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3, append4, append5]; + return ret; +} + +-(void)appendQueryToURL:(NSMutableString *)url query:(UGQuery *)query +{ + if ( query ) + { + [url appendFormat:@"%@", [query getURLAppend]]; + } +} + +-(NSString *)createJSON:(NSDictionary *)data +{ + NSString *ret = [self createJSON:data error:nil]; + + // the only way for ret to be nil here is for an internal + // function to have a bug. + assert(ret); + return ret; +} + +-(NSString *)createJSON:(NSDictionary *)data error:(NSString **)error +{ + SBJsonWriter *writer = [SBJsonWriter new]; + NSError *jsonError; + NSString *jsonStr = [writer stringWithObject:data error:&jsonError]; + + if ( jsonStr ) + { + return jsonStr; + } + + // if we're here, there was an assembly error + if ( error ) + { + *error = [jsonError localizedDescription]; + } + return nil; +} + +/************************** UGHTTPMANAGER DELEGATES *******************************/ +/************************** UGHTTPMANAGER DELEGATES *******************************/ +/************************** UGHTTPMANAGER DELEGATES *******************************/ +-(void)httpManagerError:(UGHTTPManager *)manager error:(NSString *)error +{ + // prep an error response + UGClientResponse *response = [UGClientResponse new]; + [response setTransactionID:[manager getTransactionID]]; + [response setTransactionState:kUGClientResponseFailure]; + [response setResponse:error]; + [response setRawResponse:nil]; + + if ( m_bLogging ) + { + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + NSLog(@"Response: ERROR: %@", error); + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); + } + + // fire it off. Wrap in mutex locks to ensure we don't get + // race conditions that cause us to fire it off to Mr. Nil. + [m_delegateLock lock]; + if ( m_delegate ) + { + [m_delegate performSelector:@selector(ugClientResponse:) withObject:response]; + } + [m_delegateLock unlock]; + + // now that the callback is complete, it's safe to release this manager + [self releaseHTTPManager:manager]; +} + +-(void)httpManagerResponse:(UGHTTPManager *)manager response:(NSString *)response +{ + if ( m_bLogging ) + { + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + NSLog(@"Response (Transaction ID %d):\n%@", [manager getTransactionID], response); + NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); + } + + // form up the response + UGClientResponse *ugResponse = [self createResponse:[manager getTransactionID] jsonStr:response]; + + // if this is part of a multi-step call, we press on. + for ( int i=0 ; i<[m_pendingMultiStepActions count] ; i++ ) + { + UGMultiStepAction *action = [m_pendingMultiStepActions objectAtIndex:i]; + if ( [action transactionID] == [ugResponse transactionID] ) + { + // multi-step call. Fire off the action. + ugResponse = [self doMultiStepAction:action mostRecentResponse:ugResponse]; + if ( ![action reportToClient] ) + { + // the action is still pending. We do not report this + // to the user. We're done with the httpmanager we were using, + // though. + [self releaseHTTPManager:manager]; + return; + } + + // when the action is complete, we want to immediately break + // from this loop, then fall through to the normal reporting + // to the user. + break; + } + } + + // fire it off + [m_delegateLock lock]; + if ( m_delegate ) + { + [m_delegate performSelector:@selector(ugClientResponse:) withObject:ugResponse]; + } + [m_delegateLock unlock]; + + // now that the callback is complete, it's safe to release this manager + [self releaseHTTPManager:manager]; +} + +// multi-step follow-up function +-(UGClientResponse *)multiStepAction: (UGMultiStepAction *)action +{ + // different behavior if synch or asynch + if ( m_delegate ) + { + // asynch. Fire it off and we're done + return [self doMultiStepAction:action mostRecentResponse:nil]; + } + else + { + // synchronous. keep calling until it finished or fails + UGClientResponse *response = nil; + do + { + response = [self doMultiStepAction:action mostRecentResponse:response]; + if ( [action reportToClient] ) + { + // done + return response; + } + } while ([response transactionState] == kUGClientResponseSuccess); + + // if we're here, there was an error + return response; + } +} + +-(UGClientResponse *)doMultiStepAction: (UGMultiStepAction *)action mostRecentResponse:(UGClientResponse *)mostRecentResponse +{ + // clear the pending array of this object + [m_pendingMultiStepActions removeObject:action]; + + // assume we aren't reporting to the client + [action setReportToClient:NO]; + + if ( mostRecentResponse ) + { + // we don't care about pending responses + if ( [mostRecentResponse transactionState] == kUGClientResponsePending ) + { + // put ourselves back in the list + [m_pendingMultiStepActions addObject:action]; + return mostRecentResponse; + } + + // any failure is an immediate game ender + if ( [mostRecentResponse transactionState] == kUGClientResponseFailure ) + { + [mostRecentResponse setTransactionID:[action transactionID]]; + return mostRecentResponse; + } + } + + // if mostRecentRespons is nil, that means it's the first call to initiate + // the chain. So we continue on with processing. + + // so either we are reacting to a success or we are starting off the chain + UGClientResponse *result = nil; + if ( [action nextAction] == kMultiStepCreateActivity ) + { + // create the activity + result = [self createActivity:[action activity]]; + + // advance ourselves to the next step + [action setNextAction:kMultiStepPostActivity]; + } + else if ( [action nextAction] == kMultiStepCreateGroupActivity ) + { + // create the activity + result = [self createActivity:[action activity]]; + + // advance ourselves to the next step + [action setNextAction:kMultiStepPostGroupActivity]; + } + else if ( [action nextAction] == kMultiStepPostActivity ) + { + // we just created an activity, now we need to associate it with a user. + // first, we'll need the activity's uuid + NSDictionary *dict = [mostRecentResponse response]; // dictionary for the response + NSArray *entities = [dict objectForKey:@"entities"]; // array for the entities + NSDictionary *activity = [entities objectAtIndex:0]; // dict for the activity + NSString *activityUUID = [activity valueForKey:@"uuid"]; // and finally the uuid string + + // fire off the next step + result = [self postUserActivityByUUID:[action userID] activity:activityUUID]; + + // advance the action + [action setNextAction:kMultiStepCleanup]; + } + else if ( [action nextAction] == kMultiStepPostGroupActivity ) + { + // we just created an activity, now we need to associate it with a user. + // first, we'll need the activity's uuid + NSDictionary *dict = [mostRecentResponse response]; // dictionary for the response + NSArray *entities = [dict objectForKey:@"entities"]; // array for the entities + NSDictionary *activity = [entities objectAtIndex:0]; // dict for the activity + NSString *activityUUID = [activity valueForKey:@"uuid"]; // and finally the uuid string + + // fire off the next step + result = [self postGroupActivityByUUID:[action groupID] activity:activityUUID]; + + // advance the action + [action setNextAction:kMultiStepCleanup]; + } + else if ( [action nextAction] == kMultiStepCleanup ) + { + // all we do in cleanup is update the transaction ID of the + // response that was sent in. We do this to ensure that the transaction + // id is constant across the entire transaction + result = mostRecentResponse; + [result setTransactionID:[action outwardTransactionID]]; + [action setReportToClient:YES]; + } + + if ( !mostRecentResponse ) + { + // if mostRecentResponse is nil, it means we're on the first step. That means + // we need to adopt a unique outward transaction ID. We'll simply use + // the ID given back by the first transaction in the chain. This also means + // we can simply return the first transaction pending response without modification. + [action setOutwardTransactionID:[result transactionID]]; + } + + // wherever we landed, if it's a pending transaction, the action needs to + // know that transaction ID. Also, we need to go in to the pending array + if ( [result transactionState] == kUGClientResponsePending ) + { + [action setTransactionID:[result transactionID]]; + [m_pendingMultiStepActions addObject:action]; + } + + // result is now properly set up and ready to be handed to the user. + return result; +} + +/*************************** LOGIN / LOGOUT ****************************/ +/*************************** LOGIN / LOGOUT ****************************/ +/*************************** LOGIN / LOGOUT ****************************/ +-(UGClientResponse *)logInUser: (NSString *)userName password:(NSString *)password +{ + return [self logIn:@"password" userKey:@"username" userValue:userName pwdKey:@"password" pwdValue:password]; +} + +-(UGClientResponse *)logInUserWithPin: (NSString *)userName pin:(NSString *)pin +{ + return [self logIn:@"pin" userKey:@"username" userValue:userName pwdKey:@"pin" pwdValue:pin]; +} + +-(UGClientResponse *)logInUserWithFacebook: (NSString *)facebookToken +{ + NSMutableString *url = [self createURL:@"auth/facebook"]; + UGQuery *query = [[UGQuery alloc] init]; + [query addURLTerm:@"fb_access_token" equals:facebookToken]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)logInAdmin: (NSString *)adminUserName secret:(NSString *)adminSecret +{ + return [self logIn:@"client_credentials" userKey:@"client_id" userValue:adminUserName pwdKey:@"client_secret" pwdValue:adminSecret]; +} + +-(void)logOut +{ + // clear out auth + [self setAuth: nil]; +} + +// general workhorse for auth logins +-(UGClientResponse *)logIn:(NSString *)grantType userKey:(NSString *)userKey userValue:(NSString *)userValue pwdKey:(NSString *)pwdKey pwdValue:(NSString *)pwdValue +{ + // create the URL + NSString *url = [self createURL:@"token"]; + + // because it's read as form data, we need to escape special characters. + NSString *escapedUserValue = [UGHTTPManager escapeSpecials:userValue]; + NSString *escapedPwdValue = [UGHTTPManager escapeSpecials:pwdValue]; + + // create the post data. For auth functions, we don't use json, + // but instead use web form style data + NSMutableString *postData = [NSMutableString new]; + [postData appendFormat:@"grant_type=%@&%@=%@&%@=%@", grantType, userKey, escapedUserValue, pwdKey, escapedPwdValue]; + + // fire off the request + return [self httpTransaction:url op:kUGHTTPPostAuth opData:postData]; +} +/*************************** USER MANAGEMENT ***************************/ +/*************************** USER MANAGEMENT ***************************/ +/*************************** USER MANAGEMENT ***************************/ +-(UGClientResponse *)addUser:(NSString *)username email:(NSString *)email name:(NSString *)name password:(NSString *)password +{ + // make the URL we'll be posting to + NSString *url = [self createURL:@"users"]; + + // make the post data we'll be sending along with it. + NSMutableDictionary *toPost = [NSMutableDictionary new]; + [toPost setObject:username forKey:@"username"]; + [toPost setObject:name forKey:@"name"]; + [toPost setObject:email forKey:@"email"]; + [toPost setObject:password forKey:@"password"]; + NSString *toPostStr = [self createJSON:toPost]; + + // fire it off + return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; +} + +// updates a user's password +-(UGClientResponse *)updateUserPassword:(NSString *)usernameOrEmail oldPassword:(NSString *)oldPassword newPassword:(NSString *)newPassword +{ + // make the URL we'll be posting to + NSString *url = [self createURL:@"users" append2:usernameOrEmail append3:@"password"]; + + // make the post data we'll be sending along with it. + NSMutableDictionary *toPost = [NSMutableDictionary new]; + [toPost setObject:oldPassword forKey:@"oldpassword"]; + [toPost setObject:newPassword forKey:@"newpassword"]; + NSString *toPostStr = [self createJSON:toPost]; + + // fire it off + return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; +} + +-(UGClientResponse *)getGroupsForUser: (NSString *)userID; +{ + // make the URL, and fire off the get + NSString *url = [self createURL:@"users" append2:userID append3:@"groups"]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)getUsers: (UGQuery *)query +{ + // create the URL + NSMutableString *url = [self createURL:@"users"]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +/************************** ACTIVITY MANAGEMENT **************************/ +/************************** ACTIVITY MANAGEMENT **************************/ +/************************** ACTIVITY MANAGEMENT **************************/ +-(UGClientResponse *)createActivity: (NSDictionary *)activity +{ + // make the URL + NSString *url = [self createURL:@"activity"]; + + // get the json to send. + // we have to json-ify a dictionary that was sent + // in by the client. So naturally, we can't just trust it + // to work. Therefore we can't use our internal convenience + // function for making the json. We go straight to SBJson, so + // we can identify and report any errors. + SBJsonWriter *writer = [SBJsonWriter new]; + NSError *jsonError; + NSString *toPostStr = [writer stringWithObject:activity error:&jsonError]; + + if ( !toPostStr ) + { + // error during json assembly + UGClientResponse *ret = [UGClientResponse new]; + [ret setTransactionState:kUGClientResponseFailure]; + [ret setTransactionID:-1]; + [ret setResponse:[jsonError localizedDescription]]; + [ret setRawResponse:nil]; + return ret; + } + + // fire it off + return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; +} + +// create an activity and post it to a user in a single step +-(UGClientResponse *)postUserActivity: (NSString *)userID activity:(NSDictionary *)activity +{ + // prep a multi-step action + UGMultiStepAction *action = [UGMultiStepAction new]; + + // set it up to start the create activity / post to user chain + [action setNextAction:kMultiStepCreateActivity]; + [action setUserID:userID]; + [action setActivity:activity]; + + // fire it off + return [self multiStepAction:action]; +} + +-(UGClientResponse *)postUserActivityByUUID: (NSString *)userID activity:(NSString *)activityUUID +{ + // make the URL and fire off the post. there is no data + NSString *url = [self createURL:@"users" append2:userID append3:@"activities" append4:activityUUID]; + return [self httpTransaction:url op:kUGHTTPPost opData:nil]; +} + +-(UGClientResponse *)postGroupActivity:(NSString *)groupID activity:(NSDictionary *)activity +{ + // prep a multi-step action + UGMultiStepAction *action = [UGMultiStepAction new]; + + // set it up to start the create activity / post to user chain + [action setNextAction:kMultiStepCreateGroupActivity]; + [action setGroupID:groupID]; + [action setActivity:activity]; + + // fire it off + return [self multiStepAction:action]; +} + +-(UGClientResponse *)postGroupActivityByUUID: (NSString *)groupID activity:(NSString *)activityUUID +{ + // make the URL and fire off the post. there is no data + NSString *url = [self createURL:@"groups" append2:groupID append3:@"activities" append4:activityUUID]; + return [self httpTransaction:url op:kUGHTTPPost opData:nil]; +} + +-(UGClientResponse *)getActivitiesForUser: (NSString *)userID query:(UGQuery *)query +{ + NSMutableString *url = [self createURL:@"users" append2:userID append3:@"activities"]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)getActivityFeedForUser: (NSString *)userID query:(UGQuery *)query +{ + NSMutableString *url = [self createURL:@"users" append2:userID append3:@"feed"]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)getActivitiesForGroup: (NSString *)groupID query:(UGQuery *)query +{ + NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"activities"]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)getActivityFeedForGroup: (NSString *)groupID query:(UGQuery *)query +{ + NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"feed"]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)removeActivity:(NSString *)activityUUID +{ + NSString *url = [self createURL:@"activities" append2:activityUUID]; + return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; +} + +/************************** GROUP MANAGEMENT **************************/ +/************************** GROUP MANAGEMENT **************************/ +/************************** GROUP MANAGEMENT **************************/ +-(UGClientResponse *)createGroup:(NSString *)groupPath groupTitle:(NSString *)groupTitle +{ + // make the URL + NSString *url = [self createURL:@"groups"]; + + // make the post data we'll be sending along with it. + NSMutableDictionary *toPost = [NSMutableDictionary new]; + [toPost setObject:groupPath forKey:@"path"]; + if ( groupTitle ) + { + [toPost setObject:groupTitle forKey:@"title"]; + } + NSString *toPostStr = [self createJSON:toPost]; + + // fire it off + return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr]; +} + +-(UGClientResponse *)addUserToGroup:(NSString *)userID group:(NSString *)groupID +{ + // make the URL + NSString *url = [self createURL:@"groups" append2:groupID append3:@"users" append4:userID]; + + // fire it off. This is a data-less POST + return [self httpTransaction:url op:kUGHTTPPost opData:nil]; +} + +-(UGClientResponse *)removeUserFromGroup:(NSString *)userID group:(NSString *)groupID +{ + // this is identical to addUserToGroup, except we use the DELETE method instead of POST + // make the URL + NSString *url = [self createURL:@"groups" append2:groupID append3:@"users" append4:userID]; + + // fire it off. This is a data-less POST + return [self httpTransaction:url op:kUGHTTPDelete opData:nil];} + +-(UGClientResponse *)getUsersForGroup:(NSString *)groupID query:(UGQuery *)query +{ + // create the URL + NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"users"]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +/************************** ENTITY MANAGEMENT **************************/ +/************************** ENTITY MANAGEMENT **************************/ +/************************** ENTITY MANAGEMENT **************************/ +// jsonify the entity. If there's an error, it creates a UGClientResponse and +// returns it. If there's no error, it returns nil, and the outJson field and +// the type field will be set correctly. +// yes, it's odd to have a function return nil on success, but it's internal. +-(UGClientResponse *)validateEntity:(NSDictionary *)newEntity outJson:(NSString **)jsonStr outType:(NSString **)type +{ + // validation + NSString *error = nil; + + // the entity must exist + if ( !newEntity ) + { + error =@"entity is nil"; + } + + // the entity must have a "type" field + *type = [newEntity valueForKey:@"type"]; + if ( !*type ) + { + error = @"entity is missing a type field"; + } + + // make sure it can parse to a json + SBJsonWriter *writer = [SBJsonWriter new]; + NSError *jsonError; + *jsonStr = [writer stringWithObject:newEntity error:&jsonError]; + if ( !*jsonStr ) + { + error = [jsonError localizedDescription]; + } + + // if error got set to anything, it means we failed + if ( error ) + { + UGClientResponse *ret = [UGClientResponse new]; + [ret setTransactionState:kUGClientResponseFailure]; + [ret setTransactionID:-1]; + [ret setResponse:error]; + [ret setRawResponse:nil]; + return ret; + } + + // if we're here, it's a good json and we're done + return nil; +} + +-(UGClientResponse *)createEntity:(NSDictionary *)newEntity +{ + NSString *jsonStr; + NSString *type; + UGClientResponse *errorRet = [self validateEntity:newEntity outJson:&jsonStr outType:&type]; + if ( errorRet ) return errorRet; + + // we have a valid entity, ready to post. Make the URL + NSString *url = [self createURL:type]; + + // post it + return [self httpTransaction:url op:kUGHTTPPost opData:jsonStr]; +} + +-(UGClientResponse *)getEntities: (NSString *)type query:(UGQuery *)query +{ + NSMutableString *url = [self createURL:type]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)updateEntity: (NSString *)entityID entity:(NSDictionary *)updatedEntity +{ + NSString *jsonStr; + NSString *type; + UGClientResponse *errorRet = [self validateEntity:updatedEntity outJson:&jsonStr outType:&type]; + if ( errorRet ) return errorRet; + + // we have a valid entity, ready to post. Make the URL + NSString *url = [self createURL:type append2:entityID]; + + // post it + return [self httpTransaction:url op:kUGHTTPPut opData:jsonStr]; +} + +-(UGClientResponse *)removeEntity: (NSString *)type entityID:(NSString *)entityID +{ + // Make the URL, then fire off the delete + NSString *url = [self createURL:type append2:entityID]; + return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; +} + + +-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType connecteeType:(NSString *)connecteeType connecteeID:(NSString *)connecteeID +{ + NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeType append5:connecteeID]; + return [self httpTransaction:url op:kUGHTTPPost opData:nil]; +} + +-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID +{ + NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeID]; + return [self httpTransaction:url op:kUGHTTPPost opData:nil]; +} + +-(UGClientResponse *)disconnectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID +{ + NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeID]; + return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; +} + +-(UGClientResponse *)getEntityConnections: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType query:(UGQuery *)query +{ + NSMutableString *url = [self createURL:connectorType append2:connectorID append3:connectionType]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPPost opData:nil]; +} + +/************************** MESSAGE MANAGEMENT **************************/ +/************************** MESSAGE MANAGEMENT **************************/ +/************************** MESSAGE MANAGEMENT **************************/ +-(UGClientResponse *)postMessage: (NSString *)queuePath message:(NSDictionary *)message +{ + // because the NSDictionary is from the client, we can't trust it. We need + // to go through full error checking + NSString *error; + NSString *jsonStr = [self createJSON:message error:&error]; + + if ( !jsonStr ) + { + // report the error + UGClientResponse *ret = [UGClientResponse new]; + [ret setTransactionID:-1]; + [ret setTransactionState:kUGClientResponseFailure]; + [ret setResponse:error]; + [ret setRawResponse:nil]; + return ret; + } + + // make the path and fire it off + NSString *url = [self createURL:@"queues" append2:queuePath]; + return [self httpTransaction:url op:kUGHTTPPost opData:jsonStr]; +} + +-(UGClientResponse *)getMessages: (NSString *)queuePath query:(UGQuery *)query; +{ + NSMutableString *url = [self createURL:@"queues" append2:queuePath]; + [self appendQueryToURL:url query:query]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +-(UGClientResponse *)addSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath +{ + NSString *url = [self createURL:@"queues" append2:queuePath append3:@"subscribers" append4:subscriberPath]; + return [self httpTransaction:url op:kUGHTTPPost opData:nil]; +} + +-(UGClientResponse *)removeSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath +{ + NSString *url = [self createURL:@"queues" append2:queuePath append3:@"subscribers" append4:subscriberPath]; + return [self httpTransaction:url op:kUGHTTPDelete opData:nil]; +} + +/*************************** REMOTE PUSH NOTIFICATIONS ***************************/ +/*************************** REMOTE PUSH NOTIFICATIONS ***************************/ +/*************************** REMOTE PUSH NOTIFICATIONS ***************************/ + +- (UGClientResponse *)setDevicePushToken:(NSData *)newDeviceToken forNotifier:(NSString *)notifier +{ + // Pull the push token string out of the device token data + NSString *tokenString = [[[newDeviceToken description] + stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] + stringByReplacingOccurrencesOfString:@" " withString:@""]; + + // Register device and push token to App Services + NSString *deviceId = [UGClient getUniqueDeviceID]; + + // create/update device - use deviceId for App Services entity UUID + NSMutableDictionary *entity = [[NSMutableDictionary alloc] init]; + [entity setObject: @"device" forKey: @"type"]; + [entity setObject: deviceId forKey: @"uuid"]; + + NSString *notifierKey = [notifier stringByAppendingString: @".notifier.id"]; + [entity setObject: tokenString forKey: notifierKey]; + + return [self updateEntity: deviceId entity: entity]; +} + +- (UGClientResponse *)pushAlert:(NSString *)message + withSound:(NSString *)sound + to:(NSString *)path + usingNotifier:(NSString *)notifier +{ + NSDictionary *apsDict = [NSDictionary dictionaryWithObjectsAndKeys: + message, @"alert", + sound, @"sound", + nil]; + + NSDictionary *notifierDict = [NSDictionary dictionaryWithObjectsAndKeys: + apsDict, @"aps", + nil]; + + NSDictionary *payloadsDict = [NSDictionary dictionaryWithObjectsAndKeys: + notifierDict, notifier, + nil]; + + NSString *notificationsPath = [path stringByAppendingString: @"/notifications"]; + + NSMutableDictionary *entity = [[NSMutableDictionary alloc] init]; + [entity setObject: notificationsPath forKey: @"type"]; + [entity setObject: payloadsDict forKey: @"payloads"]; + + return [self createEntity: entity]; +} + + +/*************************** SERVER-SIDE STORAGE ***************************/ +/*************************** SERVER-SIDE STORAGE ***************************/ +/*************************** SERVER-SIDE STORAGE ***************************/ ++(NSString *)getUniqueDeviceID +{ + // cached? + if (g_deviceUUID) return g_deviceUUID; + + // in our keychain? + g_deviceUUID = [SSKeychain passwordForService:@"Usergrid" account:@"DeviceUUID"]; + if (g_deviceUUID) return g_deviceUUID; + + // in the (legacy) app defaults? + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + g_deviceUUID = [defaults valueForKey:@"UGClientDeviceUUID"]; + + // if none found in storage, generate one + if (!g_deviceUUID) { + + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")) { + // use identifierForVendor where possible + g_deviceUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString]; + } + else { + // otherwise, create a UUID (legacy method) + CFUUIDRef uuidRef = CFUUIDCreate(nil); + CFStringRef uuidStringRef = CFUUIDCreateString(nil, uuidRef); + CFRelease(uuidRef); + g_deviceUUID = [NSString stringWithString:(__bridge NSString *)uuidStringRef]; + } + } + + // store in keychain for future reference + [SSKeychain setPassword:g_deviceUUID forService:@"Usergrid" account:@"DeviceUUID"]; + + return g_deviceUUID; +} + +-(UGClientResponse *)setRemoteStorage: (NSDictionary *)data +{ + // prep and validate the sent-in dict + NSString *error; + NSString *jsonStr = [self createJSON:data error:&error]; + if ( !jsonStr ) + { + // report the error + UGClientResponse *ret = [UGClientResponse new]; + [ret setTransactionID:-1]; + [ret setTransactionState:kUGClientResponseFailure]; + [ret setResponse:error]; + [ret setRawResponse:nil]; + return ret; + } + + NSString *handsetUUID = [UGClient getUniqueDeviceID]; + NSString *url = [self createURL:@"devices" append2:handsetUUID]; + + // this is a put. We replace whatever was there before + return [self httpTransaction:url op:kUGHTTPPut opData:jsonStr]; +} + +-(UGClientResponse *)getRemoteStorage +{ + NSString *handsetUUID = [UGClient getUniqueDeviceID]; + NSString *url = [self createURL:@"devices" append2:handsetUUID]; + return [self httpTransaction:url op:kUGHTTPGet opData:nil]; +} + +/***************************** OBLIQUE USAGE ******************************/ +-(UGClientResponse *)apiRequest: (NSString *)url operation:(NSString *)op data:(NSString *)opData +{ + // work out the op to use + int opID = kUGHTTPGet; + if ( [op isEqualToString:@"GET"] ) opID = kUGHTTPGet; + if ( [op isEqualToString:@"POST"] ) opID = kUGHTTPPost; + if ( [op isEqualToString:@"POSTFORM"] ) opID = kUGHTTPPostAuth; + if ( [op isEqualToString:@"PUT"] ) opID = kUGHTTPPut; + if ( [op isEqualToString:@"DELETE"] ) opID = kUGHTTPDelete; + + // fire it off. The data, formatting, etc. is all the client's problem. + // That's the way oblique functionality is. + return [self httpTransaction:url op:opID opData:opData]; +} + +/**************************** LOGGING ************************************/ +-(void)setLogging: (BOOL)loggingState +{ + m_bLogging = loggingState; +} + +@end + + http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClientDelegate.h ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGClientDelegate.h b/sdks/other/ios/UGAPI/UGClientDelegate.h new file mode 100644 index 0000000..28bc996 --- /dev/null +++ b/sdks/other/ios/UGAPI/UGClientDelegate.h @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> +#import "UGClientResponse.h" + +/******************************A NOTE ON THIS DELEGATE******************************** +Objects conform to this protocol to take advantage of the Asynchronus SDK functionality. +The setDelegate method needs to be called on the current UGClient for this function to +be called on an implemented delegate. + +If you do not set a delegate, all functions will run synchronously, blocking +until a response has been received or an error detected. +*************************************************************************************/ +@protocol UGClientDelegate <NSObject> + +//This method is called after every request to the UserGrid API. +//It passes in the response to the API request, and returns nothing. +-(void)ugClientResponse:(UGClientResponse *)response; + +@end http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClientResponse.h ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGClientResponse.h b/sdks/other/ios/UGAPI/UGClientResponse.h new file mode 100755 index 0000000..01bc5a1 --- /dev/null +++ b/sdks/other/ios/UGAPI/UGClientResponse.h @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> + + +// response states +enum +{ + kUGClientResponseSuccess = 0, + kUGClientResponseFailure = 1, + kUGClientResponsePending = 2 +}; + + +@interface UGClientResponse : NSObject + +// this will be a unique ID for this transaction. If you have +// multiple transactions in progress, you can keep track of them +// with this value. Note: The transaction ID of a synchronous +// call response is always -1. +@property int transactionID; + +// this will be one of three possible valuse: +// kUGClientResponseSuccess: The operation is complete and was successful. response will +// be valid, as will rawResponse +// +// kUGClientResponseFailure: There was an error with the operation. No further +// processing will be done. response will be an NSString with +// a plain-text description of what went wrong. rawResponse +// will be valid if the error occurred after receiving data from +// the service. If it occurred before, rawResponse will be nil. +// +// kUGClientResponsePending: The call is being handled asynchronously and not yet complete. +// response will be nil. rawResponse will also be nil +@property int transactionState; + +// This is the response. The type of this variable is dependant on the call that caused +// this response. +@property id response; + +// This is the raw text that was returned by the server. +@property NSString *rawResponse; + +@end http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClientResponse.m ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGClientResponse.m b/sdks/other/ios/UGAPI/UGClientResponse.m new file mode 100755 index 0000000..dc49fb4 --- /dev/null +++ b/sdks/other/ios/UGAPI/UGClientResponse.m @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "UGClientResponse.h" + +@implementation UGClientResponse + +@synthesize transactionID; +@synthesize transactionState; +@synthesize response; +@synthesize rawResponse; + +-(id)init +{ + self = [super init]; + if ( self ) + { + transactionID = -1; + transactionState = -1; + response = nil; + rawResponse = nil; + } + return self; +} + +@end http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGHTTPManager.h ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGHTTPManager.h b/sdks/other/ios/UGAPI/UGHTTPManager.h new file mode 100755 index 0000000..d6c1822 --- /dev/null +++ b/sdks/other/ios/UGAPI/UGHTTPManager.h @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> + +enum +{ + kUGHTTPGet = 0, + kUGHTTPPost = 1, + kUGHTTPPostAuth = 2, + kUGHTTPPut = 3, + kUGHTTPDelete = 4 +}; + +@interface UGHTTPManager : NSObject + +// blocks until a response is received, or until there's an error. +// in the event of a response, it's returned. If there's an error, +// the funciton returns nil and you can call getLastError to see what +// went wrong. +-(NSString *)syncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData; + +// sets up the transaction asynchronously. The delegate that's sent in +// must have the following functions: +// +// -(void)httpManagerError:(UGHTTPManager *)manager error:(NSString *)error +// -(void)httpManagerResponse:(UGHTTPManager *)manager response:(NSString *)response +// +// In all cases, it returns a transaction ID. A return value +// of -1 means there was an error. +// You can call getLastError to find out what went wrong. +-(int)asyncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData delegate:(id)delegate; + +// get the current transactionID +-(int)getTransactionID; + +// sets the auth key +-(void)setAuth: (NSString *)auth; + +// cancel a pending transaction. The delegate will not be called and the results +// will be ignored. Though the server side will still have happened. +-(void)cancel; + +// returns YES if this instance is available. NO if this instance is currently +// in use as part of an asynchronous transaction. +-(BOOL)isAvailable; + +// sets the availability flag of this instance. This is done by UGClient +-(void)setAvailable:(BOOL)available; + +// a helpful utility function to make a string comform to URL +// rules. It will escape all the special characters. ++(NSString *)escapeSpecials:(NSString *)raw; + +// At all times, this will return the plain-text explanation of the last +// thing that went wrong. It is cleared to "No Error" at the beginnign of +// each new transaction. +-(NSString *)getLastError; + +@end http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGHTTPManager.m ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGHTTPManager.m b/sdks/other/ios/UGAPI/UGHTTPManager.m new file mode 100755 index 0000000..5d62fe9 --- /dev/null +++ b/sdks/other/ios/UGAPI/UGHTTPManager.m @@ -0,0 +1,328 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "UGHTTPManager.h" + +// all transaction IDs are unique across all UGHTTPManagers. +// this global increases every time there's an asynchronous +// transaction. +static int g_nextTransactionID = 1; + +// a mutex to protect against multiple threads getting +// IDs at the same time, and possibly getting the same ID +// because of it +NSRecursiveLock *g_transactionIDLock = nil; + +@implementation UGHTTPManager +{ + // data management for collecting incoming HTTP data + // during asynch transactions + NSMutableData *m_receivedData; + + // a general error string + NSString *m_lastError; + + // the delegate sent in to the asynch method + id m_delegate; + + // the transaction ID of the current (or most recent) transaction + int m_transactionID; + + // availability of this instance. Managed by UGClient + BOOL m_bAvailable; + + // mutex used to ensure the delegate is not changed + // in a way that would cause a race condition. + NSRecursiveLock *m_delegateLock; + + // the auth key to send along to requests + NSString *m_auth; +} + +-(id)init +{ + self = [super init]; + if ( self ) + { + m_lastError = @"No error"; + m_receivedData = [NSMutableData data]; + m_bAvailable = YES; + m_delegateLock = [NSRecursiveLock new]; + m_transactionID = -1; + m_auth = nil; + + // lazy-init the transaction lock + if ( !g_transactionIDLock ) + { + g_transactionIDLock = [NSRecursiveLock new]; + } + } + return self; +} + +-(void)setAuth: (NSString *)auth +{ + m_auth = auth; +} + +-(NSString *)getLastError +{ + return m_lastError; +} + +-(int)getNextTransactionID +{ + // make sure we can use this lock + assert(g_transactionIDLock); + + [g_transactionIDLock lock]; + int ret = g_nextTransactionID++; + [g_transactionIDLock unlock]; + + return ret; +} + +-(int)getTransactionID +{ + return m_transactionID; +} + + +//----------------------- SYNCHRONOUS CALLING ------------------------ +-(NSString *)syncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData; +{ + // clear the transaction ID + m_transactionID = -1; + + // use the synchronous funcitonality of NSURLConnection + // clear the error + m_lastError = @"No error"; + + // formulate the request + NSURLRequest *req = [self getRequest:url operation:op operationData:opData]; + + NSURLResponse *response; + NSError *error; + NSData *resultData = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error]; + + if ( resultData ) + { + // we got results + NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding]; + return resultString; + } + + // if we're here, it means we got nil as the result + m_lastError = [error localizedDescription]; + return nil; +} + +//----------------------- ASYNCHRONOUS CALLING ------------------------ +-(int)asyncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData delegate:(id)delegate; +{ + // clear the transaction ID + m_transactionID = -1; + + // clear the error + m_lastError = @"No error"; + + if ( !delegate ) + { + // an asynch transaction with no delegate has no meaning + m_lastError = @"Delegate was nil"; + return -1; + } + + // make sure the delegate responds to the various messages that + // are required + if ( ![delegate respondsToSelector:@selector(httpManagerError:error:)] ) + { + m_lastError = @"Delegate does not have httpManagerError:error: method"; + return -1; + } + if ( ![delegate respondsToSelector:@selector(httpManagerResponse:response:)] ) + { + m_lastError = @"Delegate does not have httpManagerResponse:response: method"; + return -1; + } + + // only once we're assured everything is right do we set the internal value + [m_delegateLock lock]; + m_delegate = delegate; + [m_delegateLock unlock]; + + // prep a transaction ID for this transaction + m_transactionID = [self getNextTransactionID]; + + // formulate the request + NSURLRequest *req = [self getRequest:url operation:op operationData:opData]; + + // fire it off + NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self]; + + // Note failure + if ( !conn ) + { + // failed to connect + m_lastError = @"Unable to initiate connection."; + return -1; + } + + // success + return m_transactionID; +} + +-(BOOL)isAvailable +{ + return m_bAvailable; +} + +-(void)setAvailable:(BOOL)available +{ + m_bAvailable = available; +} + +-(void)cancel +{ + // we wrap this in a lock to ensure that the client can + // call it at any time. It will not cause a race condition in + // any callback thread. + [m_delegateLock lock]; + m_delegate = nil; + [m_delegateLock unlock]; + + // note that we do not modify the "in use" flag. If we werei n use, + // we remain in use until we receive a response or error. This ensures + // no confusion on any subsequent transaction. We don't want the case + // where a transaction is started, then cancelled, then a new transaction begun + // before the first transaction's result comes in. That would lead to the second + // transaction being answered with the first's reply. We avoid that possibility by + // simply remaining "in use" until we get the reply or error. +} + +// general helper function form aking escaped-strings ++(NSString *)escapeSpecials:(NSString *)raw; +{ + NSString *converted = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)raw, nil, CFSTR(";/?:@&=$+{}<>"), kCFStringEncodingUTF8); + return converted; +} + +// INTERNAL function for forming the request +-(NSURLRequest *)getRequest:(NSString *)url operation:(int)op operationData:(NSString *)opStr; +{ + // make the url + NSURL *nsurl = [NSURL URLWithString:url]; + + NSMutableURLRequest *req = [NSMutableURLRequest new]; + [req setURL:nsurl]; + + switch ( op ) + { + case kUGHTTPGet: [req setHTTPMethod:@"GET"]; break; + case kUGHTTPPost: [req setHTTPMethod:@"POST"]; break; + case kUGHTTPPostAuth: [req setHTTPMethod:@"POST"]; break; + case kUGHTTPPut: [req setHTTPMethod:@"PUT"]; break; + case kUGHTTPDelete: [req setHTTPMethod:@"DELETE"]; break; + } + + // set the auth, if any is available + if ( m_auth ) + { + NSMutableString *authStr = [NSMutableString new]; + [authStr appendFormat:@"Bearer %@", m_auth]; + [req setValue:authStr forHTTPHeaderField:@"Authorization"]; + } + + // if they sent an opStr, we make that the content + if ( opStr ) + { + // prep the post data + NSData *opData = [opStr dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; + + // make a string that tells the length of the post data. We'll need that for the HTTP header setup + NSString *opLength = [NSString stringWithFormat:@"%d", [opData length]]; + + [req setValue:opLength forHTTPHeaderField:@"Content-Length"]; + + // PostAuth uses form encoding. All other operations use json + if ( op == kUGHTTPPostAuth ) + { + [req setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + } + else + { + [req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + } + + [req setHTTPBody:opData]; + } + + // all set up and ready for use + return req; + +} + +//------------------------------------------------------------------------------------------ +//-------------------------- NSURLCONNECTION DELEGATE METHODS ------------------------------ +//------------------------------------------------------------------------------------------ +-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + // got a response. clear out the data + [m_receivedData setLength:0]; +} + +-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + // got some data. Append it. + [m_receivedData appendData:data]; +} + +-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + // connection failed. Note the error + m_lastError = [error localizedDescription]; + + // send the error to the delegate. We wrap this in + // send the result to the delegate. + // wrap this in mutex locks, then check for validity of m_delegate inside. + // this ensures no race conditions and allows arbitrary cancellation of callbacks + [m_delegateLock lock]; + if ( m_delegate ) + { + [m_delegate performSelector:@selector(httpManagerError:error:) withObject:self withObject:m_lastError]; + } + m_delegate = nil; + [m_delegateLock unlock]; +} + +-(void)connectionDidFinishLoading:(NSURLConnection*)connection +{ + // all done. Let's turn it in to a string + NSString *resultString = [[NSString alloc] initWithData:m_receivedData encoding:NSUTF8StringEncoding]; + + // send it to the delegate. See connection:didFailWithError: for an explanation + // of the mutex locks + [m_delegateLock lock]; + if ( m_delegate ) + { + [m_delegate performSelector:@selector(httpManagerResponse:response:) withObject:self withObject:resultString]; + } + m_delegate = nil; + [m_delegateLock unlock]; +} + +@end http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGMultiStepAction.h ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGMultiStepAction.h b/sdks/other/ios/UGAPI/UGMultiStepAction.h new file mode 100755 index 0000000..fbc1259 --- /dev/null +++ b/sdks/other/ios/UGAPI/UGMultiStepAction.h @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> + +// an enumeration for multi-step processes. +enum +{ + kMultiStepNone = 0, + kMultiStepCreateActivity = 1, // create an activity + kMultiStepPostActivity = 2, // after creating an activity, post it to the user + kMultiStepCreateGroupActivity = 3, // create an activity + kMultiStepPostGroupActivity = 4, // after creating an activity, post it to the user + kMultiStepCleanup = 5 // the final step of all multi-step transactions +}; + +// UGMultiStepAction is used internally for client actions that +// require multiple transactions with the service. It is simple data +// storage, used by UGClient in abstracting out multi-step transactions +@interface UGMultiStepAction : NSObject + +// the transaction ID that this multistep is associated +// with. When a transaction of this ID is complete, this is the +// UGMultiStepAction instance ot ask what to do next +@property int transactionID; + +// the next action this transaction should take +@property int nextAction; + +// data necessary for subsequent steps +@property NSString *userID; +@property NSString *groupID; +@property NSDictionary *activity; + +// the transactionID that will be sent to the user. +// This is distinct from normal transaction IDs, +// which we use internally at each step. +@property int outwardTransactionID; + +// YES if this action should be reported to the +// caller .No if not. +@property BOOL reportToClient; + +@end http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGMultiStepAction.m ---------------------------------------------------------------------- diff --git a/sdks/other/ios/UGAPI/UGMultiStepAction.m b/sdks/other/ios/UGAPI/UGMultiStepAction.m new file mode 100755 index 0000000..6a1b7c7 --- /dev/null +++ b/sdks/other/ios/UGAPI/UGMultiStepAction.m @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "UGMultiStepAction.h" + +@implementation UGMultiStepAction + +@synthesize transactionID; +@synthesize nextAction; +@synthesize userID; +@synthesize groupID; +@synthesize activity; +@synthesize outwardTransactionID; +@synthesize reportToClient; + + +-(id)init +{ + self = [super init]; + if ( self ) + { + reportToClient = NO; + } + return self; +} +@end
