Showing posts with label dev. Show all posts
Showing posts with label dev. Show all posts

Wednesday, July 30, 2008

Via vafer.org: Emailing from Cocoa

I decided to check the "interwebs" earlier this evening for some information on the current state of Cocoa SMTP client libraries.
I was very pleasantly surprised to read Torsten Curdt's great post on the subject. I hope to test Pantomime out as a possible replacement to JavaMail for all my SMTP needs. If SSL and Mime attachments work as Torsten shows in his example, I might be able to finally go 100% native Cocoa for the next release of iPhoto2Gmail and Aperture2Gmail... we'll see!

Things certainly look better than when I checked last year.
http://vafer.org/blog/20080604120118

Thursday, May 29, 2008

One step Universal Binary builds Compatible with 10.3 PPC with Xcode (for reals)

Dang. That was a long title. An alternate title could have been: how to misread Apple's Cross Development documentation.

Anyway, when I upgraded my main development system to Leopard 10.5, my Xcode 2.4 projects worked beatifully with Xcode 3.0. I didn't change anything, and builds were still working fine in 10.4 and 10.5. Alas, 10.3.9 users weren't having much luck with my builds. Hrmm...

I double checked the project and target build settings. The target SDK no longer read 10.3, instead 10.4u was selected. Disclaimer: I have no idea if this was something that happened by default, or if I had changed this at some point when I was still running 10.4.

Oh well, so I chose the 10.3.9 SDK for cross-development and tried building. No dice. A bazillion errors. Most notably:

stdbool.h:8: error: #error "This header only supports __MWERKS__."

Some more keyword fiddling with the Googles, and this thread saved the day. A helpful Apple engineer shed some light on the subtleties of Universal Binaries, 10.3, and PPC machines. In the same thread, a poster noted that Lipo was not necessary, just some special "Per-Architecture" Xcode build settings. Turns out adding per-architecture settings is now even easier with Xcode 3.

To build your app as a UB that is compatible back to 10.3.9 on PPC machines set Base SDK Path (Under Target...Build...Build Locations) to 10.3.9 for PPC and 10.4u for Intel.

Your Target's build settings should end up looking something like this:

Note: if you already set the Base SDK Project-wide to be 10.4u, then you can just add a Per-Architecture setting for PowerPC at the Target level.

Now you'll be able to compile just fine on Intel because you'll be using 10.4u, but PPC machines on 10.3.9 will still be able to run your app. Remember to be careful to use 10.3.9 APIs.

Thursday, May 22, 2008

Xcode 3.0 Snapshots: Notes on Repository Location

Man, I love this new feature. Being able to diff files to any of my previous Snapshots is superb. Being able to roll back changes on a single file or over the entire project is even better. I find I give myself more freedom to try new things because I know I have a good clean (compiling) Snapshot to fall back to. Scotty's Xcode Quick Tip is a great place to learn more about them.
--
I wanted to make sure that I was backing up the Xcode snapshot sparse image, so I went looking for it. Currently, Apple's docs say the file should be here:
~/Library/Application\ Support/Apple/Developer Tools/Snapshot Repository


Well, the location in the current documentation is wrong, the disk image really is here:
~/Library/Application\ Support/Developer/Shared


I've also found that the defaults command for changing the location of this magical disk image doesn't work for me on Xcode 3.0. Anybody have any tips?
defaults write com.apple.Xcode XCSnapshotRepositoryPath /Volumes/Backup/Xcode\ Snapshots/

Saturday, February 16, 2008

Google ClientLogin in Objective C

I recently wrote this function to replace libgmailer's role in iPhoto2Gmail and Aperture2Gmail as an authentication validator.
I looked into using the excellent GData APIs Objective-C Client Library, however it does not yet have a standalone "just authenticate" function.

If authentication is necessary for a particular GData request, the library authenticates. For my purposes (just check if this username/password combo is valid) I had to choose a specific service (like Calendar), and make a bogus request (get calendars). This might be OK if I could assume that everyone has "initialized" their Calendar account, which I can't. But it would still be a hack to do just for authentication purposes. Also, the library weighs in at close 500K, so it would double the size of my plugins if added. In any case, if the library ever adds official API for contacts (pretty, please Google!), then I will likely switch to using it, but for now, I created a simple function based on NSURLConnection. The roadmap hints that standard authentication methods and nibs will be added in the future.

