Archives

All posts for the month January, 2011

A few days ago I had to change some file permissions on my freenas server to allow ssh to work. In doing so I could no longer login to the AFP share from my desktop. The strange thing was I could not login from bonjour ‘Shared’ panel on the left of the Finder window but I could connect through Finder in the Go–>Connect. After some investigation I deleted the following files from the root of the share folder on the Freenas.

  1. .AppleDB
  2. .AppleDesktop
  3. .AppleDouble
  4. .TemporaryItems
  5. Network Trash Folder
  6. Temporary Items
  7. .DS_Store

Restarting the AFP service on the Freenas restored the files and everything was back to normal.

After further investigation I realized by looking at the logs that Error Code -5014 refers to a corrupt .AppleDB file. I only needed to delete the .AppleDB file and resart the AFP service to rebuild the database.

While working on a mp4 meta data tagger application, I wanted to use the Amazon Web service API to get the meta data info for the video files.  I remember seeing an example of this in one of my readings.  I found it as one of the chapters in Aaron Hillgass book Cocoa(R) Programming for Mac(R) OS X (3rd Edition).  However, the info was somewhat outdated as Amazon has changed the Restful request requirements.  What follows below is how I accomplished this.  While I would like to claim most of this work my own, that would not be true.  The vast majority of the info was garnered from this post on the bignerd ranch forum.  I simply clarified, adapted and expanded the info to present in this page. The following is the AmaZone project from the book adapted to grab video information.  I would highly suggest reading the chapter to get an idea of what needs to be accomplished.

Creating a Cocoa application that accesses Amazon’s web service requires a quite of bit of coding and time.  Below is a list of requirements to get this going.

1.  Sign up for Amazon Web Service (AWS).

2.  Your Access Key and Secret Key from the AWS service. These can be found under the Account tab Security Credentials.

AWS can be accessed through SOAP or Restful request.  The Restful services is the newer of the two services ad is probably the simplest route to go.  To access the service you will use your Access Key, which any one can see, and your secret key, which is used as part of a Base64 encoding scheme to produce a signature.  Through this process, AWS can assure that you are who you claim to be and no one is tampering with your request.

Open the project in Xcode.

To make this process simpler, start by using the existing code from the book found here on the download examples button.  Once the download is complete, navigate to the AmaZone directory and open the project file.

amazonepict.jpg

Adding the OpenSSL libcrypto.dylib to the AmaZone project.

Libcrypto.dylib is part of the OpenSSL Cryptography and SSL/TLS ToolKit and is included in the Xcode developer Frameworks. We will use this framework to pass the html request to for encoding into a Base64 string. This will create the signature necessary for the Restful request to be successful.  To add it, simply right click on Frameworks in the Groups & Files pane in Xcode and Select Add –> Existing Frameworks.  In the frameworks search window it may be helpful to filter the search to “Dylibs” in the drop down menu.  This should bring up libcrypto.dylib and selecting it should add it to the projects frameworks.

framworkssearch.jpg

Making Changes to the AppController file.

In the AppController.m file add the following #includes.

#import <openssl/ssl.h>//for BIO, etc

#import <CommonCrypto/CommonHMAC.h>//for kCCHmacAlgSHA256

#import <CommonCrypto/CommonDigest.h>//for CC_SHA256_DIGEST_LENGTH

 

Also add your Access Key and Secret Key.

#define AWS_ID @“AKXAJDX2BXQWOKM47XXX”

#define SECRET_KEY @“v111+11111u1i11111hhxVYvzPC1111111111111″

Now we need two things to happen

1.  encode the string into a Base64String.

2.  Make an NSString from the Base64String.

To accomplish this create two categories.

While still in AppController.m we add a method to NSData to accomplish the encoding.  This is done this by creating a Category for NSData .

Insert this before the implementation statement for AppController.

@interface NSData (Base64)

- (NSString *)encodeBase64;

- (NSString *)encodeBase64WithNewlines: (BOOL) encodeWithNewlines;

@end

@implementation NSData (Base64)

- (NSString *)encodeBase64

{

return [selfencodeBase64WithNewlines: NO];

}

- (NSString *)encodeBase64WithNewlines: (BOOL) encodeWithNewlines

