1/* Classes for integration of avahi-client into NSRunLoop.
2   Copyright (C) 2010 Free Software Foundation, Inc.
3
4   Written by:  Niels Grewe <niels.grewe@halbordnung.de>
5   Date: March 2010
6
7   This file is part of the GNUstep Base Library.
8
9   This library is free software; you can redistribute it and/or
10   modify it under the terms of the GNU Lesser General Public
11   License as published by the Free Software Foundation; either
12   version 2 of the License, or (at your option) any later version.
13
14   This library is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public
20   License along with this library; if not, write to the Free
21   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22   Boston, MA 02110 USA.
23   */
24
25#import "GSAvahiRunLoopIntegration.h"
26
27#define CTX(x) GSAvahiRunLoopContext *ctx = (GSAvahiRunLoopContext*)x
28@interface GSAvahiWatcher: NSObject <RunLoopEvents>
29{
30  //The callback to call for avahi.
31  AvahiWatchCallback callback;
32  BOOL callbackInProgress;
33  AvahiWatchEvent oldEvents;
34  AvahiWatchEvent lastEvent;
35  int fileDesc;
36  GSAvahiRunLoopContext *ctx;
37  void *userData;
38}
39- (void)listenForEvents: (AvahiWatchEvent)events;
40- (AvahiWatchEvent)getEvents;
41- (void)removeFromContext;
42- (void)setContext: (GSAvahiRunLoopContext*)aCtxt;
43- (void)unschedule;
44- (void)reschedule;
45@end
46
47@implementation GSAvahiWatcher
48- (void) listenForEvents: (AvahiWatchEvent)events
49               saveState: (BOOL)saveState
50{
51  /* FIXME: NSRunLoop doesn't expose equivalents for POLLERR and POLLHUP but
52   * Avahi doesn't seem to strictly require them (their Qt API doesn't handle
53   * them either). Still, it would be nice to handle AVAHI_WATCH_(ERR|HUP)
54   * here.
55   */
56
57  // Remove old events:
58  if (!(events & AVAHI_WATCH_IN)
59    && (oldEvents & AVAHI_WATCH_IN))
60    {
61      [[ctx runLoop] removeEvent: (void*)(intptr_t)fileDesc
62                            type: ET_RDESC
63                         forMode: [ctx mode]
64                             all: NO];
65    }
66  if (!(events & AVAHI_WATCH_OUT)
67    && (oldEvents & AVAHI_WATCH_OUT))
68    {
69      [[ctx runLoop] removeEvent: (void*)(intptr_t)fileDesc
70                            type: ET_WDESC
71                         forMode: [ctx mode]
72                             all: NO];
73    }
74
75  // Remember event state:
76  if (saveState)
77    {
78      oldEvents = events;
79    }
80
81  // Dispatch new events to the runLoop:
82  if (events & AVAHI_WATCH_IN)
83    {
84      [[ctx runLoop] addEvent: (void*)(intptr_t)fileDesc
85                         type: ET_RDESC
86                      watcher: self
87                      forMode: [ctx mode]];
88    }
89  if (events & AVAHI_WATCH_OUT)
90    {
91      [[ctx runLoop] addEvent: (void*)(intptr_t)fileDesc
92                         type: ET_WDESC
93                      watcher: self
94                      forMode: [ctx mode]];
95    }
96}
97
98- (void) listenForEvents: (AvahiWatchEvent)events
99{
100  [self listenForEvents: events
101              saveState: YES];
102}
103
104- (void) unschedule
105{
106  /* Don't save the new event state (i.e. no events) if we are unscheduling
107   * the watcher. We might want to reschedule it with the prior state.
108   */
109  [self listenForEvents: (AvahiWatchEvent)0
110              saveState: NO];
111}
112
113- (void) reschedule
114{
115  [self listenForEvents: oldEvents
116              saveState: NO];
117}
118
119- (id) initWithCallback: (AvahiWatchCallback)cback
120             andContext: (GSAvahiRunLoopContext*)aCtx
121                onEvent: (AvahiWatchEvent)someEvents
122                  forFd: (int)fd
123               userData: (void*)ud
124{
125  if (nil == (self = [super init]))
126    {
127      return nil;
128    }
129  fileDesc = fd;
130  // The context retains its watchers and timers:
131  ctx = aCtx;
132  callback = cback;
133  userData = ud;
134  [self listenForEvents: someEvents];
135  return self;
136}
137
138- (AvahiWatchEvent) getEvents
139{
140  if (callbackInProgress)
141    {
142      return (AvahiWatchEvent)0;
143    }
144  return lastEvent;
145}
146
147- (void) removeFromContext
148{
149  [self unschedule];
150  [ctx removeWatcher: self];
151  // Don't reference the context anymore, since it won't have any chance of
152  // notifying us if it goes away.
153  ctx = nil;
154}
155
156- (void) receivedEvent: (void*)data
157                  type: (RunLoopEventType)type
158                 extra: (void*)extra
159               forMode: (NSString*)mode
160{
161  int fd = (int)(intptr_t)data;
162
163  if (fileDesc != fd)
164    {
165      //Not good
166      return;
167    }
168
169  /* FIXME ... in the following switch, as well as setting lastEvent the
170   * code was clearing the corresponding bit in the oldEvents bitmask.
171   * This was causng a crash becasue it meant that we didn't unregister
172   * the event watcher from the run loop before deallocating it and a
173   * new incoming event was sent to the deallocated instance.
174   * I therefore removed that code, but can't see what it was intended
175   * to do.
176   */
177  switch (type)
178    {
179      case ET_RDESC:
180        lastEvent = AVAHI_WATCH_IN;
181        break;
182      case ET_WDESC:
183        lastEvent = AVAHI_WATCH_OUT;
184        break;
185      default:
186        return;
187    }
188
189  /* FIXME: NSRunLoop doesn't expose equivalents for POLLERR and POLLHUP but
190   * Avahi doesn't seem to strictly require them (their Qt API doesn't handle
191   * them either).
192   */
193  callbackInProgress = YES;
194  callback((AvahiWatch*)self, fd, lastEvent, userData);
195  callbackInProgress = NO;
196}
197
198- (void) setContext: (GSAvahiRunLoopContext*)aCtxt
199{
200  ctx = aCtxt;
201}
202
203- (void) dealloc
204{
205  // Remove all leftover event-handlers from the runLoop:
206  [self listenForEvents: (AvahiWatchEvent)0];
207  [super dealloc];
208}
209@end
210
211
212@interface GSAvahiTimer: NSObject
213{
214  GSAvahiRunLoopContext *ctx;
215  AvahiTimeoutCallback callback;
216  NSTimer *timer;
217  NSDate *fireDate;
218  void *userData;
219}
220@end
221
222@implementation GSAvahiTimer
223- (void) didTimeout: (NSTimer*)timer
224{
225  callback((AvahiTimeout*)self, userData);
226}
227
228- (void) setTimerToInterval: (NSTimeInterval)interval
229{
230  // Invalidate the old timer;
231  if (timer != nil)
232    {
233      [timer invalidate];
234      timer = nil;
235    }
236
237  // NOTE: the timer ivar is a weak reference; runloops retain their
238  // timers.
239  timer = [NSTimer timerWithTimeInterval: interval
240                                  target: self
241                                selector: @selector(didTimeout:)
242                                userInfo: nil
243                                 repeats: NO];
244  [[ctx runLoop] addTimer: timer
245                  forMode: [ctx mode]];
246}
247
248- (void) setTimerToTimeval: (const struct timeval*)tv
249{
250  // Invalidate the old timer
251  if (timer != nil)
252    {
253      [timer invalidate];
254      timer = nil;
255    }
256
257  if (NULL != tv)
258    {
259      // Construct a NSTimeInterval for the timer:
260      NSTimeInterval interval = (NSTimeInterval)tv->tv_sec;
261      interval += (NSTimeInterval)(tv->tv_usec / 1000000.0);
262      [self setTimerToInterval: interval];
263    }
264}
265- (id) initWithCallback: (AvahiTimeoutCallback)aCallback
266             andContext: (GSAvahiRunLoopContext*)aCtx
267             forTimeval: (const struct timeval*)tv
268               userData: (void*)ud
269{
270  if (nil == (self = [super init]))
271    {
272      return nil;
273    }
274  // The context retains its watchers and timeouts:
275  ctx = aCtx;
276  callback = aCallback;
277  userData = ud;
278  [self setTimerToTimeval: tv];
279  return self;
280}
281
282- (void) unschedule
283{
284  if ([timer isValid])
285    {
286      fireDate = [[timer fireDate] retain];
287      [timer invalidate];
288      timer = nil;
289    }
290}
291
292- (void) removeFromContext
293{
294  [self unschedule];
295  [ctx removeTimeout: self];
296  ctx = nil;
297}
298
299- (void) setContext: (GSAvahiRunLoopContext*)aCtxt
300{
301  ctx = aCtxt;
302}
303
304- (void) reschedule
305{
306  // Only reschedule if fireDate has been set, otherwise the Avahi layer will
307  // schedule a new timer.
308  if (nil != fireDate)
309    {
310      NSTimeInterval interval = [fireDate timeIntervalSinceNow];
311      [self setTimerToInterval: MAX(0.1,interval)];
312      [fireDate release];
313      fireDate = nil;
314    }
315}
316
317- (void) dealloc
318{
319  if (nil != timer)
320    {
321      [timer invalidate];
322    }
323  [super dealloc];
324}
325@end
326
327static AvahiWatch*
328GSAvahiWatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent
329  event, AvahiWatchCallback callback, void *userData)
330{
331  // NOTE: strangly enough, the userData parameter is not the userdata we
332  // passed to the poll structure (it is somehow related to the dbus
333  // internals).
334  CTX(api->userdata);
335  GSAvahiWatcher *w = [ctx avahiWatcherWithCallback: callback
336                                            onEvent: event
337                                  forFileDescriptor: fd
338                                           userData: userData];
339  // NOTE: avahi defines AvahiWatch as a struct, since we only pass around
340  // pointers to those, we can just cast the pointer to our watcher object to
341  // AvahiWatch*.
342  return (AvahiWatch*)w;
343}
344
345static void
346GSAvahiWatchUpdate(AvahiWatch *watch, AvahiWatchEvent event)
347{
348  [(GSAvahiWatcher*)watch listenForEvents: event];
349}
350
351static AvahiWatchEvent
352GSAvahiWatchGetEvents(AvahiWatch *watch)
353{
354  return [(GSAvahiWatcher*)watch getEvents];
355}
356
357static void
358GSAvahiWatchFree(AvahiWatch *watch)
359{
360  [(GSAvahiWatcher*)watch removeFromContext];
361}
362
363static AvahiTimeout*
364GSAvahiTimeoutNew(const AvahiPoll *api,
365  const struct timeval *tv, AvahiTimeoutCallback callback, void *userData)
366{
367  // NOTE: strangly enough, the userData parameter is not the userdata we
368  // passed to the poll structure (it is somehow related to the dbus
369  // internals.)
370  CTX(api->userdata);
371  GSAvahiTimer *t = [ctx avahiTimerWithCallback: callback
372                                    withTimeval: tv
373                                       userData: userData];
374  // NOTE: Cf. GSAvahiWatchNew().
375  return (AvahiTimeout*)t;
376}
377
378static void
379GSAvahiTimeoutUpdate(AvahiTimeout* timeout,
380  const struct timeval *tv)
381{
382  [(GSAvahiTimer*)timeout setTimerToTimeval: tv];
383}
384
385static void
386GSAvahiTimeoutFree(AvahiTimeout* timeout)
387{
388  [(GSAvahiTimer*)timeout removeFromContext];
389}
390
391@implementation GSAvahiRunLoopContext
392- (id) initWithRunLoop: (NSRunLoop*)rl
393               forMode: (NSString*)aMode
394{
395  if (nil == (self = [super init]))
396    {
397      return nil;
398    }
399  lock = [[NSLock alloc] init];
400  [lock setName: @"GSAvahiRunLoopContextLock"];
401  poll = malloc(sizeof(AvahiPoll));
402  NSAssert(poll, @"Could not allocate avahi polling structure.");
403  poll->userdata = (void*)self; //userInfo
404  poll->watch_new = GSAvahiWatchNew; //create a new GSAvahiWatcher
405  poll->watch_update = GSAvahiWatchUpdate; //update the watcher
406  poll->watch_get_events = GSAvahiWatchGetEvents; //retrieve events
407  poll->watch_free = GSAvahiWatchFree; //remove watcher from context
408  poll->timeout_new = GSAvahiTimeoutNew; //create a new GSAvahiTimer
409  poll->timeout_update = GSAvahiTimeoutUpdate; //update the timer
410  poll->timeout_free = GSAvahiTimeoutFree; //remove the timer from context
411  //Runloops don't need to be retained;
412  runLoop = rl;
413  ASSIGNCOPY(mode,aMode);
414  children = [[NSMutableArray alloc] init];
415  return self;
416}
417
418- (NSRunLoop*) runLoop
419{
420  // NOTE: We don't protect this with the lock because it will only ever be
421  // changed by -removeFromRunLoop:forMode: or -scheduleInRunLoop:forMode:,
422  // which is where we do the locking.
423  return runLoop;
424}
425
426- (NSString*) mode
427{
428  /* NOTE: We don't protect this with the lock because it will only ever be
429   * changed by -removeFromRunLoop:forMode: or -scheduleInRunLoop:forMode:,
430   * which is where we do the locking.
431   */
432  return mode;
433}
434
435- (const AvahiPoll*) avahiPoll
436{
437  return (const AvahiPoll*)poll;
438}
439
440- (GSAvahiTimer*) avahiTimerWithCallback: (AvahiTimeoutCallback)callback
441                             withTimeval: (const struct timeval*)tv
442                                userData: (void*)ud
443{
444  GSAvahiTimer *timer = nil;
445  [lock lock];
446  timer = [[[GSAvahiTimer alloc] initWithCallback: callback
447                                       andContext: self
448                                       forTimeval: tv
449                                         userData: ud] autorelease];
450  if (nil != timer)
451    {
452      [children addObject: timer];
453    }
454  [lock unlock];
455  return timer;
456}
457
458- (GSAvahiWatcher*) avahiWatcherWithCallback: (AvahiWatchCallback)callback
459                                     onEvent: (AvahiWatchEvent)someEvents
460                           forFileDescriptor: (NSInteger)fd
461                                    userData: (void*)ud
462{
463  GSAvahiWatcher *w = nil;
464
465  [lock lock];
466  w = [[[GSAvahiWatcher alloc] initWithCallback: callback
467                                     andContext: self
468                                        onEvent: someEvents
469                                          forFd: fd
470                                       userData: ud] autorelease];
471
472  if (nil != w)
473    {
474      [children addObject: w];
475    }
476  [lock unlock];
477  return w;
478}
479
480- (void) removeChild: (id)c
481{
482  if (nil != c)
483    {
484      [lock lock];
485      [children removeObject: c];
486      [lock unlock];
487    }
488}
489
490- (void) removeWatcher: (GSAvahiWatcher*)w
491{
492  [self removeChild: w];
493}
494
495- (void) removeTimeout: (GSAvahiTimer*)at
496{
497  [self removeChild: at];
498}
499
500- (void) removeFromRunLoop: (NSRunLoop*)rl
501                   forMode: (NSString*)m
502{
503  [lock lock];
504  if ((rl == runLoop) && [mode isEqualToString: m])
505    {
506      FOR_IN(GSAvahiWatcher*, child, children)
507        {
508          [child unschedule];
509        }
510      END_FOR_IN(children)
511      runLoop = nil;
512      [mode release];
513      mode = nil;
514    }
515  [lock unlock];
516}
517
518- (void) scheduleInRunLoop: (NSRunLoop*)rl
519                   forMode: (NSString*)m
520{
521  [lock lock];
522  if ((runLoop == nil) && (mode == nil)
523    && ((rl != nil) && (m != nil)))
524    {
525      runLoop = rl;
526      ASSIGNCOPY(mode,m);
527      FOR_IN(GSAvahiWatcher*, child, children)
528        {
529          [child reschedule];
530        }
531      END_FOR_IN(children)
532    }
533  [lock unlock];
534}
535
536- (void) dealloc
537{
538  /* Some avahi internals might still reference the poll structure and could
539   * try to create additional watchers and timers on the runloop, so we should
540   * clean it up properly:
541   */
542  poll->userdata = (void*)NULL;
543  [self removeFromRunLoop: runLoop
544                  forMode: mode];
545  FOR_IN(GSAvahiWatcher*, child, children)
546    {
547      [child setContext: nil];
548    }
549  END_FOR_IN(children)
550  free(poll);
551  poll = NULL;
552  [children release];
553  [mode release];
554  [lock release];
555  [super dealloc];
556}
557@end
558