Cocoa in the Shell

Disk Arbitration callback issue

I’m currently coding an utility that needs the Disk Arbitration framework. I use this framework to be notified each time a disk image (.dmg) is mounted on the filesystem by registering to DADiskMountApprovalCallback with the DARegisterDiskMountApprovalCallback() function. If you are interested in how these functions work you can read one of my previous post in which I talk about this.

Anyway, I’m writing this because I had a problem when using this callback with disk images.
Each time I was opening a dmg, the callback was called twice, which was very annoying, so I asked on stackoverflow and Apple filesystem dev list, but nobody was able to told me why I had this strange behaviour, so I ended up coding a tiny detector for the second callback to ignore it.

Here is the code of a little Oriented Object wrapper for Disk Arbitration which only deal with DADiskMountApprovalCallback.

DiskArbitrationWrapper.h :

#import <foundation/Foundation.h>
#import <diskArbitration/DiskArbitration.h>

@interface DiskArbitrationWrapper : NSObject
{
    // DiskArbitration session
    DASessionRef _session;
    // For some reason callback is called twice, this is used as detector
    NSMutableDictionary* _recentlyMounted;
}
-(void)handleImageDiskWithInformation:(NSTimer*)aTimer;

@end

DiskArbitrationWrapper.m :

#import "DiskArbitrationWrapper.h"

DADissenterRef diskDidMount(DADiskRef dsk, void* context)
{
    NSDictionary* dict = (NSDictionary*)DADiskCopyDescription(dsk);

    NSString* deviceModel = [dict objectForKey:@"DADeviceModel"];

    if (NSNotFound != [deviceModel rangeOfString:@"Disk Image"].location) // Disk image (.dmg)
    {
        DiskArbitrationWrapper* daw = (DiskArbitrationWrapper*)context;
        [NSTimer scheduledTimerWithTimeInterval:1 target:daw selector:@selector(handleImageDiskWithInformation:) userInfo:dict repeats:NO];
    }
    [dict release]; // DADiskCopyDescription returns a retained object
    return NULL; // Approve mount in any case
}

@implementation DiskArbitrationWrapper

-(id)init
{
    if ((self = [super init]) != nil)
    {
        _recentlyMounted = [[NSMutableDictionary alloc] init];

        _session = DASessionCreate(kCFAllocatorDefault);
        DARegisterDiskMountApprovalCallback(_session, kDADiskDescriptionMatchVolumeMountable, diskDidMount, self);
        DASessionScheduleWithRunLoop(_session, [[NSRunLoop mainRunLoop] getCFRunLoop], kCFRunLoopDefaultMode);
    }
    return self;
}

-(void)dealloc
{
    DASessionUnscheduleFromRunLoop(_session, [[NSRunLoop mainRunLoop] getCFRunLoop], kCFRunLoopDefaultMode);
    CFRelease(_session);
    [_recentlyMounted release];
    [super dealloc];
}

-(void)handleImageDiskWithInformation:(NSTimer*)aTimer
{
    NSDictionary* infos = [aTimer userInfo];

    NSString* volumeName = [infos objectForKey:@"DAVolumeName"];
    if ([_recentlyMounted objectForKey:volumeName]) // If the value returned is not nil, it's the 2nd callback
    {
        NSLog(@"2nd callback on same image, ignoring...");
        return;
    }
    // First callback, add the UUID and name to dictionary
    // Use the volume name as key, and volume UUID as value
    [_recentlyMounted setObject:[infos objectForKey:@"DAVolumeUUID"] forKey:volumeName];
    // Schedule the suppression, you can put a lower value than 5 here
    [_recentlyMounted performSelector:@selector(removeObjectForKey:) withObject:volumeName afterDelay:5];

    // Do whatever you want
}

@end

EDIT : After Aaron Burghardt’s comment, I tried his method and made a new post about it, here.