I recently thought I'd venture into creating a mac client for flickr. I use flickr a lot but find the current selection of uploading tools pretty dismal.
Flickr's public API is quite extensive and comes in three flavours: REST, XML-RPC, and SOAP. Next I needed to see what Apple had in the way of Frameworks and I turned up the Web Services Core framework which supports both XML-RPC and SOAP.
I decided to use XML-RPC. While I was leaning towards using REST I was curious about this Framework. And well, I find SOAP too noisy - so XML-RPC it is.
Reading through the flickr api docs, particularly this page on writing a desktop application I saw that the first call to make to flickr was called getFrob. This method call, like most others, requires a parameter called api_sig that is an MD5 sum of our parameter list and our shared secret (obtained when you configure your API key with flickr).
Back to google, I had no idea how to do MD5 in Objective C. I found this page over at CocoaDev though that described the process quite nicely - pay particular attention to the part at the end about using stringWithFormat to generate a String representation of the hex based MD5 number.
NSString* apiKey = @"..."; NSString* sharedSecret = @"..."; NSString* methodName = @"flickr.auth.getFrob"; NSString* signatureString = [NSString stringWithFormat:@"%@api_key%@", sharedSecret, apiKey]; NSData *data = [signatureString dataUsingEncoding:NSUTF8StringEncoding]; NSMutableData *digest = [NSMutableData dataWithLength:MD5_DIGEST_LENGTH]; MD5([data bytes], [data length], [digest mutableBytes]); unsigned char* bytes = (unsigned char*)[digest bytes]; NSString* apiSig = [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]];
One caveat to remember, with XML-RPC you do not include the method name in the signature string as you do with the REST approach (which is what flickr uses for all their examples). Originally I had the method name in there as well and kept getting invalid signature responses back from flickr.
With my API signature generated I was ready to make a call to flickr. Unfortunately the docs at Apple weren't very clear so I googled and found some sample code from Ranchero Software here. Except it was archived in SIT format and macs no longer come with Stuffit... luckily the text contained all the code snippets I needed.
WSMethodInvocationRef rpcCall;
NSURL *rpcURL = [NSURL URLWithString: @"http://api.flickr.com/services/xmlrpc/"];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
apiSig, @"api_sig",
apiKey, @"api_key",
nil];
NSDictionary *result;
rpcCall = WSMethodInvocationCreate (
(CFURLRef) rpcURL,
(CFStringRef) methodName,
kWSXMLRPCProtocol);
WSMethodInvocationSetParameters (
rpcCall,
(CFDictionaryRef) [NSDictionary
dictionaryWithObject:params
forKey:@"blah"],
NULL);
result = (NSDictionary *) (WSMethodInvocationInvoke (rpcCall));
if (WSMethodResultIsFault ((CFDictionaryRef) result))
NSLog(@"Error: %@", [result objectForKey: (NSString *) kWSFaultString]);
else
NSLog(@"Success: %@", [result objectForKey: (NSString *) kWSMethodInvocationResult]);
Another caveat here. Note that I don't pass in the params dictionary as-is, I add it to another dictionary with a dummy key and pass that it. The reason is, flickr expects a single parameter of type struct. If you don't wrap the parameter dictionary in another dictionary you end up passing multiple unnamed parameters to flickr and it will simply complain that it can't find your api key. How did I figure this out? I downloaded this simple proxy written in Python and repointed my client to it. The proxy would echo both my request and flickr's response to the terminal.
I haven't decided if I will continue down the path of an XML-RPC based client or if I will use REST instead but the code here should give anyone struggling with Core Web Services a head start.
