summaryrefslogtreecommitdiffstats
path: root/macosx/HBOutputRedirect.m
blob: a4415792a07dbd3d02235aa65bd99d3d23d78f8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/**
 * @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 (Private)
- (id)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)
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSData *data = [[NSData alloc] initWithBytes:buffer length:size];
	[g_stdoutRedirect performSelectorOnMainThread:@selector(forwardOutput:) withObject:data waitUntilDone:NO];
	[data release];
	[pool release];
	return size;
}

/**
 * Function that replaces stderr->_write and forwards stderr to g_stderrRedirect.
 */
int	stderrwrite(void *inFD, const char *buffer, int size)
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSData *data = [[NSData alloc] initWithBytes:buffer length:size];
	[g_stderrRedirect performSelectorOnMainThread:@selector(forwardOutput:) withObject:data waitUntilDone:NO];
	[data release];
	[pool release];
	return size;
}

@implementation HBOutputRedirect

/**
 * Returns HBOutputRedirect object used to redirect stdout.
 */
+ (id)stdoutRedirect
{
	if (!g_stdoutRedirect)
		g_stdoutRedirect = [[HBOutputRedirect alloc] initWithStream:stdout selector:@selector(stdoutRedirect:)];
		
	return g_stdoutRedirect;
}

/**
 * Returns HBOutputRedirect object used to redirect stderr.
 */
+ (id)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 <HBOutputRedirectListening>)aListener
{
	NSAssert2([aListener respondsToSelector:forwardingSelector], @"Object %@ doesn't respond to selector \"%@\"", aListener, NSStringFromSelector(forwardingSelector));

	if (![listeners containsObject:aListener])
	{
		[listeners addObject:aListener];
		[aListener release];
	}
	
	if ([listeners count] > 0)
		[self startRedirect];
}

/**
 * Stops forwarding for this output to the specified listener object.
 */
- (void)removeListener:(id <HBOutputRedirectListening>)aListener
{
	if ([listeners containsObject:aListener])
	{
		[aListener retain];
		[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];
		[self autorelease];

		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 wil be redirected (stdout or stderr).
 * @param aSelector	Selector that will be called in listeners to redirect the stream.
 *
 * @return New HBOutputRedirect object.
 */
- (id)initWithStream:(FILE *)aStream selector:(SEL)aSelector
{
	if (self = [super init])
	{
		listeners = [[NSMutableSet alloc] init];
		forwardingSelector = aSelector;
		stream = aStream;
		oldWriteFunc = NULL;
	}
	return self;
}

/**
 * Frees all the listeners and deallocs the object.
 */
- (void)dealloc
{
	[listeners release];
	[super dealloc];
}

/**
 * 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];
    }
	[string release];
}

@end