{

// Create a memory buffer which will contain the Base64 encoded string

BIO * mem = BIO_new(BIO_s_mem());

// Push on a Base64 filter so that writing to the buffer encodes the data

BIO * b64 = BIO_new(BIO_f_base64());

if (!encodeWithNewlines)

BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

mem = BIO_push(b64, mem);

// Encode all the data

BIO_write(mem, [selfbytes], [selflength]);

BIO_flush(mem);

// Create a new string from the data in the memory buffer

char * base64Pointer;

BIO_get_mem_data(mem, &base64Pointer);

// deprecated

// long base64Length = BIO_get_mem_data(mem, &base64Pointer);

// NSString * base64String = [NSString stringWithCString: base64Pointer

// length: base64Length];

BIO_get_mem_data(mem, &base64Pointer);

NSString * base64String = [NSString stringWithUTF8String: base64Pointer];

// Clean up and go home

BIO_free_all(mem);

return base64String;

}

@end

Now we need to add a method to NSString so we can take the encoded base64String and form a proper URL. Again this is added before the @implementation statement in the AppController.m file.

@interface NSString (ForURL)

- (NSString*)reallyEncodeURL;

@end

@implementation NSString (ForURL)

- (NSString*)reallyEncodeURL

{

return (NSString*)CFURLCreateStringByAddingPercentEscapes(

NULL,

(CFStringRef)self,

NULL,

(CFStringRef)@”!*’();:@&=+$,/?%#[]”,

kCFStringEncodingUTF8 );

}

@end

Now we need to direct the old fetchBooks method to use the encoding.  Here I also varied from the original AmaZone.  The original AmaZone was a book search while this is a video search.  If you notice in the response_group I included the cover image as well as the Editorial review (summary).

Replace the old fetchBooks method with this one.

- (void)fetchBooks:(id)sender

{

// Show the user that something is going on

[progressstartAnimation:nil];

// Put together the request

// See http://www.amazon.com/gp/aws/landing.html

// Get the string and percent-escape for insertion into URL

NSString *searchString = [[searchFieldstringValue] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSLog(@”searchString = %@”, searchString);

// Create the URL

//Timestamp format : needs to be yyyy-mm-ddThh:mm+/-timediff , it’s also set to tomorrow to avoid Request Expired error.

NSDate *now = [[NSDatealloc] initWithTimeIntervalSinceNow:24 * 60 * 60];

NSDateFormatter *dateFormatter = [[NSDateFormatteralloc] init];

[dateFormatter setDateFormat:@”yyyy-MM-dd’T'hh:mm:ssZ”];

NSString *timestamp = [dateFormatter stringFromDate:now];

NSString * encoded_timestamp = [timestamp reallyEncodeURL];

NSLog(@”Date formatted %@ => %@”, timestamp, encoded_timestamp);

Your ads will be inserted here by

Easy AdSense.

Please go to the plugin admin page to
Paste your ad code OR
Suppress this ad slot OR
Suppress Placement Boxes.

// parameters have to be binary sorted

NSString *service_URL = @”ecs.amazonaws.com”;

NSString *service_path = @”/onca/xml”;

NSString *access_ID = [NSString stringWithFormat:@”AWSAccessKeyId=%@&”,AWS_ID];

NSString *keywords = [NSString stringWithFormat:@”Keywords=%@&”,searchString];

NSString *operation = @”Operation=ItemSearch&”;

NSString *response_group = @”ResponseGroup=ItemAttributes%2CImages%2CEditorialReview&”;

NSString *search_index = @”SearchIndex=Video&”;

NSString *service = @”Service=AWSEcommerceService&”;

NSString *signature_method = @”SignatureMethod=HmacSHA256&”;

NSString *signature_version = @”SignatureVersion=2&”;

NSString *ts = [NSString stringWithFormat:@”Timestamp=%@&”, encoded_timestamp];

NSString *version = @”Version=2010-01-01″;

//Signature

NSString *string_to_sign = [NSStringstringWithFormat:@”GETn%@n%@n%@%@%@%@%@%@%@%@%@%@”,

service_URL, service_path, access_ID, keywords, operation, response_group,

search_index, service, signature_method, signature_version, ts, version];

NSLog(@”String to sign = %@”, string_to_sign);

NSString *key = SECRET_KEY;

NSString *data = string_to_sign;

constchar *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];

constchar *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];

unsignedchar cHMAC[CC_SHA256_DIGEST_LENGTH];

CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC

length:sizeof(cHMAC)];

NSString *hash = [HMAC encodeBase64];

NSString *encoded_hash = [hash reallyEncodeURL];

NSLog(@”hash = %@- => %@”, hash, encoded_hash);

// Create the URL

NSString *urlString = [NSStringstringWithFormat:@”http://%@%@?%@%@%@%@%@%@%@%@%@%@&Signature=%@”,

service_URL, service_path, access_ID, keywords, operation, response_group,

search_index, service, signature_method, signature_version, ts, version, encoded_hash];

