/* =============================================================================
	FILE:		UKFNSubscribeFileWatcher.m
	PROJECT:	Filie
    
    COPYRIGHT:  (c) 2005 M. Uli Kusterer, all rights reserved.
    
	AUTHORS:	M. Uli Kusterer - UK
    
    LICENSES:   MIT License

	REVISIONS:
		2006-03-13	UK	Commented, added singleton, added notifications.
		2005-03-02	UK	Created.
   ========================================================================== */

// -----------------------------------------------------------------------------
//  Headers:
// -----------------------------------------------------------------------------

#import "UKFNSubscribeFileWatcher.h"
#import <Carbon/Carbon.h>


// -----------------------------------------------------------------------------
//  Prototypes:
// -----------------------------------------------------------------------------

void    UKFileSubscriptionProc(FNMessage message, OptionBits flags, void *refcon, FNSubscriptionRef subscription);


@implementation UKFNSubscribeFileWatcher

// -----------------------------------------------------------------------------
//  sharedFileWatcher:
//		Singleton accessor.
// -----------------------------------------------------------------------------

+(id) sharedFileWatcher
{
	static UKFNSubscribeFileWatcher* sSharedFileWatcher = nil;
	
	if( !sSharedFileWatcher )
		sSharedFileWatcher = [[UKFNSubscribeFileWatcher alloc] init];	// This is a singleton, and thus an intentional "leak".
    
    return sSharedFileWatcher;
}


// -----------------------------------------------------------------------------
//  * CONSTRUCTOR:
// -----------------------------------------------------------------------------

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


// -----------------------------------------------------------------------------
//  * DESTRUCTOR:
// -----------------------------------------------------------------------------

-(void) dealloc
{
    NSEnumerator*   enny = [subscriptions objectEnumerator];
    NSValue*        subValue = nil;
    
    while( (subValue = [enny nextObject]) )
    {
        FNSubscriptionRef   subscription = [subValue pointerValue];
        FNUnsubscribe( subscription );
    }
    
    [subscriptions release];
    [super dealloc];
}


// -----------------------------------------------------------------------------
//  addPath:
//		Start watching the object at the specified path. This only sends write
//		notifications for all changes, as FNSubscribe doesn't tell what actually
//		changed about our folder.
// -----------------------------------------------------------------------------

-(void) addPath: (NSString*)path
{
    OSStatus                    err = noErr;
    static FNSubscriptionUPP    subscriptionUPP = NULL;
    FNSubscriptionRef           subscription = NULL;
    
    if( !subscriptionUPP )
        subscriptionUPP = NewFNSubscriptionUPP( UKFileSubscriptionProc );
    
    err = FNSubscribeByPath( (UInt8*) [path fileSystemRepresentation], subscriptionUPP, (void*)self,
                                kNilOptions, &subscription );
    if( err != noErr )
    {
        NSLog( @"UKFNSubscribeFileWatcher addPath: %@ failed due to error ID=%ld.", path, err );
        return;
    }
    
    [subscriptions setObject: [NSValue valueWithPointer: subscription] forKey: path];
}


// -----------------------------------------------------------------------------
//  removePath:
//		Stop watching the object at the specified path.
// -----------------------------------------------------------------------------

-(void) removePath: (NSString*)path
{
    NSValue*            subValue = nil;
    @synchronized( self )
    {
        subValue = [[[subscriptions objectForKey: path] retain] autorelease];
        [subscriptions removeObjectForKey: path];
    }
    
	if( subValue )
	{
		FNSubscriptionRef   subscription = [subValue pointerValue];
		
		FNUnsubscribe( subscription );
	}
}


// -----------------------------------------------------------------------------
//  sendDelegateMessage:forSubscription:
//		Bottleneck for change notifications. This is called by our callback
//		function to actually inform the delegate and send out notifications.
//
//		This *only* sends out write notifications, as FNSubscribe doesn't tell
//		what changed about our folder.
// -----------------------------------------------------------------------------

-(void) sendDelegateMessage: (FNMessage)message forSubscription: (FNSubscriptionRef)subscription
{
    NSValue*                    subValue = [NSValue valueWithPointer: subscription];
    NSString*                   path = [[subscriptions allKeysForObject: subValue] objectAtIndex: 0];
    
	[[[NSWorkspace sharedWorkspace] notificationCenter] postNotificationName: UKFileWatcherWriteNotification
															object: self
															userInfo: [NSDictionary dictionaryWithObjectsAndKeys: path, @"path", nil]];
	
    [delegate watcher: self receivedNotification: UKFileWatcherWriteNotification forPath: path];
    //NSLog( @"UKFNSubscribeFileWatcher noticed change to %@", path );	// DEBUG ONLY!
}



// -----------------------------------------------------------------------------
//  delegate:
//		Accessor for file watcher delegate.
// -----------------------------------------------------------------------------

-(id)   delegate
{
    return delegate;
}


// -----------------------------------------------------------------------------
//  setDelegate:
//		Mutator for file watcher delegate.
// -----------------------------------------------------------------------------

-(void) setDelegate: (id)newDelegate
{
    delegate = newDelegate;
}


@end


// -----------------------------------------------------------------------------
//  UKFileSubscriptionProc:
//		Callback function we hand to Carbon so it can tell us when something
//		changed about our watched folders. We set the refcon to a pointer to
//		our object. This simply extracts the object and hands the info off to
//		sendDelegateMessage:forSubscription: which does the actual work.
// -----------------------------------------------------------------------------

void    UKFileSubscriptionProc( FNMessage message, OptionBits flags, void *refcon, FNSubscriptionRef subscription )
{
    UKFNSubscribeFileWatcher*   obj = (UKFNSubscribeFileWatcher*) refcon;
    
    if( message == kFNDirectoryModifiedMessage )    // No others exist as of 10.4
        [obj sendDelegateMessage: message forSubscription: subscription];
    else
        NSLog( @"UKFileSubscriptionProc: Unknown message %d", message );
}