Some folks may shy away from doing a synchronous network request, in my opinion it is totally valid in this scenario. The UI is showing a modal "Authenticating" sheet with an indeterminate progress bar, so what's wrong with blocking? If authentication does not complete succesfully, the plugin is useless anyway. Additionally, we have a timeout set. *And* by using sendSynchronousRequest: we eliminate the run loop requirements that NSURLConnection delegates have, and let NSURLConnection handle the threading as well -- this is key when developing plugins!

I wish I had a wiki so that folks could chime in and improve it until it's a nice lightweight class for authenticating... for now, the blog format will have to suffice.

Here's a link to the source, since the formatting below is a bit hard to read.


#define GOOGLE_CLIENT_AUTH_URL @"https://www.google.com/accounts/ClientLogin"
#define GOOGLE_CAPTCHA_STANDARD_UNLOCK_URL @"https://www.google.com/accounts/DisplayUnlockCaptcha"


- (NSArray*) authenticateWithGoogleClientLogin:(NSString *)userName withPassword:(NSString*)password {
/* Google clientLogin API:
Content-type: application/x-www-form-urlencoded
Email=userName
Passwd=password
accountType=HOSTED_OR_GOOGLE
service=xapi
source = @"myComp-myApp-1.0"
*/

//define our return objects
BOOL authOK;
NSString *authMessage = [[NSString alloc] init];
NSArray *returnArray = nil;
//begin NSURLConnection prep:
NSMutableURLRequest *httpReq = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:GOOGLE_CLIENT_AUTH_URL] ];
[httpReq setTimeoutInterval:30.0];
//[httpReq setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[httpReq setHTTPMethod:@"POST"];
//set headers
[httpReq addValue:@"Content-Type" forHTTPHeaderField:@"application/x-www-form-urlencoded"];
//set post body
NSString *requestBody = [[NSString alloc]
initWithFormat:@"Email=%@&Passwd=%@&service=xapi&accountType=HOSTED_OR_GOOGLE&source=%@",
userName, password, [NSString stringWithFormat:@"%@%d", pluginVersion]];

[httpReq setHTTPBody:[requestBody dataUsingEncoding:NSASCIIStringEncoding]];

NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *data = nil;
NSString *responseStr = nil;
NSArray *responseLines = nil;
NSString *errorString;
//NSDictionary *dict;
int responseStatus = 0;
//this should be quick, and to keep same workflow, we'll do this sync.
//this should also get us by without messing with threads and run loops on Tiger.
data = [NSURLConnection sendSynchronousRequest:httpReq returningResponse:&response error:&error];