NSLog(@”urlString = %@”, urlString); // you can paste this in your web browser to see the XML returned

NSURL *url = [NSURL URLWithString:urlString];

NSURLRequest *urlRequest = [NSURLRequestrequestWithURL:url

cachePolicy:NSURLRequestReturnCacheDataElseLoad

timeoutInterval:30];

// Fetch the XML response

NSData *urlData;

NSURLResponse *response;

NSError *error;

urlData = [NSURLConnection sendSynchronousRequest:urlRequest

returningResponse:&response

error:&error];

if (!urlData) {

NSRunAlertPanel(@”Error loading”, @”%@”, nil, nil, nil, [error localizedDescription]);

return;

}

// Parse the XML response

[docrelease];

doc = [[NSXMLDocument alloc] initWithData:urlData

options:0

error:&error];

NSLog(@”doc = %@”, doc);

//ShowTree(doc, 0);

if (!doc) {

NSAlert *alert = [NSAlert alertWithError:error];

[alert runModal];

return;

}

[itemNodesrelease];

itemNodes = [[docnodesForXPath:@”ItemSearchResponse/Items/Item”error:&error] retain];

if (!itemNodes) {

NSAlert *alert = [NSAlert alertWithError:error];

[alert runModal];

return;

}

// Update the interface

[tableViewreloadData];

[progressstopAnimation:nil];

}

We also need to change the tableview data source methods.

- (id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row

{

NSXMLNode *node = [itemNodes objectAtIndex:row];

NSString *xPath = [tableColumn identifier];

NSLog(@”XPATH = %@”, xPath);

if ([[tableColumn identifier] isEqual:@”Image”])

{

NSString *imageXPath = @”SmallImage/URL”;

NSString *urlString = [self stringForPath:imageXPath ofNode:node];

if (urlString) {

NSURL *url = [NSURL URLWithString:urlString];

NSImage *image = [[NSImagealloc] initWithContentsOfURL:url];

return image;

} else {

returnnil;

}

}

return [selfstringForPath:xPath ofNode:node];

}

@end

Since we have changed the content to video and added images as well as Editorial reviews we need to add two columns in the Tableview.

AmazoneUI.jpg

Add 2 columns.  One with header titled images the other titled Description.  Drag an image cell from the UI library to the Image column. Now run the application and you should get results for your video.

When developing plugin for Quartz composer it is not possible to use a modal window for NSOpenPanel. You must also subclass the QCPluginViewController class

For OS X 10.5 and below

-(IBAction)importFile:(id)sender

{

fileTypesArray = [[NSArray alloc]initWithObjects:@”aac”,@”aiff”,@”wav”,@”wma”,@”mp3″, nil];

NSOpenPanel *panel  = [[NSOpenPanel openPanel]retain];

//Run the open panel

[panel beginForDirectory:nil file:nil types:fileTypesArray modelessDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo :) contextInfo:NULL];

}

 

-(void)openPanelDidEnd:(NSOpenPanel*)sheet

returnCode:(int)returnCode

contextInfo:(void*)contextInfo

{


//Did they choose open?

if (returnCode == NSOKButton)

{

NSString *path = [sheet filename];

NSFileManager *fileManager = [NSFileManager defaultManager];

self.fileUrl = [fileManager displayNameAtPath:path];

[sheet release];

}

}

 

The panel must be retained when called and released when user is finished.  If the window is not retained it will appear and disappear quickly.

If developing for OS X 10.6 use the following code as the above code is deprecated. Again make sure to retain (or at least a strong reference when using Garbage colection) the window and release when finished.

-(IBAction)chooseFile:(id)sender

{

NSOpenPanel *panel = [[NSOpenPanel openPanel] retain];

[panel setAllowedFileTypes:[NSSound soundUnfilteredTypes]];

NSLog(@”This is the list of supported Files %@”, [NSSound soundUnfilteredTypes]);

[panel setCanChooseFiles:YES];

[panel setCanChooseDirectories:YES];

[panel setCanCreateDirectories:NO];

[panel setAllowsMultipleSelection:NO];

//Run the open panel  Notice the panel no longer uses a delegate but the new C blocks.

[panel beginWithCompletionHandler:^void (NSInteger result) {

if (result == NSFileHandlingPanelCancelButton) {

return;

}

NSLog(@”This is the openpanel handle %@”,[[[panel URLs]objectAtIndex:0]path]);

 

//more code here……..


[panel release];

}];

 

 

}