From 778a1d5d977b911613ded500bb5d338fbc39c492 Mon Sep 17 00:00:00 2001 From: Richard Frith-MacDonald Date: Mon, 2 Nov 1998 16:59:57 +0000 Subject: [PATCH] Added Distributed notification center server stuff. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@3161 72102866-910b-0410-8b05-ffd578937521 --- Tools/GNUmakefile | 7 +- Tools/gdnc.h | 61 ++++ Tools/gdnc.m | 756 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 821 insertions(+), 3 deletions(-) create mode 100644 Tools/gdnc.h create mode 100644 Tools/gdnc.m diff --git a/Tools/GNUmakefile b/Tools/GNUmakefile index 307d6b762..4465c1282 100644 --- a/Tools/GNUmakefile +++ b/Tools/GNUmakefile @@ -28,19 +28,20 @@ GNUSTEP_MAKEFILES = $(GNUSTEP_SYSTEM_ROOT)/Makefiles include $(GNUSTEP_MAKEFILES)/common.make # The application to be compiled -TOOL_NAME = defaults dread dwrite dremove +TOOL_NAME = gdnc defaults dread dwrite dremove OBJC_PROGRAM_NAME = gdomap # The source files to be compiled gdomap_C_FILES = gdomap.c +gdnc_OBJC_FILES = gdnc.m defaults_OBJC_FILES = defaults.m dread_OBJC_FILES = dread.m dremove_OBJC_FILES = dremove.m dwrite_OBJC_FILES = dwrite.m -SOURCES = gdomap.c defaults.m dread.m dremove.m dwrite.m +SOURCES = gdomap.c gdnc.m defaults.m dread.m dremove.m dwrite.m -HEADERS = gdomap.h +HEADERS = gdomap.h gdnc.h DIST_FILES = GNUmakefile Makefile.preamble Makefile.postamble \ $(SOURCES) $(HEADERS) diff --git a/Tools/gdnc.h b/Tools/gdnc.h new file mode 100644 index 000000000..3f0b5c006 --- /dev/null +++ b/Tools/gdnc.h @@ -0,0 +1,61 @@ +/* Include for GNUstep Distributed NotificationCenter + Copyright (C) 1998 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Created: October 1998 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define GDNC_SERVICE @"GDNCServer" + +@protocol GDNCClient +- (void) postNotificationName: (NSString*)name + object: (NSString*)object + userInfo: (NSData*)info + selector: (SEL)aSelector + to: (unsigned long)observer; +@end + +@protocol GDNCProtocol +- (void) addObserver: (unsigned long)anObserver + selector: (SEL)aSelector + name: (NSString*)notificationname + object: (NSString*)anObject + suspensionBehavior: (NSNotificationSuspensionBehavior)suspensionBehavior + for: (id)client; + +- (void) postNotificationName: (NSString*)notificationName + object: (NSString*)anObject + userInfo: (NSData*)d + deliverImmediately: (BOOL)deliverImmediately + for: (id)client; + +- (void) registerClient: (id)client; + +- (void) removeObserver: (unsigned long)anObserver + name: (NSString*)notificationname + object: (NSString*)anObject + for: (id)client; + +- (void) setSuspended: (BOOL)flag + for: (id)client; + +- (void) unregisterClient: (id)client; + +@end + diff --git a/Tools/gdnc.m b/Tools/gdnc.m new file mode 100644 index 000000000..492304b2c --- /dev/null +++ b/Tools/gdnc.m @@ -0,0 +1,756 @@ +/* Implementation of GNUstep Distributed Notification Center + Copyright (C) 1998 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Created: October 1998 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gdnc.h" + +@interface GDNCNotification : NSObject +{ +@public + NSString *name; + NSString *object; + NSData *info; +} ++ (GDNCNotification*) notificationWithName: (NSString*)notificationName + object: (NSString*)notificationObject + data: (NSData*)notificationData; +@end + +@implementation GDNCNotification +- (void) dealloc +{ + [name release]; + [object release]; + [info release]; + [super dealloc]; +} ++ (GDNCNotification*) notificationWithName: (NSString*)notificationName + object: (NSString*)notificationObject + data: (NSData*)notificationData +{ + GDNCNotification *tmp = [GDNCNotification alloc]; + + tmp->name = [notificationName retain]; + tmp->object = [notificationObject retain]; + tmp->info = [notificationData retain]; +} +@end + + +/* + * Information about a notification observer. + */ +@interface GDNCClient : NSObject +{ +@public + BOOL suspended; + id client; + NSMutableArray *observers; +} +@end + +@implementation GDNCClient +- (void) dealloc +{ + [observers release]; + [super dealloc]; +} + +- (id) init +{ + observers = [NSMutableArray new]; + return self; +} +@end + + + +/* + * Information about a notification observer. + */ +@interface GDNCObserver : NSObject +{ +@public + unsigned observer; + NSString *notificationName; + NSString *notificationObject; + SEL selector; + GDNCClient *client; + NSMutableArray *queue; + NSNotificationSuspensionBehavior behavior; +} +@end + +@implementation GDNCObserver + +- (void) dealloc +{ + [queue release]; + [notificationName release]; + [notificationObject release]; + [super dealloc]; +} + +- (id) init +{ + queue = [[NSMutableArray alloc] initWithCapacity: 1]; +} +@end + + +@interface GDNCServer : NSObject +{ + NSConnection *conn; + NSMapTable *connections; + NSHashTable *allObservers; + NSMutableDictionary *observersForNames; + NSMutableDictionary *observersForObjects; +} + +- (void) addObserver: (unsigned long)anObserver + selector: (SEL)aSelector + name: (NSString*)notificationname + object: (NSString*)anObject + suspensionBehavior: (NSNotificationSuspensionBehavior)suspensionBehavior + for: (id)client; + +- (NSConnection*) connection: (NSConnection*)ancestor + didConnect: (NSConnection*)newConn; + +- (id) connectionBecameInvalid: (NSNotification*)notification; + +- (void) postNotificationName: (NSString*)notificationName + object: (NSString*)anObject + userInfo: (NSData*)d + deliverImmediately: (BOOL)deliverImmediately + for: (id)client; + +- (void) removeObserver: (GDNCObserver*)observer; + +- (void) removeObserversForClients: (NSMapTable*)clients; + +- (void) removeObserver: (unsigned long)anObserver + name: (NSString*)notificationname + object: (NSString*)anObject + for: (id)client; + +- (void) setSuspended: (BOOL)flag + for: (id)client; +@end + +@implementation GDNCServer + +- (void) dealloc +{ + NSMapEnumerator enumerator; + NSConnection *connection; + NSMapTable *clients; + + if (conn) + { + [NSNotificationCenter removeObserver: self + name: NSConnectionDidDieNotification + object: conn]; + [conn release]; + conn = nil; + } + + /* + * Free all the client map tables in the connections map table and + * ignore notifications from those connections. + */ + enumerator = NSEnumerateMapTable(connections); + while (NSNextMapEnumeratorPair(&enumerator, + (void**)&connection, (void**)&clients) == YES) + { + [NSNotificationCenter removeObserver: self + name: NSConnectionDidDieNotification + object: connection]; + [self removeObserversForClients: clients]; + NSFreeMapTable(clients); + } + + /* + * Now free the connections map itsself and the table of observers. + */ + NSFreeMapTable(connections); + NSFreeHashTable(allObservers); + + /* + * And release the maps of notification names and objects. + */ + [observersForNames release]; + [observersForObjects release]; + [super dealloc]; +} + +- (id) init +{ + connections = NSCreateMapTable(NSObjectMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + allObservers = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 0); + observersForNames = [NSMutableDictionary new]; + observersForObjects = [NSMutableDictionary new]; + conn = [NSConnection newRegisteringAtName: GDNC_SERVICE + withRootObject: self]; + if (conn == nil) + { + NSLog(@"gdnc - unable to register with name server - quiting.\n"); + [self release]; + return nil; + } + + /* + * Get notifications for new connections and connection losses. + */ + [conn setDelegate: self]; + [NSNotificationCenter addObserver: self + selector: @selector(connectionBecameInvalid:) + name: NSConnectionDidDieNotification + object: conn]; + return self; +} + +- (void) addObserver: (unsigned long)anObserver + selector: (SEL)aSelector + name: (NSString*)notificationName + object: (NSString*)anObject + suspensionBehavior: (NSNotificationSuspensionBehavior)suspensionBehavior + for: (id)client +{ + GDNCClient *info; + NSMapTable *clients; + GDNCObserver *obs; + NSConnection *connection; + + connection = [(NSDistantObject*)client connectionForProxy]; + clients = (NSMapTable*)NSMapGet(connections, connection); + if (clients == 0) + { + [NSException raise: NSInvalidArgumentException + format: @"Unknown connection for new observer"]; + } + info = (GDNCClient*)NSMapGet(clients, client); + if (info == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"Unknown client for new observer"]; + } + + /* + * Create new observer info and add to array of observers for this + * client and the table of all observers. + */ + obs = [GDNCObserver new]; + obs->observer = anObserver; + obs->client = info; + obs->behavior = suspensionBehavior; + obs->selector = aSelector; + [info->observers addObject: obs]; + [obs release]; + NSHashInsert(allObservers, obs); + + /* + * Now add the observer to the lists of observers interested in it's + * particular notification names and objects. + */ + if (anObject) + { + NSMutableArray *objList; + + objList = [observersForObjects objectForKey: anObject]; + if (objList == nil) + { + objList = [NSMutableArray new]; + [observersForObjects setObject: objList forKey: anObject]; + [objList release]; + } + /* + * If possible use an existing string as the key. + */ + if ([objList count] > 0) + { + GDNCObserver *tmp = [objList objectAtIndex: 0]; + + anObject = tmp->notificationObject; + } + obs->notificationName = [anObject retain]; + [objList addObject: obs]; + } + + if (notificationName) + { + NSMutableArray *namList; + + namList = [observersForNames objectForKey: notificationName]; + if (namList == nil) + { + namList = [NSMutableArray new]; + [observersForNames setObject: namList forKey: notificationName]; + [namList release]; + } + /* + * If possible use an existing string as the key. + */ + if ([namList count] > 0) + { + GDNCObserver *tmp = [namList objectAtIndex: 0]; + + notificationName = tmp->notificationObject; + } + obs->notificationName = [notificationName retain]; + [namList addObject: obs]; + } + +} + +- (NSConnection*) connection: (NSConnection*)ancestor + didConnect: (NSConnection*)newConn +{ + NSMapTable *table; + + [NSNotificationCenter addObserver: self + selector: @selector(connectionBecameInvalid:) + name: NSConnectionDidDieNotification + object: newConn]; + [newConn setDelegate: self]; + /* + * Create a new map table entry for this connection with a value that + * is a table (normally with a single entry) containing registered + * clients (proxies for NSDistributedNotificationCenter objects). + */ + table = NSCreateMapTable(NSObjectMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + NSMapInsert(connections, newConn, table); + return newConn; +} + +- (id) connectionBecameInvalid: (NSNotification*)notification +{ + id connection = [notification object]; + + [NSNotificationCenter removeObserver: self + name: NSConnectionDidDieNotification + object: connection]; + + if (connection == conn) + { + NSLog(@"argh - gdnc server root connection has been destroyed.\n"); + exit(1); + } + else + { + NSMapTable *table; + + /* + * Remove all clients registered via this connection + * (should normally only be 1) - then the connection. + */ + table = NSMapGet(connections, connection); + if (table) + { + [self removeObserversForClients: table]; + NSFreeMapTable(table); + } + NSMapRemove(connections, connection); + } +} + +- (void) registerClient: (id)client +{ + NSMapTable *table; + GDNCClient *info; + + table = NSMapGet(connections, [(NSDistantObject*)client connectionForProxy]); + if (table == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"registration with unknown connection"]; + } + if (NSMapGet(table, client) != 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"registration with registered client"]; + } + info = [GDNCClient new]; + info->client = client; + NSMapInsert(table, client, info); + [info release]; +} + +- (void) postNotificationName: (NSString*)notificationName + object: (NSString*)notificationObject + userInfo: (NSData*)d + deliverImmediately: (BOOL)deliverImmediately + for: (id)client +{ + NSMutableArray *observers = [NSMutableArray array]; + NSMutableArray *byName; + NSMutableArray *byObject; + unsigned pos; + GDNCNotification *notification; + + byName = [observersForNames objectForKey: notificationName]; + byObject = [observersForObjects objectForKey: notificationObject]; + /* + * Build up a list of all those observers that should get sent this. + */ + for (pos = [byName count]; pos > 0; pos--) + { + GDNCObserver *obs = [byName objectAtIndex: pos]; + + if (obs->notificationObject == nil || + [obs->notificationObject isEqual: notificationObject]) + { + [observers addObject: obs]; + } + } + for (pos = [byObject count]; pos > 0; pos--) + { + GDNCObserver *obs = [byObject objectAtIndex: pos]; + + if (obs->notificationName == nil || + [obs->notificationName isEqual: notificationName]) + { + if ([observers indexOfObjectIdenticalTo: obs] == NSNotFound) + { + [observers addObject: obs]; + } + } + } + + /* + * Build notification object to queue for observer. + */ + if ([observers count] > 0) + { + notification = [GDNCNotification notificationWithName: notificationName + object: notificationObject + data: d]; + } + + /* + * Add the object to the queue for this observer depending on suspension + * state of the client NSDistributedNotificationCenter etc. + */ + for (pos = [observers count]; pos > 0; pos--) + { + GDNCObserver *obs = [observers objectAtIndex: pos]; + + if (obs->client->suspended == NO || deliverImmediately == YES) + { + [obs->queue addObject: d]; + } + else + { + switch (obs->behavior) + { + case NSNotificationSuspensionBehaviorDrop: + break; + case NSNotificationSuspensionBehaviorCoalesce: + [obs->queue removeAllObjects]; + [obs->queue addObject: d]; + break; + case NSNotificationSuspensionBehaviorHold: + [obs->queue addObject: d]; + break; + case NSNotificationSuspensionBehaviorDeliverImmediately: + [obs->queue addObject: d]; + break; + } + } + } + + /* + * Now perform the actual posting of the notification to the observers in + * our array. + */ + for (pos = [observers count]; pos > 0; pos--) + { + GDNCObserver *obs = [observers objectAtIndex: pos]; + + if (obs->client->suspended == NO || deliverImmediately == YES) + { + /* + * Post notifications to the observer until: + * an exception (obs is set to nil) + * the queue is empty ([obs->queue count] == 0) + * the observer is removed (obs is not in allObservers) + */ + while (obs != nil && [obs->queue count] > 0 && + NSHashGet(allObservers, obs) != 0) + { + NS_DURING + { + GDNCNotification *n; + + n = [[obs->queue objectAtIndex: 0] retain]; + [obs->queue removeObjectAtIndex: 0]; + [obs->client->client postNotificationName: n->name + object: n->object + userInfo: n->info + selector: obs->selector + to: obs->observer]; + [n release]; + } + NS_HANDLER + { + [obs release]; + obs = nil; + } + NS_ENDHANDLER + } + } + } +} + +- (void) removeObserver: (GDNCObserver*)obs +{ + if (obs->notificationObject) + { + NSMutableArray *objList; + + objList = [observersForObjects objectForKey: obs->notificationObject]; + if (objList != nil) + { + [objList removeObject: obs]; + } + } + if (obs->notificationName) + { + NSMutableArray *namList; + + namList = [observersForNames objectForKey: obs->notificationName]; + if (namList != nil) + { + [namList removeObject: obs]; + } + } + NSHashRemove(allObservers, obs); +} + +- (void) removeObserversForClients: (NSMapTable*)clients +{ + NSMapEnumerator enumerator; + NSObject *client; + GDNCClient *info; + + enumerator = NSEnumerateMapTable(clients); + while (NSNextMapEnumeratorPair(&enumerator, + (void**)&client, (void**)&info) == YES) + { + while ([info->observers count] > 0) + { + [self removeObserver: [info->observers objectAtIndex: 0]]; + } + } +} + +- (void) removeObserver: (unsigned long)anObserver + name: (NSString*)notificationName + object: (NSString*)notificationObject + for: (id)client +{ + if (anObserver == 0) + { + if (notificationName == nil) + { + NSMutableArray *observers; + + /* + * No notification name - so remove all with matching object. + */ + observers = [observersForObjects objectForKey: notificationObject]; + while ([observers count] > 0) + { + GDNCObserver *obs; + + obs = [observers objectAtIndex: 0]; + [self removeObserver: obs]; + } + } + else if (notificationObject == nil) + { + NSMutableArray *observers; + + /* + * No notification object - so remove all with matching name. + */ + observers = [observersForObjects objectForKey: notificationName]; + while ([observers count] > 0) + { + GDNCObserver *obs; + + obs = [observers objectAtIndex: 0]; + [self removeObserver: obs]; + } + } + else + { + NSMutableArray *byName; + NSMutableArray *byObject; + unsigned pos; + + /* + * Remove observers that match both name and object. + */ + byName = [observersForObjects objectForKey: notificationName]; + byObject = [observersForObjects objectForKey: notificationName]; + for (pos = [byName count]; pos > 0; pos--) + { + GDNCObserver *obs; + + obs = [byName objectAtIndex: pos]; + if ([byObject indexOfObjectIdenticalTo: obs] != NSNotFound) + { + [self removeObserver: obs]; + } + } + for (pos = [byObject count]; pos > 0; pos--) + { + GDNCObserver *obs; + + obs = [byObject objectAtIndex: pos]; + if ([byName indexOfObjectIdenticalTo: obs] != NSNotFound) + { + [self removeObserver: obs]; + } + } + } + } + else + { + NSMapTable *table; + GDNCClient *info; + + /* + * If an observer object (as an unsigned) was specified then + * the observer MUST be from this client - so we can look + * through the per-client list of objects. + */ + table = NSMapGet(connections, + [(NSDistantObject*)client connectionForProxy]); + if (table == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"removeObserver with unknown connection"]; + } + info = (GDNCClient*)NSMapGet(table, client); + if (info != nil) + { + unsigned pos = [info->observers count]; + + while (pos > 0) + { + GDNCObserver *obs = [info->observers objectAtIndex: --pos]; + + if (obs->observer == anObserver) + { + if (notificationName == nil || + [notificationName isEqual: obs->notificationName]) + { + if (notificationObject == nil || + [notificationObject isEqual: obs->notificationObject]) + { + [self removeObserver: obs]; + } + } + } + } + } + } +} + +- (void) setSuspended: (BOOL)flag + for: (id)client +{ + NSMapTable *table; + GDNCClient *info; + + table = NSMapGet(connections, [(NSDistantObject*)client connectionForProxy]); + if (table == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"setSuspended: with unknown connection"]; + } + info = (GDNCClient*)NSMapGet(table, client); + if (info == nil) + { + [NSException raise: NSInternalInconsistencyException + format: @"setSuspended: with unregistered client"]; + } + info->suspended = flag; +} + +- (void) unregisterClient: (id)client +{ + NSMapTable *table; + GDNCClient *info; + + table = NSMapGet(connections, [(NSDistantObject*)client connectionForProxy]); + if (table == 0) + { + [NSException raise: NSInternalInconsistencyException + format: @"unregistration with unknown connection"]; + } + info = (GDNCClient*)NSMapGet(table, client); + if (info == nil) + { + [NSException raise: NSInternalInconsistencyException + format: @"unregistration with unregistered client"]; + } + while ([info->observers count] > 0) + { + [self removeObserver: [info->observers objectAtIndex: 0]]; + } + NSMapRemove(table, client); +} + +@end + +int +main() +{ + GDNCServer *server; + NSAutoreleasePool *pool; + + pool = [NSAutoreleasePool new]; + server = [GDNCServer new]; + [pool release]; + if (server != nil) + { + [[NSRunLoop currentRunLoop] run]; + } + exit(0); +} +