if ([data length] > 0) {
responseStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
//NSLog(@"Response From Google: %@", responseStr);
responseStatus = [response statusCode];
//dict = [[NSDictionary alloc] initWithDictionary:[response allHeaderFields]];
//if we got 200 authentication was successful
if (responseStatus == 200 ) {
authOK = TRUE;
authMessage = @"Authentication Successful...";
}
//403 = authentication failed.
else if (responseStatus == 403) {
authOK = FALSE;
//get Error code.
responseLines = [responseStr componentsSeparatedByString:@"\n"];
//find the line with the error string:
int i;
for (i =0; i < [responseLines count]; i++ ) {
if ([[responseLines objectAtIndex:i] rangeOfString:@"Error="].length != 0) {
errorString = [responseLines objectAtIndex:i] ;
}
}

errorString = [errorString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
/*
Official Google clientLogin Error Codes:
Error Code Description
BadAuthentication The login request used a username or password that is not recognized.
NotVerified The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
TermsNotAgreed The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
CaptchaRequired A CAPTCHA is required. (A response with this error code will also contain an image URL and a CAPTCHA token.)
Unknown The error is unknown or unspecified; the request contained invalid input or was malformed.
AccountDeleted The user account has been deleted.
AccountDisabled The user account has been disabled.
ServiceDisabled The user's access to the specified service has been disabled. (The user account may still be valid.)
ServiceUnavailable The service is not available; try again later.
*/

if ([errorString rangeOfString:@"BadAuthentication" ].length != 0) {
authMessage = @"Please Check your Username and Password and try again.";
}else if ([errorString rangeOfString:@"NotVerified"].length != 0) {
authMessage = @"This account has not been verified. You will need to access your Google account directly to resolve this";
}else if ([errorString rangeOfString:@"TermsNotAgreed" ].length != 0) {
authMessage = @"You have not agreed to Google terms of use. You will need to access your Google account directly to resolve this";
}else if ([errorString rangeOfString:@"CaptchaRequired" ].length != 0) {
authMessage = @"Google is requiring a CAPTCHA response to continue. Please complete the CAPTCHA challenge in your browser, and try authenticating again";
//NSString *captchaURL = [responseStr substringFromIndex: [responseStr rangeOfString:@"CaptchaURL="].length];
//either open the standard URL in a browser, or show a custom sheet with the image and send it back...
//parse URL to append to GOOGLE_CAPTCHA_URL_PREFIX
//but for now... just launch the standard URL.
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:GOOGLE_CAPTCHA_STANDARD_UNLOCK_URL]];
}else if ([errorString rangeOfString:@"Unknown" ].length != 0) {
authMessage = @"An Unknow error has occurred; the request contained invalid input or was malformed.";
}else if ([errorString rangeOfString:@"AccountDeleted" ].length != 0) {
authMessage = @"This user account previously has been deleted.";
}else if ([errorString rangeOfString:@"AccountDisabled" ].length != 0) {
authMessage = @"This user account has been disabled.";
}else if ([errorString rangeOfString:@"ServiceDisabled" ].length != 0) {
authMessage = @"Your access to the specified service has been disabled. Please try again later.";
}else if ([errorString rangeOfString:@"ServiceUnavailable" ].length != 0) {
authMessage = @"The service is not available; please try again later.";
}

}//end 403 if

}
//check most likely: no internet connection error:
if (error != nil) {
authOK = FALSE;
if ( [error domain] == NSURLErrorDomain) {
authMessage = @"Could not reach Google.com. Please check your Internet Connection";
}else {
//other error
authMessage = [authMessage stringByAppendingFormat:@"Internal Error. Please contact notoptimal.net for further assistance. Error: %@", [error localizedDescription] ];
}
}
//NSLog (@"err localized description %@", [error localizedDescription]) ;
//NSLog (@"err localized failure reasons %@", [error localizedFailureReason]) ;
//NSLog(@"err code %d", [error code]) ;
//NSLog (@"err domain %@", [error domain]) ;

//build return array [0] = authOK [1] = authMessage
returnArray = [NSArray arrayWithObjects: [NSNumber numberWithBool:authOK], authMessage, responseLines, nil ];
return returnArray;

}



Sound off in the comments! Feedback is quite welcome

Monday, March 26, 2007

Testers Wanted for Aperture2Gmail


Due to my wife abandoning me for the weekend to go visit her sister, I had some time to hack on Aperture2Gmail some more... I am glad to announce that I am about 93% done with version 0.1 ! All major features from iPhoto2Gmail work, I just need to finish some UI details.

Since I only have 22 18 15 days left on my Aperture Trial, I need to get this tested! If you are interested and willing to try out this new plugin, drop me a line at jils.i2g__a_t__symbol then the gmail bit.

The Aperture version will be limited to 10.4 and Aperture 1.5

UPDATE: I dare say the completion percentage is up to 98%100% Yep. v0.1 is ready for Testers so drop me a line!

Wednesday, March 21, 2007

Aperture2Gmail

As I hinted at over at iphoto2gmail, I want to try to write a version for Aperture. Given the fact that Apple has a public SDK and a great sample plugin available for download, I figured the learning curve for writing this plugin would be considerably less steep.

So far, I was right. I have the skeleton of the plugin laid out, and I was able to get past some Interface Builder strangeness last night. Tonight, after writing this up, I am going to try to integrate the model methods and the PHP scripts see how far I get.

I wonder why Apple has never released an iPhoto SDK. instead making devs rely on reverse engineering existing plugins to figure out the quirky API. Lucky for me, stand up guys like Richard Newman, Fraser Speirs, Andrew Stone, and Dean Jackson all made their efforts public and therefore available to other devs wanting to take a crack at it.