From fe61888d3d29727f4c0eb8ce57dd48247ff0c462 Mon Sep 17 00:00:00 2001 From: glenlow Date: Tue, 27 May 2008 06:50:13 +0000 Subject: [PATCH] graph display updates when underlying file is changed; GVWindowController and GVAttributeInspectorController now reference document instead of graph --- macosx/GVFileNotificationCenter.h | 33 ++++++ macosx/GVFileNotificationCenter.m | 170 ++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 macosx/GVFileNotificationCenter.h create mode 100644 macosx/GVFileNotificationCenter.m diff --git a/macosx/GVFileNotificationCenter.h b/macosx/GVFileNotificationCenter.h new file mode 100644 index 000000000..3e0f2afa3 --- /dev/null +++ b/macosx/GVFileNotificationCenter.h @@ -0,0 +1,33 @@ +/* $Id$ $Revision$ */ +/* vim:set shiftwidth=4 ts=8: */ + +/********************************************************** +* This software is part of the graphviz package * +* http://www.graphviz.org/ * +* * +* Copyright (c) 1994-2008 AT&T Corp. * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Corp. * +* * +* Information and Software Systems Research * +* AT&T Research, Florham Park NJ * +**********************************************************/ + +#import + +@interface GVFileNotificationCenter : NSObject +{ + CFFileDescriptorRef _queue; + NSMutableSet *_records; +} + ++ (void)initialize; ++ (id)defaultCenter; + +- (id)init; + +- (void)addObserver:(id)observer selector:(SEL)selector path:(NSString *)path; +- (void)removeObserver:(id)observer path:(NSString *)path; + +@end diff --git a/macosx/GVFileNotificationCenter.m b/macosx/GVFileNotificationCenter.m new file mode 100644 index 000000000..9288d6de7 --- /dev/null +++ b/macosx/GVFileNotificationCenter.m @@ -0,0 +1,170 @@ +/* $Id$ $Revision$ */ +/* vim:set shiftwidth=4 ts=8: */ + +/********************************************************** +* This software is part of the graphviz package * +* http://www.graphviz.org/ * +* * +* Copyright (c) 1994-2008 AT&T Corp. * +* and is licensed under the * +* Common Public License, Version 1.0 * +* by AT&T Corp. * +* * +* Information and Software Systems Research * +* AT&T Research, Florham Park NJ * +**********************************************************/ + +#include +#include + +#import "GVFileNotificationCenter.h" + +static GVFileNotificationCenter *_defaultCenter = nil; + +@interface GVFileNotificationRecord : NSObject +{ + id _observer; + NSString *_path; + SEL _selector; + int _fileDescriptor; +} + +@property(readonly) id observer; +@property(readonly) NSString *path; +@property SEL selector; +@property int fileDescriptor; + +@end + +@implementation GVFileNotificationRecord + +@synthesize observer = _observer; +@synthesize path = _path; +@synthesize selector = _selector; +@synthesize fileDescriptor = _fileDescriptor; + +- (id)initWithObserver:(id)observer path:(NSString *)path +{ + if (self = [super init]) { + _observer = observer; + _path = [path retain]; + } + return self; +} + +- (BOOL)isEqual:(id)anObject +{ + /* if the other object is one of us, compare observers + paths only */ + return [anObject isKindOfClass:[GVFileNotificationRecord class]] ? [self observer] == [anObject observer] && [[self path] isEqualToString:[anObject path]] : NO; +} + +- (NSUInteger)hash +{ + /* hash based on observers + paths only */ + return (NSUInteger)_observer ^ [_path hash]; +} + +- (void)dealloc +{ + [_path release]; + [super dealloc]; +} + +@end + +static void noteFileChanged(CFFileDescriptorRef queue, CFOptionFlags callBackTypes, void *info) +{ + int queueDescriptor = CFFileDescriptorGetNativeDescriptor(queue); + + /* grab the next event from the kernel queue */ + struct kevent event; + kevent(queueDescriptor, NULL, 0, &event, 1, NULL); + + GVFileNotificationRecord *record = (GVFileNotificationRecord *)event.udata; + if (record) { + /* report the file change to the observer */ + [record.observer performSelector:record.selector withObject:record.path]; + + /* if watched file is deleted, try to reopen the file and watch it again */ + if (event.fflags & NOTE_DELETE) { + close(record.fileDescriptor); + + int fileDescriptor = open([record.path UTF8String], O_EVTONLY); + if (fileDescriptor != -1) { + struct kevent change; + EV_SET (&change, fileDescriptor, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND, 0, record); + if (kevent (queueDescriptor, &change, 1, NULL, 0, NULL) != -1) + record.fileDescriptor = fileDescriptor; + } + } + + } + + /* reenable the callbacks (they were automatically disabled when handling the CFFileDescriptor) */ + CFFileDescriptorEnableCallBacks(queue, kCFFileDescriptorReadCallBack); +} + + +@implementation GVFileNotificationCenter + ++ (void)initialize +{ + if (!_defaultCenter) + _defaultCenter = [[GVFileNotificationCenter alloc] init]; +} + ++ (id)defaultCenter +{ + return _defaultCenter; +} + +- (id)init +{ + if (self = [super init]) { + /* create kernel queue, wrap a CFFileDescriptor around it and schedule it on the Cocoa run loop */ + _queue = CFFileDescriptorCreate(kCFAllocatorDefault, kqueue(), true, noteFileChanged, NULL); + CFFileDescriptorEnableCallBacks(_queue, kCFFileDescriptorReadCallBack); + CFRunLoopAddSource( + [[NSRunLoop currentRunLoop] getCFRunLoop], + CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, _queue, 0), + kCFRunLoopDefaultMode); + + /* need to keep track of observers */ + _records = [[NSMutableSet alloc] init]; + } + return self; +} + +- (void)addObserver:(id)observer selector:(SEL)selector path:(NSString *)path +{ + GVFileNotificationRecord *record = [[[GVFileNotificationRecord alloc] initWithObserver:observer path:path] autorelease]; + GVFileNotificationRecord *oldRecord = [_records member:record]; + + if (oldRecord) + /* record already exists, just update the selector */ + oldRecord.selector = selector; + else { + /* new record, start monitoring the path */ + int fileDescriptor = open([path UTF8String], O_EVTONLY); + if (fileDescriptor != -1) { + struct kevent change; + EV_SET (&change, fileDescriptor, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND, 0, record); + if (kevent (CFFileDescriptorGetNativeDescriptor(_queue), &change, 1, NULL, 0, NULL) != -1) { + record.selector = selector; + record.fileDescriptor = fileDescriptor; + [_records addObject:record]; + } + } + } +} + +- (void)removeObserver:(id)observer path:(NSString *)path +{ + GVFileNotificationRecord *record = [_records member:[[[GVFileNotificationRecord alloc] initWithObserver:observer path:path] autorelease]]; + if (record) { + close(record.fileDescriptor); /* closing the file descriptor also removes it from the kqueue */ + [_records removeObject:record]; + } + +} +@end -- 2.40.0