/** * @file * @date 17.5.2007 * * Implementation of class HBOutputRedirect. */ #import "HBOutputRedirect.h" /// Global pointer to HBOutputRedirect object that manages redirects for stdout. static HBOutputRedirect *g_stdoutRedirect = nil; /// Global pointer to HBOutputRedirect object that manages redirects for stderr. static HBOutputRedirect *g_stderrRedirect = nil; static int stdoutwrite(void *inFD, const char *buffer, int size); static int stderrwrite(void *inFD, const char *buffer, int size); @interface HBOutputRedirect () { /// Set that contains all registered listeners for this output. NSMutableSet *listeners; /// Selector that is called on listeners to forward the output. SEL forwardingSelector; /// Output stream (@c stdout or @c stderr) redirected by this object. FILE *stream; /// Pointer to old write function for the stream. int (*oldWriteFunc)(void *, const char *, int); } @end @interface HBOutputRedirect (Private) - (instancetype)initWithStream:(FILE *)aStream selector:(SEL)aSelector; - (void)startRedirect; - (void)stopRedirect; - (void)forwardOutput:(NSData *)data; @end /** * Function that replaces stdout->_write and forwards stdout to g_stdoutRedirect. */ int stdoutwrite(void *inFD, const char *buffer, int size) { @autoreleasepool { NSData *data = [[NSData alloc] initWithBytes:buffer length:size]; [g_stdoutRedirect performSelectorOnMainThread:@selector(forwardOutput:) withObject:data waitUntilDone:NO]; } return size; } int stderrwrite(void *inFD, const char *buffer, int size) { @autoreleasepool { NSData *data = [[NSData alloc] initWithBytes:buffer length:size]; [g_stderrRedirect performSelectorOnMainThread:@selector(forwardOutput:) withObject:data waitUntilDone:NO]; } return size; } @implementation HBOutputRedirect /** * Returns HBOutputRedirect object used to redirect stdout. */ + (instancetype)stdoutRedirect { if (!g_stdoutRedirect) g_stdoutRedirect = [[HBOutputRedirect alloc] initWithStream:stdout selector:@selector(stdoutRedirect:)]; return g_stdoutRedirect; } /** * Returns HBOutputRedirect object used to redirect stderr. */ + (instancetype)stderrRedirect { if (!g_stderrRedirect) g_stderrRedirect = [[HBOutputRedirect alloc] initWithStream:stderr selector:@selector(stderrRedirect:)]; return g_stderrRedirect; } /** * Adds specified object as listener for this output. Method @c stdoutRedirect: * or @c stderrRedirect: of the listener is called to redirect the output. */ - (void)addListener:(id )aListener { NSAssert2([aListener respondsToSelector:forwardingSelector], @"Object %@ doesn't respond to selector \"%@\"", aListener, NSStringFromSelector(forwardingSelector)); if (![listeners containsObject:aListener]) { [listeners addObject:aListener]; } if ([listeners count] > 0) [self startRedirect]; } /** * Stops forwarding for this output to the specified listener object. */ - (void)removeListener:(id )aListener { if ([listeners containsObject:aListener]) { [listeners removeObject:aListener]; } // If last listener is removed, stop redirecting output and autorelease // self. Remember to set proper global pointer to NULL so the object is // recreated again when needed. if ([listeners count] == 0) { [self stopRedirect]; if (self == g_stdoutRedirect) g_stdoutRedirect = nil; else if (self == g_stderrRedirect) g_stderrRedirect = nil; } } @end @implementation HBOutputRedirect (Private) /** * Private constructor which should not be called from outside. This is used to * initialize the class at @c stdoutRedirect and @c stderrRedirect. * * @param aStream Stream that will be redirected (stdout or stderr). * @param aSelector Selector that will be called in listeners to redirect the stream. * * @return New HBOutputRedirect object. */ - (instancetype)initWithStream:(FILE *)aStream selector:(SEL)aSelector { if (self = [super init]) { listeners = [[NSMutableSet alloc] init]; forwardingSelector = aSelector; stream = aStream; oldWriteFunc = NULL; } return self; } /** * Starts redirecting the stream by redirecting its output to function * @c stdoutwrite() or @c stderrwrite(). Old _write function is stored to * @c oldWriteFunc so it can be restored. */ - (void)startRedirect { if (!oldWriteFunc) { oldWriteFunc = stream->_write; stream->_write = stream == stdout ? stdoutwrite : stderrwrite; } } /** * Stops redirecting of the stream by returning the stream's _write function * to original. */ - (void)stopRedirect { if (oldWriteFunc) { stream->_write = oldWriteFunc; oldWriteFunc = NULL; } } /** * Called from @c stdoutwrite() and @c stderrwrite() to forward the output to * listeners. */ - (void)forwardOutput:(NSData *)data { NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; if (string) { [listeners makeObjectsPerformSelector:forwardingSelector withObject:string]; } } @end