1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include "WORunLoop.h"
23#include "common.h"
24
25// TODO: not used anymore?
26
27#if 0
28#if !LIB_FOUNDATION_LIBRARY && !APPLE_Foundation_LIBRARY && !NeXT_Foundation_LIBRARY
29
30#ifndef CREATE_AUTORELEASE_POOL
31#  define CREATE_AUTORELEASE_POOL(pool) \
32            id pool = [[NSAutoreleasePool alloc] init]
33#endif
34
35#include <sys/types.h>
36#include <sys/errno.h>
37#include <errno.h>
38
39#include <sys/time.h>	/* for struct timeval */
40#include <string.h>
41#include <memory.h>
42#include <libc.h>
43#include <unistd.h>
44#include <sys/select.h>
45
46#include "WORunLoop.h"
47#import <Foundation/Foundation.h>
48
49#if NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY
50#  include <FoundationExt/objc-runtime.h>
51#else
52#  include <extensions/objc-runtime.h>
53#endif
54
55#if 0
56#warning breaks AppKit, should *extend* NSRunLoop on MacOSX
57@implementation NSRunLoop(Override)
58+ (NSRunLoop *)currentRunLoop {
59  return [WORunLoop currentRunLoop];
60}
61@end
62#endif
63
64#if 0
65typedef enum {
66  NSPosixNoActivity = 0,
67  NSPosixReadableActivity = 1,
68  NSPosixWritableActivity = 2,
69  NSPosixExceptionalActivity = 4
70} NSPosixFileActivities;
71#endif
72
73NSString* NSFileObjectBecameActiveNotificationName =
74  @"NSFileObjectBecameActiveNotificationName";
75
76static char *activityDesc[8] = {
77  "---", // 0
78  "--R", // 1
79  "-W-", // 2
80  "-WR", // 3
81  "E--", // 4
82  "E-R", // 5
83  "EW-", // 6
84  "EWR"  // 7
85};
86
87@interface WORunLoopFileObjectInfo : NSObject
88{
89  id                    fileObject;
90  NSPosixFileActivities watchedActivities;
91  BOOL                  canCheckAlive;
92}
93
94- (id)initWithFileObject:(id)_fileObject
95  activities:(NSPosixFileActivities)_activities;
96
97- (BOOL)isAlive;
98- (int)fileDescriptor;
99- (NSPosixFileActivities)watchedActivities;
100
101- (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd;
102
103@end
104
105@implementation WORunLoopFileObjectInfo
106
107- (id)initWithFileObject:(id)_fileObject
108  activities:(NSPosixFileActivities)_activities
109{
110  self->fileObject        = RETAIN(_fileObject);
111  self->watchedActivities = _activities;
112  self->canCheckAlive     = [_fileObject respondsToSelector:@selector(isAlive)];
113  return self;
114}
115- (id)init
116{
117  [self errorWithFormat:@"do not use init with WORunLoopFileObjectInfo .."];
118  AUTORELEASE(self);
119  return nil;
120}
121
122- (void)dealloc
123{
124  RELEASE(self->fileObject); self->fileObject = nil;
125  [super dealloc];
126}
127
128- (BOOL)isEqual:(WORunLoopFileObjectInfo*)otherInfo
129{
130  return [self->fileObject isEqual:otherInfo->fileObject];
131}
132
133- (BOOL)isAlive {
134  return (self->canCheckAlive) ? [self->fileObject isAlive] : YES;
135}
136- (int)fileDescriptor
137{
138  return [self->fileObject fileDescriptor];
139}
140
141- (NSPosixFileActivities)watchedActivities
142{
143  return self->watchedActivities;
144}
145
146- (void)activity:(NSPosixFileActivities)_activity onDescriptor:(int)_fd
147{
148  //NSLog(@"FileObject %@ was active ..", self->fileObject);
149
150  [[NSNotificationCenter defaultCenter]
151                         postNotificationName:
152                           NSFileObjectBecameActiveNotificationName
153                         object:self->fileObject];
154}
155
156- (NSString *)description
157{
158  return [NSString stringWithFormat:
159                     @"<%@[0x%p]: object=%@ actitivity=%s>",
160                     NSStringFromClass([self class]), self,
161                     self->fileObject,
162                     activityDesc[self->watchedActivities]
163                   ];
164}
165
166@end
167
168@interface WORunLoopTimerInfo : NSObject
169{
170    NSTimer* timer;
171    NSDate* fireDate;
172}
173
174+ (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)timer;
175- (void)recomputeFireDate;
176- (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject;
177- (NSTimer*)timer;
178- (NSDate*)fireDate;
179@end
180
181@implementation WORunLoopTimerInfo
182
183+ (WORunLoopTimerInfo*)infoWithTimer:(NSTimer*)aTimer
184{
185    WORunLoopTimerInfo* info = [self new];
186
187    info->timer    = RETAIN(aTimer);
188    info->fireDate = RETAIN([aTimer fireDate]);
189    return AUTORELEASE(info);
190}
191
192- (void)dealloc
193{
194    RELEASE(timer);
195    RELEASE(fireDate);
196    [super dealloc];
197}
198
199- (void)recomputeFireDate
200{
201  if ([timer isValid]) {
202    id tmp = [timer fireDate];
203    ASSIGN(fireDate, tmp);
204  }
205}
206
207- (NSComparisonResult)compare:(WORunLoopTimerInfo*)anObject
208{
209    return [fireDate compare:anObject->fireDate];
210}
211
212- (NSTimer*)timer			{ return timer; }
213- (NSDate*)fireDate			{ return fireDate; }
214
215@end
216
217@interface WORunLoopActionHolder : NSObject
218{
219    id target;
220    id argument;
221    SEL action;
222    int order;
223}
224+ objectWithTarget:(id)target
225  argument:(id)argument
226  selector:(SEL)action
227  order:(int)order;
228- (BOOL)isEqual:(id)anotherHolder;
229- (void)execute;
230@end
231
232@implementation WORunLoopActionHolder
233
234+ objectWithTarget:(id)_target
235  argument:(id)_argument
236  selector:(SEL)_action
237  order:(int)_order
238{
239    WORunLoopActionHolder* holder = AUTORELEASE([self alloc]);
240
241    holder->target = RETAIN(_target);
242    holder->argument = RETAIN(_argument);
243    holder->action = _action;
244    holder->order = _order;
245
246    return holder;
247}
248
249- (unsigned)hash
250{
251  return [(NSObject*)target hash];
252}
253
254- (BOOL)isEqual:(WORunLoopActionHolder*)anotherHolder
255{
256    return [target isEqual:anotherHolder->target]
257	    && [argument isEqual:anotherHolder->argument]
258	    && sel_isEqual(action, anotherHolder->action);
259}
260
261- (void)execute
262{
263    [target performSelector:action withObject:argument];
264}
265
266- (NSComparisonResult)compare:(WORunLoopActionHolder*)anotherHolder
267{
268    return order - anotherHolder->order;
269}
270
271@end
272
273
274@interface WORunLoopInputManager : NSObject
275{
276    NSMutableArray* fileObjects;
277    NSMutableArray* timers;
278    NSMutableArray* otherOperations;
279}
280
281- (void)addFileObject:(id)_fileObject
282  activities:(NSPosixFileActivities)_activities;
283- (void)removeFileObject:(id)_fileObject;
284
285- (void)addTimer:(NSTimer*)aTimer;
286
287- (NSMutableArray*)fileObjects;
288- (NSMutableArray*)timers;
289
290- (void)addOperation:(WORunLoopActionHolder*)holder;
291- (void)removeOperation:(WORunLoopActionHolder*)holder;
292- (void)performAdditionalOperations;
293@end
294
295
296@implementation WORunLoopInputManager
297
298- init
299{
300    fileObjects     = [NSMutableArray new];
301    timers          = [NSMutableArray new];
302    otherOperations = [NSMutableArray new];
303    return [super init];
304}
305
306- (void)dealloc
307{
308    RELEASE(fileObjects);
309    RELEASE(timers);
310    RELEASE(otherOperations);
311    [super dealloc];
312}
313
314- (void)addFileObject:(id)_fileObject
315  activities:(NSPosixFileActivities)_activities
316{
317    WORunLoopFileObjectInfo *info = nil;
318    //NSAssert(_activities, @"no activity to watch ?!");
319    info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
320                                     initWithFileObject:_fileObject
321                                     activities:_activities];
322    [self->fileObjects addObject:info];
323    //NSLog(@"file objects now: %@", self->fileObjects);
324    RELEASE(info); info = nil;
325}
326- (void)removeFileObject:(id)_fileObject
327{
328    WORunLoopFileObjectInfo *info = nil;
329    info = [[WORunLoopFileObjectInfo allocWithZone:[self zone]]
330                                     initWithFileObject:_fileObject
331                                     activities:0];
332    [self->fileObjects removeObject:info];
333    //NSLog(@"file objects now: %@", self->fileObjects);
334    RELEASE(info); info = nil;
335}
336
337- (void)addTimer:(NSTimer*)aTimer
338{
339    [timers addObject:[WORunLoopTimerInfo infoWithTimer:aTimer]];
340}
341
342- (void)addOperation:(WORunLoopActionHolder*)holder
343{
344    [otherOperations addObject:holder];
345    [otherOperations sortUsingSelector:@selector(compare:)];
346}
347
348- (void)removeOperation:(WORunLoopActionHolder*)holder
349{
350    [otherOperations removeObject:holder];
351}
352
353- (void)performAdditionalOperations
354{
355    [otherOperations makeObjectsPerformSelector:@selector(execute)];
356}
357
358- (NSMutableArray*)fileObjects { return fileObjects; }
359- (NSMutableArray*)timers      { return timers; }
360
361@end /* WORunLoopInputManager */
362
363@implementation WORunLoop
364
365/* Class variable */
366static WORunLoop *currentRunLoop = nil;
367static BOOL      taskIsMultithreaded = NO;
368
369+ (void)error:(id)_o {
370  NSLog(@"ERROR:");
371  NSLog(@"  %@", _o);
372}
373
374+ (NSRunLoop *)currentRunLoop
375{
376  if (taskIsMultithreaded) {
377    NSLog(@"WORunLoop does not work multithreaded, exit ..");
378    return nil;
379  }
380  else {
381    if (!currentRunLoop)
382	    currentRunLoop = [[self alloc] init];
383    return currentRunLoop;
384  }
385}
386
387- (id)init {
388  self->inputsForMode = [[NSMutableDictionary allocWithZone:[self zone]] init];
389  self->mode = RETAIN(NSDefaultRunLoopMode);
390  return self;
391}
392
393- (void)dealloc {
394  RELEASE(self->inputsForMode);
395  RELEASE(self->mode);
396  [super dealloc];
397}
398
399- (NSString *)currentMode {
400    return self->mode;
401}
402
403static inline WORunLoopInputManager*
404_getInputManager(WORunLoop *self, NSString *_mode)
405{
406  WORunLoopInputManager* inputManager;
407
408  inputManager = [self->inputsForMode objectForKey:_mode];
409  if (inputManager == nil) {
410    inputManager = [WORunLoopInputManager new];
411    [self->inputsForMode setObject:inputManager forKey:_mode];
412    RELEASE(inputManager);
413  }
414  return inputManager;
415}
416
417static int compare_fire_dates(id timer1, id timer2, void* userData)
418{
419  return [[timer1 fireDate] compare:[timer2 fireDate]];
420}
421
422- (NSDate*)limitDateForMode:(NSString*)aMode
423{
424    NSString       *format = @"%s: Caught exception %@ with reason %@ ";
425    NSMutableArray *timers = [[inputsForMode objectForKey:aMode] timers];
426    volatile int   i, count;
427    NSMutableArray *copyOfTimers;
428
429    ASSIGN(mode, aMode);
430
431    /* Remove invalid timers */
432    for(count = [timers count], i = count - 1; i >= 0; i--)
433        if(![[[timers objectAtIndex:i] timer] isValid]) {
434            [timers removeObjectAtIndex:i];
435	    count--;
436        }
437
438    /* Currently only timers have limit dates associated with them */
439    if(!count)
440        return nil;
441
442    copyOfTimers = [timers mutableCopy];
443
444    /* Sort the timers based on their fire date */
445    [copyOfTimers sortUsingFunction:compare_fire_dates context:NULL];
446
447    /* Fire all the timers with their fire date expired */
448    for(i = 0; i < count; i++) {
449        WORunLoopTimerInfo* timerInfo = [copyOfTimers objectAtIndex:i];
450        NSDate* fireDate = [timerInfo fireDate];
451        NSDate* currentDate = [NSDate date];
452
453        if([fireDate earlierDate:currentDate] == fireDate
454	   || [fireDate isEqualToDate:currentDate]) {
455            NSTimer* timer = [timerInfo timer];
456            NS_DURING
457	      [timer fire];
458            NS_HANDLER
459	      NSLog(format, __PRETTY_FUNCTION__,
460                    [localException name], [localException reason]);
461            NS_ENDHANDLER;
462
463#if 0
464#warning no repeated timers !
465            if(![timer repeats])
466#endif
467                [timer invalidate];
468        }
469    }
470
471    RELEASE(copyOfTimers);
472
473    /* Recompute the fire dates for this cycle */
474    [timers makeObjectsPerformSelector:@selector(recomputeFireDate)];
475
476    /* Sort the timers based on their fire date */
477    [timers sortUsingFunction:compare_fire_dates context:NULL];
478
479    return [timers count] ? [[timers objectAtIndex:0] fireDate] : nil;
480}
481
482- (void)addTimer:(NSTimer*)aTimer
483	forMode:(NSString*)aMode
484{
485    [_getInputManager(self, aMode) addTimer:aTimer];
486}
487
488- (BOOL)runMode:(NSString*)aMode
489	beforeDate:(NSDate*)limitDate
490{
491    id inputManager, fileObjects;
492    NSArray* timers;
493    NSDate* date;
494
495    /* Retain the limitDate so it doesn't get released by limitDateForMode:
496	if it fires a timer that has as fireDate the limitDate.
497	(bug report from Benhur Stein <Benhur-de-Oliveira.Stein@imag.fr>)
498      */
499    (void)RETAIN(limitDate);
500
501    inputManager = [inputsForMode objectForKey:aMode];
502    timers       = [inputManager timers];
503    fileObjects  = [inputManager fileObjects];
504
505    if (([timers count] != 0) || ([fileObjects count] != 0)) {
506	CREATE_AUTORELEASE_POOL(pool);
507
508	date = [self limitDateForMode:aMode];
509	date = date ? [date earlierDate:limitDate] : limitDate;
510	[self acceptInputForMode:aMode beforeDate:date];
511	RELEASE(pool);
512	RELEASE(limitDate);
513	return YES;
514    }
515
516    RELEASE(limitDate);
517    return NO;
518}
519
520/*  Runs the loop until limitDate or until the earliest limit date for input
521    sources in the specified mode. */
522- (void)acceptInputForMode:(NSString*)aMode
523	beforeDate:(NSDate*)limitDate
524{
525    id              inputManager, fileObjects;
526    struct timeval  tp = { 0, 0 };
527    struct timeval* timeout = NULL;
528    NSTimeInterval  delay = 0;
529    fd_set          readSet, writeSet, exceptionsSet;
530    volatile int    i, r, count;
531
532    ASSIGN(mode, aMode);
533
534    if(limitDate == nil) // delay = 0
535	limitDate = [NSDate distantFuture];
536    else {
537	delay = [limitDate timeIntervalSinceNow];
538	    /* delay > 0 means a future date */
539
540	/* If limitDate is in the past return */
541	if(delay < 0)
542	    return;
543    }
544
545    inputManager = [inputsForMode objectForKey:aMode];
546    fileObjects  = [inputManager fileObjects];
547
548    /* Compute the timeout for select */
549    if([limitDate isEqual:[NSDate distantFuture]])
550	timeout = NULL;
551    else {
552	tp.tv_sec = delay;
553	tp.tv_usec = (delay - (NSTimeInterval)tp.tv_sec) * 1000000.0;
554	timeout = &tp;
555    }
556
557    ASSIGN(mode, aMode);
558
559    FD_ZERO(&readSet);
560    FD_ZERO(&writeSet);
561    FD_ZERO(&exceptionsSet);
562
563    do {
564        count = [fileObjects count];
565        for (i = 0; i < count; i++) {
566            WORunLoopFileObjectInfo *info;
567            NSPosixFileActivities   fileActivity;
568            int                     fd;
569
570            info = [fileObjects objectAtIndex:i];
571            if (![info isAlive])
572                continue;
573
574            fileActivity = [info watchedActivities];
575            fd           = [info fileDescriptor];
576
577            if (fd >= 0) {
578#if !defined(__MINGW32__) /* on Windows descriptors can be BIG */
579                if (fd >= FD_SETSIZE) {
580                    NSLog(@"%s: fd %i of %@ exceeds select size %i",
581                          __PRETTY_FUNCTION__,
582                          fd, info, FD_SETSIZE);
583                    continue;
584                }
585#endif /* !defined(__MINGW32__) */
586
587                //NSLog(@"registering activity %s for fd %i ..",
588                //      activityDesc[fileActivity], fd);
589
590                if (fileActivity & NSPosixReadableActivity)
591                    FD_SET(fd, &readSet);
592                if (fileActivity & NSPosixWritableActivity)
593                    FD_SET(fd, &writeSet);
594                if (fileActivity & NSPosixExceptionalActivity)
595                    FD_SET(fd, &exceptionsSet);
596            }
597        }
598
599        // ???: errno = 0; What is this good for ?
600	r = select(FD_SETSIZE, &readSet, &writeSet, &exceptionsSet, timeout);
601	if (r == -1) {
602	    if (errno == EINTR) {
603		/* Interrupt occured; break the loop to give a chance to
604		   UnixSignalHandler to handle the signals. */
605		errno = 0;
606		break;
607	    }
608	    else {
609		NSLog(@"%s: select() error: '%s'",
610                      __PRETTY_FUNCTION__, strerror (errno));
611                break;
612            }
613	    errno = 0;
614	}
615    } while (r == -1);
616
617    if(r > 0) {
618        id fileObjectsCopy;
619	NSString* format = @"%s: Caught exception %@ with reason %@ ";
620
621        *(&fileObjectsCopy) = nil;
622
623	NS_DURING {
624            // made copy, so that modifications in the delegate don't
625            // alter the loop
626            fileObjectsCopy = [fileObjects copyWithZone:[self zone]];
627            count           = [fileObjectsCopy count];
628
629            for (i = 0; (i < count) && (r > 0); i++) {
630                WORunLoopFileObjectInfo *info;
631                NSPosixFileActivities   activity = 0;
632                int fd;
633
634                info = [fileObjectsCopy objectAtIndex:i];
635                fd   = [info fileDescriptor];
636
637                if (fd >= 0) {
638                    //NSLog(@"checking activity for %i info %@ ..", fd, info);
639
640                    if (FD_ISSET(fd, &readSet)) {
641                        activity |= NSPosixReadableActivity;
642                        r--;
643                    }
644                    if (FD_ISSET(fd, &writeSet)) {
645                        activity |= NSPosixWritableActivity;
646                        r--;
647                    }
648                    if (FD_ISSET(fd, &exceptionsSet)) {
649                        activity |= NSPosixExceptionalActivity;
650                        r--;
651                    }
652
653                    if (activity != 0)
654                        [info activity:activity onDescriptor:fd];
655                }
656            }
657            if (r > 0) {
658              [self warnWithFormat:@"could not resolve all activities (%i) ..",
659                      r);
660            }
661	}
662        NS_HANDLER {
663          [self errorWithFormat:format, __PRETTY_FUNCTION__,
664                  [localException name], [localException reason]];
665	}
666        NS_ENDHANDLER;
667
668	RELEASE(fileObjectsCopy); fileObjectsCopy = nil;
669    }
670
671    [inputManager performAdditionalOperations];
672#if !NeXT_Foundation_LIBRARY
673    [NSNotificationQueue runLoopASAP];
674#endif
675}
676
677- (void)runUntilDate:(NSDate*)limitDate
678{
679    BOOL shouldContinue = YES;
680
681    if(!limitDate)
682	limitDate = [NSDate distantFuture];
683    else {
684	/* If limitDate is in the past return */
685	if([limitDate timeIntervalSinceNow] < 0)
686	    return;
687    }
688
689    while (shouldContinue) {
690        CREATE_AUTORELEASE_POOL(pool);
691
692        if ([limitDate laterDate:[NSDate date]] == limitDate) {
693	    if([self runMode:NSDefaultRunLoopMode beforeDate:limitDate] == NO)
694		shouldContinue = NO;
695        }
696	else
697	    shouldContinue = NO;
698        RELEASE(pool);
699    }
700}
701
702- (void)run
703{
704    [self runUntilDate:[NSDate distantFuture]];
705}
706
707- (void)performSelector:(SEL)aSelector
708  target:(id)target
709  argument:(id)anArgument
710  order:(unsigned)order
711  modes:(NSArray*)modes
712{
713    id holder = [WORunLoopActionHolder objectWithTarget:target
714					argument:anArgument
715					selector:aSelector
716					order:order];
717    int i, count = [modes count];
718
719    for (i = 0; i < count; i++)
720	[[inputsForMode objectForKey:[modes objectAtIndex:i]]
721	    addOperation:holder];
722}
723
724- (void)cancelPerformSelector:(SEL)aSelector
725  target:(id)target
726  argument:(id)anArgument
727{
728    id holder = [WORunLoopActionHolder objectWithTarget:target
729					argument:anArgument
730					selector:aSelector
731					order:0];
732    id enumerator = [inputsForMode keyEnumerator];
733    id aMode;
734
735    while ((aMode = [enumerator nextObject]))
736	[[inputsForMode objectForKey:aMode] removeOperation:holder];
737}
738
739/* Monitoring file objects */
740
741- (void)addFileObject:(id)_fileObject
742  activities:(NSPosixFileActivities)_activities
743  forMode:(NSString *)_mode
744{
745    [_getInputManager(self, _mode) addFileObject:_fileObject
746                                   activities:_activities];
747}
748
749- (void)removeFileObject:(id)_fileObject
750  forMode:(NSString *)_mode
751{
752    [_getInputManager(self, _mode) removeFileObject:_fileObject];
753}
754
755@end /* WORunLoop */
756
757#endif /* !LIB_FOUNDATION_LIBRARY */
758#endif // 0
759