libs-base/Source/GSAvahiRunLoopIntegration.m
ericwa a55aa925ee Avahi bugfixes
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@31492 72102866-910b-0410-8b05-ffd578937521
2010-10-08 22:24:21 +00:00

564 lines
14 KiB
Objective-C

/* Classes for integration of avahi-client into NSRunLoop.
Copyright (C) 2010 Free Software Foundation, Inc.
Written by: Niels Grewe <niels.grewe@halbordnung.de>
Date: March 2010
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 Lesser 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 Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
*/
#import "GSAvahiRunLoopIntegration.h"
#define CTX(x) GSAvahiRunLoopContext *ctx = (GSAvahiRunLoopContext*)x
@interface GSAvahiWatcher: NSObject <RunLoopEvents>
{
//The callback to call for avahi.
AvahiWatchCallback callback;
BOOL callbackInProgress;
AvahiWatchEvent oldEvents;
AvahiWatchEvent lastEvent;
int fileDesc;
GSAvahiRunLoopContext *ctx;
void *userData;
}
@end
@implementation GSAvahiWatcher
- (void)listenForEvents: (AvahiWatchEvent)events
saveState: (BOOL)saveState
{
/* FIXME: NSRunLoop doesn't expose equivalents for POLLERR and POLLHUP but
* Avahi doesn't seem to strictly require them (their Qt API doesn't handle
* them either). Still, it would be nice to handle AVAHI_WATCH_(ERR|HUP)
* here.
*/
// Remove old events:
if (!(events & AVAHI_WATCH_IN)
&& (oldEvents & AVAHI_WATCH_IN))
{
[[ctx runLoop] removeEvent: (void*)(intptr_t)fileDesc
type: ET_RDESC
forMode: [ctx mode]
all: NO];
}
if (!(events & AVAHI_WATCH_OUT)
&& (oldEvents & AVAHI_WATCH_OUT))
{
[[ctx runLoop] removeEvent: (void*)(intptr_t)fileDesc
type: ET_WDESC
forMode: [ctx mode]
all: NO];
}
// Remember event state:
if (saveState)
{
oldEvents = events;
}
// Dispatch new events to the runLoop:
if (events & AVAHI_WATCH_IN)
{
[[ctx runLoop] addEvent: (void*)(intptr_t)fileDesc
type: ET_RDESC
watcher: self
forMode: [ctx mode]];
}
if (events & AVAHI_WATCH_OUT)
{
[[ctx runLoop] addEvent: (void*)(intptr_t)fileDesc
type: ET_WDESC
watcher: self
forMode: [ctx mode]];
}
}
- (void)listenForEvents: (AvahiWatchEvent)events
{
[self listenForEvents: events
saveState: YES];
}
- (void)unschedule
{
// Don't save the new event state (i.e. no events) if we are unscheduling
// the watcher. We might want to reschedule it with the prior state.
[self listenForEvents: (AvahiWatchEvent)0
saveState: NO];
}
- (void)reschedule
{
[self listenForEvents: oldEvents
saveState: NO];
}
- (id)initWithCallback: (AvahiWatchCallback)cback
andContext: (GSAvahiRunLoopContext*)aCtx
onEvent: (AvahiWatchEvent)someEvents
forFd: (int)fd
userData: (void*)ud
{
if (nil == (self = [super init]))
{
return nil;
}
fileDesc = fd;
// The context retains its watchers and timers:
ctx = aCtx;
callback = cback;
userData = ud;
[self listenForEvents: someEvents];
return self;
}
- (AvahiWatchEvent)getEvents
{
if (callbackInProgress)
{
return (AvahiWatchEvent)0;
}
return lastEvent;
}
- (void)removeFromContext
{
[self unschedule];
[ctx removeWatcher: self];
// Don't reference the context anymore, since it won't have any chance of
// notifying us if it goes away.
ctx = nil;
}
- (void)receivedEvent: (void*)data
type: (RunLoopEventType)type
extra: (void*)extra
forMode: (NSString*)mode
{
int fd = (int)(intptr_t)data;
if(fileDesc != fd)
{
//Not good
return;
}
switch (type)
{
case ET_RDESC:
lastEvent = AVAHI_WATCH_IN;
// Remove the corresponding bit from the event mask:
oldEvents = (oldEvents ^ AVAHI_WATCH_IN);
break;
case ET_WDESC:
lastEvent = AVAHI_WATCH_OUT;
// Remove the corresponding bit from the event mask:
oldEvents = (oldEvents ^ AVAHI_WATCH_OUT);
break;
default:
return;
}
/* FIXME: NSRunLoop doesn't expose equivalents for POLLERR and POLLHUP but
* Avahi doesn't seem to strictly require them (their Qt API doesn't handle
* them either).
*/
callbackInProgress = YES;
callback((AvahiWatch*)self, fd, lastEvent, userData);
callbackInProgress = NO;
}
- (void)setContext: (GSAvahiRunLoopContext*)aCtxt
{
ctx = aCtxt;
}
- (void)dealloc
{
// Remove all leftover event-handlers from the runLoop:
[self listenForEvents: (AvahiWatchEvent)0];
[super dealloc];
}
@end
@interface GSAvahiTimer: NSObject
{
GSAvahiRunLoopContext *ctx;
AvahiTimeoutCallback callback;
NSTimer *timer;
NSDate *fireDate;
void *userData;
}
@end
@implementation GSAvahiTimer
- (void)didTimeout: (NSTimer*)timer
{
callback((AvahiTimeout*)self, userData);
}
- (void)setTimerToInterval: (NSTimeInterval)interval
{
// Invalidate the old timer;
if (timer != nil)
{
[timer invalidate];
timer = nil;
}
// NOTE: the timer ivar is a weak reference; runloops retain their
// timers.
timer = [NSTimer timerWithTimeInterval: interval
target: self
selector: @selector(didTimeout:)
userInfo: nil
repeats: NO];
[[ctx runLoop] addTimer: timer
forMode: [ctx mode]];
}
- (void)setTimerToTimeval: (const struct timeval*)tv
{
// Invalidate the old timer
if (timer != nil)
{
[timer invalidate];
timer = nil;
}
if (NULL != tv)
{
// Construct a NSTimeInterval for the timer:
NSTimeInterval interval = (NSTimeInterval)tv->tv_sec;
interval += (NSTimeInterval)(tv->tv_usec / 1000000.0);
[self setTimerToInterval: interval];
}
}
- (id)initWithCallback: (AvahiTimeoutCallback)aCallback
andContext: (GSAvahiRunLoopContext*)aCtx
forTimeval: (const struct timeval*)tv
userData: (void*)ud
{
if (nil == (self = [super init]))
{
return nil;
}
// The context retains its watchers and timeouts:
ctx = aCtx;
callback = aCallback;
userData = ud;
[self setTimerToTimeval: tv];
return self;
}
- (void)unschedule
{
if ([timer isValid])
{
fireDate = [[timer fireDate] retain];
[timer invalidate];
timer = nil;
}
}
- (void)removeFromContext
{
[self unschedule];
[ctx removeTimeout: self];
ctx = nil;
}
- (void)setContext: (GSAvahiRunLoopContext*)aCtxt
{
ctx = aCtxt;
}
- (void)reschedule
{
// Only reschedule if fireDate has been set, otherwise the Avahi layer will
// schedule a new timer.
if (nil != fireDate)
{
NSTimeInterval interval = [fireDate timeIntervalSinceNow];
[self setTimerToInterval: MAX(0.1,interval)];
[fireDate release];
fireDate = nil;
}
}
- (void)dealloc
{
if (nil != timer)
{
[timer invalidate];
}
[super dealloc];
}
@end
static AvahiWatch*
GSAvahiWatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent
event, AvahiWatchCallback callback, void *userData)
{
// NOTE: strangly enough, the userData parameter is not the userdata we
// passed to the poll structure (it is somehow related to the dbus
// internals).
CTX(api->userdata);
GSAvahiWatcher *w = [ctx avahiWatcherWithCallback: callback
onEvent: event
forFileDescriptor: fd
userData: userData];
// NOTE: avahi defines AvahiWatch as a struct, since we only pass around
// pointers to those, we can just cast the pointer to our watcher object to
// AvahiWatch*.
return (AvahiWatch*)w;
}
static void
GSAvahiWatchUpdate(AvahiWatch *watch, AvahiWatchEvent event)
{
[(GSAvahiWatcher*)watch listenForEvents: event];
}
static AvahiWatchEvent
GSAvahiWatchGetEvents(AvahiWatch *watch)
{
return [(GSAvahiWatcher*)watch getEvents];
}
static void
GSAvahiWatchFree(AvahiWatch *watch)
{
[(GSAvahiWatcher*)watch removeFromContext];
}
static AvahiTimeout*
GSAvahiTimeoutNew(const AvahiPoll *api,
const struct timeval *tv, AvahiTimeoutCallback callback, void *userData)
{
// NOTE: strangly enough, the userData parameter is not the userdata we
// passed to the poll structure (it is somehow related to the dbus
// internals.)
CTX(api->userdata);
GSAvahiTimer *t = [ctx avahiTimerWithCallback: callback
withTimeval: tv
userData: userData];
// NOTE: Cf. GSAvahiWatchNew().
return (AvahiTimeout*)t;
}
static void
GSAvahiTimeoutUpdate(AvahiTimeout* timeout,
const struct timeval *tv)
{
[(GSAvahiTimer*)timeout setTimerToTimeval: tv];
}
static void
GSAvahiTimeoutFree(AvahiTimeout* timeout)
{
[(GSAvahiTimer*)timeout removeFromContext];
}
@implementation GSAvahiRunLoopContext
- (id)initWithRunLoop: (NSRunLoop*)rl
forMode: (NSString*)aMode
{
if (nil == (self = [super init]))
{
return nil;
}
lock = [[NSLock alloc] init];
[lock setName: @"GSAvahiRunLoopContextLock"];
poll = malloc(sizeof(AvahiPoll));
NSAssert(poll, @"Could not allocate avahi polling structure.");
poll->userdata = (void*)self; //userInfo
poll->watch_new = GSAvahiWatchNew; //create a new GSAvahiWatcher
poll->watch_update = GSAvahiWatchUpdate; //update the watcher
poll->watch_get_events = GSAvahiWatchGetEvents; //retrieve events
poll->watch_free = GSAvahiWatchFree; //remove watcher from context
poll->timeout_new = GSAvahiTimeoutNew; //create a new GSAvahiTimer
poll->timeout_update = GSAvahiTimeoutUpdate; //update the timer
poll->timeout_free = GSAvahiTimeoutFree; //remove the timer from context
//Runloops don't need to be retained;
runLoop = rl;
ASSIGNCOPY(mode,aMode);
children = [[NSMutableArray alloc] init];
return self;
}
- (NSRunLoop*)runLoop
{
// NOTE: We don't protect this with the lock because it will only ever be
// changed by -removeFromRunLoop:forMode: or -scheduleInRunLoop:forMode:,
// which is where we do the locking.
return runLoop;
}
- (NSString*)mode
{
/* NOTE: We don't protect this with the lock because it will only ever be
* changed by -removeFromRunLoop:forMode: or -scheduleInRunLoop:forMode:,
* which is where we do the locking.
*/
return mode;
}
- (const AvahiPoll*)avahiPoll
{
return (const AvahiPoll*)poll;
}
- (GSAvahiTimer*)avahiTimerWithCallback: (AvahiTimeoutCallback)callback
withTimeval: (const struct timeval*)tv
userData: (void*)ud
{
GSAvahiTimer *timer = nil;
[lock lock];
timer = [[[GSAvahiTimer alloc] initWithCallback: callback
andContext: self
forTimeval: tv
userData: ud] autorelease];
if (nil != timer)
{
[children addObject: timer];
}
[lock unlock];
return timer;
}
- (GSAvahiWatcher*)avahiWatcherWithCallback: (AvahiWatchCallback)callback
onEvent: (AvahiWatchEvent)someEvents
forFileDescriptor: (NSInteger)fd
userData: (void*)ud
{
GSAvahiWatcher *w = nil;
[lock lock];
w = [[[GSAvahiWatcher alloc] initWithCallback: callback
andContext: self
onEvent: someEvents
forFd: fd
userData: ud] autorelease];
if (nil != w)
{
[children addObject: w];
}
[lock unlock];
return w;
}
- (void)removeChild: (id)c
{
if (nil != c)
{
[lock lock];
[children removeObject: c];
[lock unlock];
}
}
- (void)removeWatcher: (GSAvahiWatcher*)w
{
[self removeChild: w];
}
- (void)removeTimeout: (GSAvahiTimer*)at
{
[self removeChild: at];
}
- (void)removeFromRunLoop: (NSRunLoop*)rl
forMode: (NSString*)m
{
if ((rl == runLoop) && [mode isEqualToString: m])
{
[lock lock];
//Test again:
if ((rl == runLoop) && [mode isEqualToString: m])
{
FOR_IN(GSAvahiWatcher*, child, children)
{
[child unschedule];
}
END_FOR_IN(children)
runLoop = nil;
[mode release];
mode = nil;
}
[lock unlock];
}
else
{
//FIXME: Raise exception?
}
}
- (void)scheduleInRunLoop: (NSRunLoop*)rl
forMode: (NSString*)m
{
if ((runLoop == nil) && (mode == nil)
&& ((rl != nil) && (m != nil)))
{
[lock lock];
//Test again:
if ((runLoop == nil) && (mode == nil)
&& ((rl != nil) && (m != nil)))
{
runLoop = rl;
ASSIGNCOPY(mode,m);
FOR_IN(GSAvahiWatcher*, child, children)
{
[child reschedule];
}
END_FOR_IN(children)
}
[lock unlock];
}
else
{
//FIXME: Raise exception.
}
}
- (void)release
{
[super release];
}
- (void)dealloc
{
/* Some avahi internals might still reference the poll structure and could
* try to create additional watchers and timers on the runloop, so we should
* clean it up properly:
*/
poll->userdata = (void*)NULL;
[self removeFromRunLoop: runLoop
forMode: mode];
FOR_IN(GSAvahiWatcher*, child, children)
{
[child setContext: nil];
}
END_FOR_IN(children)
free(poll);
poll = NULL;
[children release];
[mode release];
[lock release];
[super dealloc];
}
@end