Solipsism Gradient

Rainer Brockerhoff’s blog

Browsing Posts published in February, 2009

Re: Klicko 1.1

No comments

And, the reachability check explained below is now also present in Klicko 1.1 (189).

Re: Cocoa musings pt.4

No comments

M. Uli Kusterer emailed me to say I’d forgotten that some people still have non-broadband connections. I had supposed that NSURLConnection would simply return an immediate error if the internet link is down – it turns out that it may autoconnect if the user has a dialup connection, for instance.

The way around that is to use the network reachability APIs. Here’s one way to insert that into the example below:

#include <SystemConfiguration/SCNetworkReachability.h>
...
- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate {
   if ((self = [super init])) {
      SCNetworkConnectionFlags flags = 0;
      if (SCNetworkCheckReachabilityByName([[theURL host] UTF8String], &flags)) {
         const SCNetworkConnectionFlags mask =
            kSCNetworkFlagsReachable|
            kSCNetworkFlagsConnectionRequired|
            kSCNetworkFlagsConnectionAutomatic|
            kSCNetworkFlagsInterventionRequired;
         if ((flags&mask)==kSCNetworkFlagsReachable) {
            delegate = theDelegate;
            data = nil;
            conn = [[NSURLConnection alloc]
               initWithRequest:[NSURLRequest requestWithURL:theURL]
               cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
               timeoutInterval:60.0]
               delegate:self];
            return self;
         }
      }
      [self autorelease];
      self = nil;
   }
   return self;
} 

This changes the alloc/init pair to return nil whenever the URL host is unreachable… you’ll have to test for that of course.

Update: the downside to the above approach is that the SCNetworkCheckReachabilityByName() function will block until it gets the information – which may take several seconds, perhaps more on some systems. The solution I’m actually using is more complex, installing a reachability transition callback, which sets a global flag that is tested before activating the NSURLConnection.

Cocoa musings pt.4

No comments

One thing I had to do very recently was to download a relatively short block of data from my server. There are some nice Cocoa shortcuts for such a thing, like:

NSError* error = nil;
NSData* data = [NSData dataWithContentsOfURL:theURL options: NSUncachedRead error:&error];

which accesses that URL from the server, presents it to you as NSData and/or gives you an error object. The problem, however, is that it’s synchronous; if your server is down, it may take some time to time-out, and all that time your UI is inactive.
The initial impulse to get around this problem would be to simply spin those two lines off into a secondary thread. It’s easy to underestimate the subtleties of that, however, and there are good alternatives available. Here’s a simplified version of what I eventually elected to use. First the .h file:

@interface AsynchDownload : NSObject {
   NSURLConnection* conn;
   NSMutableData* data;
   id delegate;
}

- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate;

- (void)cancel;

@end

and here the .m file:

@implementation AsynchDownload

- (id)initWithURL:(NSURL*)theURL delegate:(id)theDelegate {
   if ((self = [super init])) {
      delegate = theDelegate;
      data = nil;
      conn = [[NSURLConnection alloc]
         initWithRequest:[NSURLRequest requestWithURL:theURL]
         cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0]                            
         delegate:self];
   }
   return self;
}

- (void)cancel {
   [conn cancel];
}

- (void)dealloc {
   [conn release];
   [data release];
   [super dealloc];
}

- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error {
   [delegate performSelectorOnMainThread:@selector(failure:)
      withObject:[error localizedDescription] waitUntilDone:NO];
}

- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response {
   [data release];
   data = nil;
}

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)theData {
   if (!data) {
      data = [[NSMutableData alloc] init];
   }
   [data appendData:theData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
   [delegate performSelectorOnMainThread:@selector(success:)
      withObject:data waitUntilDone:NO];
}

@end

and, finally, here’s how to call it:

   AsynchDownload* download = [[AsynchDownload alloc] initWithURL:theURL delegate:self];

… and in the same class, two methods:

- (void)failure:(NSString*)failure {
   [download release];
   //.. complain and show the failure string
}

- (void)success:(NSData*)data {
   [download release];
   // do something with data
}

… and finally, you might call

   [download cancel];

somewhere inside, say, a cancel button’s action.

This takes care of nearly all common cases. The only non-obvious part is the connection:didReceiveResponse: method; according to the docs, you need to discard already-received data there, as it may be sent multiple times before you get the final data. In this method, you could also ask the NSURLResponse for the data size, to set a download progress indicator.

Finally, note the performSelectorOnMainThread: messages which pass the results back to your own code; they neatly insulate you from threading effects.

Re: Klicko 1.1

No comments

Rainer Brockerhoff wrote:

Maybe I should, after all, implement regular automatic version checking? I dislike such automatic processes myself, but I’ll look into doing it less obtrusively.

So, it’s done; Klicko 1.1 (187) has automatic version checking.

Details were trickier than I had anticipated, but I managed to do it while increasing download size by only 44K (23%). Most of that is accounted for by a new helper application. The background process checks for updates periodically. If an update is out, the process runs the helper app to display an alert; the user can then ask the app to run System Preferences to download and install the update. (If the Klicko panel is already open, the helper app isn’t called.)

Re: Klicko 1.1

No comments

And the first bug is fixed: you had to click twice in Exposé in some cases. So I pushed out 1.1 (181). This also has, again, the French localization, thanks to Ronald Leroux for rewriting it on short notice.
I think I’ll continue doing small, immediate bug fixes just by incrementing the build number, instead of doing lots of dot-dot versions like 1.1.2c15. The downside is that these small updates won’t get press releases or special mention on the version tracking sites; for now, I’ll just post a “developer note” on those when possible.
Currently the user has to check for updates by opening System Preferences, going to the “Installation” tab, and clicking on “Check for Updates”. I do get the impression that relatively few users do so regularly, or when they run into a bug. Maybe I should, after all, implement regular automatic version checking? I dislike such automatic processes myself, but I’ll look into doing it less obtrusively.

Klicko 1.1

No comments

Details on the product page. More about it later here… must run.

Cocoa musings pt.3

No comments

It’s been a while since the last post, but I’ve finally gotten past the boring parts of the new Klicko System Preference pane & background process. The latter is actually a per-user Launch Agent. I’d used launchd before in the last Quay implementation, but didn’t fully understand what I was doing.

Well, I still can’t claim I fully understand all, but I’m definitely getting better at this. As soon as Klicko 1.2 is out and working (hm, maybe that should be the other way around?) I’ll back-port everything I learned to Quay.

One important part of messing around with launchd agents is that you have to set everything up just so – permissions of the executable and of its containing folders, as well as of the controlling plist if it’s a global agent; and you have to run launchctl to load (register) the controlling plist with launchd. Only then, and if the conditions inside the plist are satisfied, launchd will run your agent for you.

Uninstalling your agent, usually for putting an updated version in its place, must also be done carefully. You have to unload it if it’s loaded (even though it may not be running at the time) before swapping in the new executable and plist. Ideally, to avoid wasting time, there should be an easy way to test if your agent is properly loaded – the user might have unloaded it manually, or whatever. Unfortunately, the only “official” way to do so is to run launchctl again, passing in the “list” parameter, and parsing the output; not ideal.

However, there’s a primitive API in place in <launch.h>, and in fact launchctl uses this API itself to communicate with launchd. Still, there’s no documentation about it; only some sample code. Also, there’s this recent reply on the launchd-dev mailing list:

Right now the <launch.h> API is only rated for
daemons checking in with launchd (ala SampleD).
It is not really designed for job management.
Rather, we recommend that folks do their job
management by fork/exec of launchctl.

Quinn “The Eskimo!”

Still, I thought it would be interesting to use this API to check on the agent; after all, if something fails you’ll get an error back, and nothing will be messed up. So here’s the code for doing so:

#include <launch.h>

static id GetFromLaunchData(launch_data_t obj);

static void Launch_data_iterate(launch_data_t obj, const char *key, void* dict) {

   if (obj) {
      id value = GetFromLaunchData(obj);
      if(value) {
         [(NSMutableDictionary*)dict setObject:value forKey:[NSString stringWithUTF8String:key]];
      }
   }
}

static NSDictionary* GetFromLaunchDictionary(launch_data_t dict) {
   NSMutableDictionary* result = NULL;
   if (launch_data_get_type(dict)==LAUNCH_DATA_DICTIONARY) {
      result = [NSMutableDictionary dictionary];
      launch_data_dict_iterate(dict, Launch_data_iterate, result);
   }
   return result;
}

static NSArray* GetFromLaunchArray(launch_data_t arr) {
   NSMutableArray* result = NULL;   
   if (launch_data_get_type(arr)==LAUNCH_DATA_ARRAY) {
      size_t count = launch_data_array_get_count(arr);
      result = [NSMutableArray arrayWithCapacity:count];
      for (size_t i=0;i<count;i++) {
         id obj = GetFromLaunchData(launch_data_array_get_index(arr, i));
         if (obj) {
            [result addObject:obj];
         }
      }
   }
   return result;
}

static id GetFromLaunchData(launch_data_t obj) {
   switch (launch_data_get_type(obj)) {
   case LAUNCH_DATA_STRING:
      return [NSString stringWithUTF8String:launch_data_get_string(obj)];
   case LAUNCH_DATA_INTEGER:
      return [NSNumber numberWithLongLong:launch_data_get_integer(obj)];
   case LAUNCH_DATA_REAL:
      return [NSNumber numberWithDouble:launch_data_get_real(obj)];
   case LAUNCH_DATA_BOOL:
      return [NSNumber numberWithBool:launch_data_get_bool(obj)?YES:NO];
   case LAUNCH_DATA_ARRAY:
      return GetFromLaunchArray(obj);
   case LAUNCH_DATA_DICTIONARY:
      return GetFromLaunchDictionary(obj);
   case LAUNCH_DATA_FD:
      return [NSNumber numberWithInt:launch_data_get_fd(obj)];
   case LAUNCH_DATA_MACHPORT:
      return [NSNumber numberWithInt:launch_data_get_machport(obj)];
   default:
      break;
   }
   return nil;
}

static NSDictionary* GetFromJobLabel(NSString* job) {
   NSDictionary* result = nil;
   launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
   if (msg&&launch_data_dict_insert(msg, launch_data_new_string([job fileSystemRepresentation]), LAUNCH_KEY_GETJOB)) {
      launch_data_t response = launch_msg(msg);
      launch_data_free(msg);
      if (response&&(launch_data_get_type(response)==LAUNCH_DATA_DICTIONARY)) {
         result = GetFromLaunchDictionary(response);
      }
      launch_data_free(response);
   }
   return result;
}

To use it, just call the last function like this:

   NSDictionary* dictionary = GetFromJobLabel(@"com.yourcompany.agentlabel");

and examine the dictionary. If it’s nil, some error happened, or your agent isn’t loaded into launchd.

If you do get a dictionary back, it will be autoreleased, and the “Label” key’s object should be equal to the string you passed in. There are two more keys that might be interesting to check: “PID” will contain your agent’s pid if it’s running, and will be missing if it’s not; and “LastExitStatus” will show you what your agent’s main() returned on its last run.

Note that most of the code above is actually based on the launchd source, converted to use Foundation objects instead of CFTypes.

Photos licensed by Creative Commons license. Unless otherwise noted, content © 2002-2024 by Rainer Brockerhoff. Iravan child theme by Rainer Brockerhoff, based on Arjuna-X, a WordPress Theme by SRS Solutions. jQuery UI based on Aristo.