1 /*
2    Copyright (c) 2001, Loki software, inc.
3    All rights reserved.
4 
5    Redistribution and use in source and binary forms, with or without modification,
6    are permitted provided that the following conditions are met:
7 
8    Redistributions of source code must retain the above copyright notice, this list
9    of conditions and the following disclaimer.
10 
11    Redistributions in binary form must reproduce the above copyright notice, this
12    list of conditions and the following disclaimer in the documentation and/or
13    other materials provided with the distribution.
14 
15    Neither the name of Loki software nor the names of its contributors may be used
16    to endorse or promote products derived from this software without specific prior
17    written permission.
18 
19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22    DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23    DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 //-----------------------------------------------------------------------------
32 //
33 // DESCRIPTION:
34 // monitoring window for running BSP processes (and possibly various other stuff)
35 
36 #include "watchbsp.h"
37 
38 #include <algorithm>
39 #include <gtk/gtkmain.h>
40 
41 #include "cmdlib.h"
42 #include "convert.h"
43 #include "string/string.h"
44 #include "stream/stringstream.h"
45 
46 #include "gtkutil/messagebox.h"
47 #include "xmlstuff.h"
48 #include "console.h"
49 #include "preferences.h"
50 #include "points.h"
51 #include "feedback.h"
52 #include "mainframe.h"
53 #include "sockets.h"
54 
message_flush(message_info_t * self)55 void message_flush( message_info_t* self ){
56 	Sys_Print( self->msg_level, self->m_buffer, self->m_length );
57 	self->m_length = 0;
58 }
59 
message_print(message_info_t * self,const char * characters,std::size_t length)60 void message_print( message_info_t* self, const char* characters, std::size_t length ){
61 	const char* end = characters + length;
62 	while ( characters != end )
63 	{
64 		std::size_t space = message_info_t::bufsize - 1 - self->m_length;
65 		if ( space == 0 ) {
66 			message_flush( self );
67 		}
68 		else
69 		{
70 			std::size_t size = std::min( space, std::size_t( end - characters ) );
71 			memcpy( self->m_buffer + self->m_length, characters, size );
72 			self->m_length += size;
73 			characters += size;
74 		}
75 	}
76 }
77 
78 
79 #include <glib.h>
80 #include "xmlstuff.h"
81 
82 class CWatchBSP
83 {
84 private:
85 // a flag we have set to true when using an external BSP plugin
86 // the resulting code with that is a bit dirty, cleaner solution would be to seperate the succession of commands from the listening loop
87 // (in two seperate classes probably)
88 bool m_bBSPPlugin;
89 
90 // EIdle: we are not listening
91 //   DoMonitoringLoop will change state to EBeginStep
92 // EBeginStep: the socket is up for listening, we are expecting incoming connection
93 //   incoming connection will change state to EWatching
94 // EWatching: we have a connection, monitor it
95 //   connection closed will see if we start a new step (EBeginStep) or launch Quake3 and end (EIdle)
96 enum EWatchBSPState { EIdle, EBeginStep, EWatching } m_eState;
97 socket_t *m_pListenSocket;
98 socket_t *m_pInSocket;
99 netmessage_t msg;
100 GPtrArray *m_pCmd;
101 // used to timeout EBeginStep
102 GTimer    *m_pTimer;
103 std::size_t m_iCurrentStep;
104 // name of the map so we can run the engine
105 char    *m_sBSPName;
106 // buffer we use in push mode to receive data directly from the network
107 xmlParserInputBufferPtr m_xmlInputBuffer;
108 xmlParserInputPtr m_xmlInput;
109 xmlParserCtxtPtr m_xmlParserCtxt;
110 // call this to switch the set listening mode
111 bool SetupListening();
112 // start a new EBeginStep
113 void DoEBeginStep();
114 // the xml and sax parser state
115 char m_xmlBuf[MAX_NETMESSAGE];
116 bool m_bNeedCtxtInit;
117 message_info_t m_message_info;
118 
119 public:
CWatchBSP()120 CWatchBSP(){
121 	m_pCmd = 0;
122 	m_bBSPPlugin = false;
123 	m_pListenSocket = NULL;
124 	m_pInSocket = NULL;
125 	m_eState = EIdle;
126 	m_pTimer = g_timer_new();
127 	m_sBSPName = NULL;
128 	m_xmlInputBuffer = NULL;
129 	m_bNeedCtxtInit = true;
130 }
~CWatchBSP()131 virtual ~CWatchBSP(){
132 	EndMonitoringLoop();
133 	Net_Shutdown();
134 
135 	g_timer_destroy( m_pTimer );
136 }
137 
HasBSPPlugin() const138 bool HasBSPPlugin() const
139 { return m_bBSPPlugin; }
140 
141 // called regularly to keep listening
142 void RoutineProcessing();
143 // start a monitoring loop with the following steps
144 void DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName );
EndMonitoringLoop()145 void EndMonitoringLoop(){
146 	Reset();
147 	if ( m_sBSPName ) {
148 		string_release( m_sBSPName, string_length( m_sBSPName ) );
149 		m_sBSPName = 0;
150 	}
151 	if ( m_pCmd ) {
152 		g_ptr_array_free( m_pCmd, TRUE );
153 		m_pCmd = 0;
154 	}
155 }
156 // close everything - may be called from the outside to abort the process
157 void Reset();
158 // start a listening loop for an external process, possibly a BSP plugin
159 void ExternalListen();
160 };
161 
162 CWatchBSP* g_pWatchBSP;
163 
164 // watch the BSP process through network connections
165 // true: trigger the BSP steps one by one and monitor them through the network
166 // false: create a BAT / .sh file and execute it. don't bother monitoring it.
167 bool g_WatchBSP_Enabled = true;
168 // do we stop the compilation process if we come accross a leak?
169 bool g_WatchBSP_LeakStop = true;
170 bool g_WatchBSP_RunQuake = false;
171 // store prefs setting for automatic sleep mode activation
172 bool g_WatchBSP_DoSleep = true;
173 // timeout when beginning a step (in seconds)
174 // if we don't get a connection quick enough we assume something failed and go back to idling
175 int g_WatchBSP_Timeout = 10;
176 
177 
Build_constructPreferences(PreferencesPage & page)178 void Build_constructPreferences( PreferencesPage& page ){
179 	GtkWidget* monitorbsp = page.appendCheckBox( "", "Enable Build Process Monitoring", g_WatchBSP_Enabled );
180 	GtkWidget* leakstop = page.appendCheckBox( "", "Stop Compilation on Leak", g_WatchBSP_LeakStop );
181 	GtkWidget* runengine = page.appendCheckBox( "", "Run Engine After Compile", g_WatchBSP_RunQuake );
182 	GtkWidget* sleep = page.appendCheckBox ( "", "Sleep When Running the Engine", g_WatchBSP_DoSleep );
183 	Widget_connectToggleDependency( leakstop, monitorbsp );
184 	Widget_connectToggleDependency( runengine, monitorbsp );
185 	Widget_connectToggleDependency( sleep, runengine );
186 }
Build_constructPage(PreferenceGroup & group)187 void Build_constructPage( PreferenceGroup& group ){
188 	PreferencesPage page( group.createPage( "Build", "Build Preferences" ) );
189 	Build_constructPreferences( page );
190 }
Build_registerPreferencesPage()191 void Build_registerPreferencesPage(){
192 	PreferencesDialog_addSettingsPage( FreeCaller1<PreferenceGroup&, Build_constructPage>() );
193 }
194 
195 #include "preferencesystem.h"
196 #include "stringio.h"
197 
BuildMonitor_Construct()198 void BuildMonitor_Construct(){
199 	g_pWatchBSP = new CWatchBSP();
200 
201 	g_WatchBSP_Enabled = !string_equal( g_pGameDescription->getKeyValue( "no_bsp_monitor" ), "1" );
202 
203 	GlobalPreferenceSystem().registerPreference( "WatchBSP", BoolImportStringCaller( g_WatchBSP_Enabled ), BoolExportStringCaller( g_WatchBSP_Enabled ) );
204 	GlobalPreferenceSystem().registerPreference( "RunQuake2Run", BoolImportStringCaller( g_WatchBSP_RunQuake ), BoolExportStringCaller( g_WatchBSP_RunQuake ) );
205 	GlobalPreferenceSystem().registerPreference( "LeakStop", BoolImportStringCaller( g_WatchBSP_LeakStop ), BoolExportStringCaller( g_WatchBSP_LeakStop ) );
206 	GlobalPreferenceSystem().registerPreference( "SleepMode", BoolImportStringCaller( g_WatchBSP_DoSleep ), BoolExportStringCaller( g_WatchBSP_DoSleep ) );
207 
208 	Build_registerPreferencesPage();
209 }
210 
BuildMonitor_Destroy()211 void BuildMonitor_Destroy(){
212 	delete g_pWatchBSP;
213 }
214 
GetWatchBSP()215 CWatchBSP *GetWatchBSP(){
216 	return g_pWatchBSP;
217 }
218 
BuildMonitor_Run(GPtrArray * commands,const char * mapName)219 void BuildMonitor_Run( GPtrArray* commands, const char* mapName ){
220 	GetWatchBSP()->DoMonitoringLoop( commands, mapName );
221 }
222 
223 
224 // Static functions for the SAX callbacks -------------------------------------------------------
225 
226 // utility for saxStartElement below
abortStream(message_info_t * data)227 static void abortStream( message_info_t *data ){
228 	GetWatchBSP()->EndMonitoringLoop();
229 	// tell there has been an error
230 #if 0
231 	if ( GetWatchBSP()->HasBSPPlugin() ) {
232 		g_BSPFrontendTable.m_pfnEndListen( 2 );
233 	}
234 #endif
235 	// yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out
236 	data->ignore_depth = -1;
237 	data->recurse++;
238 }
239 
240 #include "stream_version.h"
241 
saxStartElement(message_info_t * data,const xmlChar * name,const xmlChar ** attrs)242 static void saxStartElement( message_info_t *data, const xmlChar *name, const xmlChar **attrs ){
243 #if 0
244 	globalOutputStream() << "<" << name;
245 	if ( attrs != 0 ) {
246 		for ( const xmlChar** p = attrs; *p != 0; p += 2 )
247 		{
248 			globalOutputStream() << " " << p[0] << "=" << makeQuoted( p[1] );
249 		}
250 	}
251 	globalOutputStream() << ">\n";
252 #endif
253 
254 	if ( data->ignore_depth == 0 ) {
255 		if ( data->pGeometry != 0 ) {
256 			// we have a handler
257 			data->pGeometry->saxStartElement( data, name, attrs );
258 		}
259 		else
260 		{
261 			if ( strcmp( reinterpret_cast<const char*>( name ), "q3map_feedback" ) == 0 ) {
262 				// check the correct version
263 				// old q3map don't send a version attribute
264 				// the ones we support .. send Q3MAP_STREAM_VERSION
265 				if ( !attrs[0] || !attrs[1] || ( strcmp( reinterpret_cast<const char*>( attrs[0] ), "version" ) != 0 ) ) {
266 					message_flush( data );
267 					globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n"
268 										   "Please turn off monitored compiling if you still wish to use this q3map executable\n";
269 					abortStream( data );
270 					return;
271 				}
272 				else if ( strcmp( reinterpret_cast<const char*>( attrs[1] ), Q3MAP_STREAM_VERSION ) != 0 ) {
273 					message_flush( data );
274 					globalErrorStream() <<
275 					"This version of Radiant reads version " Q3MAP_STREAM_VERSION " debug streams, I got an incoming connection with version " << reinterpret_cast<const char*>( attrs[1] ) << "\n"
276 																																															   "Please make sure your versions of Radiant and q3map are matching.\n";
277 					abortStream( data );
278 					return;
279 				}
280 			}
281 			// we don't treat locally
282 			else if ( strcmp( reinterpret_cast<const char*>( name ), "message" ) == 0 ) {
283 				int msg_level = atoi( reinterpret_cast<const char*>( attrs[1] ) );
284 				if ( msg_level != data->msg_level ) {
285 					message_flush( data );
286 					data->msg_level = msg_level;
287 				}
288 			}
289 			else if ( strcmp( reinterpret_cast<const char*>( name ), "polyline" ) == 0 ) {
290 				// polyline has a particular status .. right now we only use it for leakfile ..
291 				data->geometry_depth = data->recurse;
292 				data->pGeometry = &g_pointfile;
293 				data->pGeometry->saxStartElement( data, name, attrs );
294 			}
295 			else if ( strcmp( reinterpret_cast<const char*>( name ), "select" ) == 0 ) {
296 				CSelectMsg *pSelect = new CSelectMsg();
297 				data->geometry_depth = data->recurse;
298 				data->pGeometry = pSelect;
299 				data->pGeometry->saxStartElement( data, name, attrs );
300 			}
301 			else if ( strcmp( reinterpret_cast<const char*>( name ), "pointmsg" ) == 0 ) {
302 				CPointMsg *pPoint = new CPointMsg();
303 				data->geometry_depth = data->recurse;
304 				data->pGeometry = pPoint;
305 				data->pGeometry->saxStartElement( data, name, attrs );
306 			}
307 			else if ( strcmp( reinterpret_cast<const char*>( name ), "windingmsg" ) == 0 ) {
308 				CWindingMsg *pWinding = new CWindingMsg();
309 				data->geometry_depth = data->recurse;
310 				data->pGeometry = pWinding;
311 				data->pGeometry->saxStartElement( data, name, attrs );
312 			}
313 			else
314 			{
315 				globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast<const char*>( name ) << ")\n";
316 				// we don't recognize this node, jump over it
317 				// (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level)
318 				data->ignore_depth = data->recurse;
319 			}
320 		}
321 	}
322 	data->recurse++;
323 }
324 
saxEndElement(message_info_t * data,const xmlChar * name)325 static void saxEndElement( message_info_t *data, const xmlChar *name ){
326 #if 0
327 	globalOutputStream() << "<" << name << "/>\n";
328 #endif
329 
330 	data->recurse--;
331 	// we are out of an ignored chunk
332 	if ( data->recurse == data->ignore_depth ) {
333 		data->ignore_depth = 0;
334 		return;
335 	}
336 	if ( data->pGeometry != 0 ) {
337 		data->pGeometry->saxEndElement( data, name );
338 		// we add the object to the debug window
339 		if ( data->geometry_depth == data->recurse ) {
340 			g_DbgDlg.Push( data->pGeometry );
341 			data->pGeometry = 0;
342 		}
343 	}
344 	if ( data->recurse == data->stop_depth ) {
345 		message_flush( data );
346 #ifdef _DEBUG
347 		globalOutputStream() << "Received error msg .. shutting down..\n";
348 #endif
349 		GetWatchBSP()->EndMonitoringLoop();
350 		// tell there has been an error
351 #if 0
352 		if ( GetWatchBSP()->HasBSPPlugin() ) {
353 			g_BSPFrontendTable.m_pfnEndListen( 2 );
354 		}
355 #endif
356 		return;
357 	}
358 }
359 
360 class MessageOutputStream : public TextOutputStream
361 {
362 message_info_t* m_data;
363 public:
MessageOutputStream(message_info_t * data)364 MessageOutputStream( message_info_t* data ) : m_data( data ){
365 }
write(const char * buffer,std::size_t length)366 std::size_t write( const char* buffer, std::size_t length ){
367 	if ( m_data->pGeometry != 0 ) {
368 		m_data->pGeometry->saxCharacters( m_data, reinterpret_cast<const xmlChar*>( buffer ), int(length) );
369 	}
370 	else
371 	{
372 		if ( m_data->ignore_depth == 0 ) {
373 			// output the message using the level
374 			message_print( m_data, buffer, length );
375 			// if this message has error level flag, we mark the depth to stop the compilation when we get out
376 			// we don't set the msg level if we don't stop on leak
377 			if ( m_data->msg_level == 3 ) {
378 				m_data->stop_depth = m_data->recurse - 1;
379 			}
380 		}
381 	}
382 
383 	return length;
384 }
385 };
386 
387 template<typename T>
operator <<(MessageOutputStream & ostream,const T & t)388 inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
389 	return ostream_write( ostream, t );
390 }
391 
saxCharacters(message_info_t * data,const xmlChar * ch,int len)392 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
393 	MessageOutputStream ostream( data );
394 	ostream << StringRange( reinterpret_cast<const char*>( ch ), reinterpret_cast<const char*>( ch + len ) );
395 }
396 
saxComment(void * ctx,const xmlChar * msg)397 static void saxComment( void *ctx, const xmlChar *msg ){
398 	globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
399 }
400 
saxWarning(void * ctx,const char * msg,...)401 static void saxWarning( void *ctx, const char *msg, ... ){
402 	char saxMsgBuffer[4096];
403 	va_list args;
404 
405 	va_start( args, msg );
406 	vsprintf( saxMsgBuffer, msg, args );
407 	va_end( args );
408 	globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
409 }
410 
saxError(void * ctx,const char * msg,...)411 static void saxError( void *ctx, const char *msg, ... ){
412 	char saxMsgBuffer[4096];
413 	va_list args;
414 
415 	va_start( args, msg );
416 	vsprintf( saxMsgBuffer, msg, args );
417 	va_end( args );
418 	globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
419 }
420 
saxFatal(void * ctx,const char * msg,...)421 static void saxFatal( void *ctx, const char *msg, ... ){
422 	char buffer[4096];
423 
424 	va_list args;
425 
426 	va_start( args, msg );
427 	vsprintf( buffer, msg, args );
428 	va_end( args );
429 	globalErrorStream() << "XML fatal error: " << buffer << "\n";
430 }
431 
432 static xmlSAXHandler saxParser = {
433 	0, /* internalSubset */
434 	0, /* isStandalone */
435 	0, /* hasInternalSubset */
436 	0, /* hasExternalSubset */
437 	0, /* resolveEntity */
438 	0, /* getEntity */
439 	0, /* entityDecl */
440 	0, /* notationDecl */
441 	0, /* attributeDecl */
442 	0, /* elementDecl */
443 	0, /* unparsedEntityDecl */
444 	0, /* setDocumentLocator */
445 	0, /* startDocument */
446 	0, /* endDocument */
447 	(startElementSAXFunc)saxStartElement, /* startElement */
448 	(endElementSAXFunc)saxEndElement, /* endElement */
449 	0, /* reference */
450 	(charactersSAXFunc)saxCharacters, /* characters */
451 	0, /* ignorableWhitespace */
452 	0, /* processingInstruction */
453 	(commentSAXFunc)saxComment, /* comment */
454 	(warningSAXFunc)saxWarning, /* warning */
455 	(errorSAXFunc)saxError, /* error */
456 	(fatalErrorSAXFunc)saxFatal, /* fatalError */
457 	0,
458 	0,
459 	0,
460 	0,
461 	0,
462 	0,
463 	0,
464 	0
465 };
466 
467 // ------------------------------------------------------------------------------------------------
468 
469 
470 guint s_routine_id;
watchbsp_routine(gpointer data)471 static gint watchbsp_routine( gpointer data ){
472 	reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
473 	return TRUE;
474 }
475 
Reset()476 void CWatchBSP::Reset(){
477 	if ( m_pInSocket ) {
478 		Net_Disconnect( m_pInSocket );
479 		m_pInSocket = NULL;
480 	}
481 	if ( m_pListenSocket ) {
482 		Net_Disconnect( m_pListenSocket );
483 		m_pListenSocket = NULL;
484 	}
485 	if ( m_xmlInputBuffer ) {
486 		xmlFreeParserInputBuffer( m_xmlInputBuffer );
487 		m_xmlInputBuffer = NULL;
488 	}
489 	m_eState = EIdle;
490 	if ( s_routine_id ) {
491 		gtk_timeout_remove( s_routine_id );
492 	}
493 }
494 
SetupListening()495 bool CWatchBSP::SetupListening(){
496 #ifdef _DEBUG
497 	if ( m_pListenSocket ) {
498 		globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
499 		return false;
500 	}
501 #endif
502 	globalOutputStream() << "Setting up\n";
503 	Net_Setup();
504 	m_pListenSocket = Net_ListenSocket( 39000 );
505 	if ( m_pListenSocket == NULL ) {
506 		return false;
507 	}
508 	globalOutputStream() << "Listening...\n";
509 	return true;
510 }
511 
DoEBeginStep()512 void CWatchBSP::DoEBeginStep(){
513 	Reset();
514 	if ( SetupListening() == false ) {
515 		const char* msg = "Failed to get a listening socket on port 39000.\nTry running with Build monitoring disabled if you can't fix this.\n";
516 		globalOutputStream() << msg;
517 		gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ), msg, "Build monitoring", eMB_OK, eMB_ICONERROR );
518 		return;
519 	}
520 	// set the timer for timeouts and step cancellation
521 	g_timer_reset( m_pTimer );
522 	g_timer_start( m_pTimer );
523 
524 	if ( !m_bBSPPlugin ) {
525 		globalOutputStream() << "=== running build command ===\n"
526 							 << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
527 
528 		if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
529 			StringOutputStream msg( 256 );
530 			msg << "Failed to execute the following command: ";
531 			msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
532 			msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
533 			globalOutputStream() << msg.c_str();
534 			gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ), msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
535 			return;
536 		}
537 		// re-initialise the debug window
538 		if ( m_iCurrentStep == 0 ) {
539 			g_DbgDlg.Init();
540 		}
541 	}
542 	m_eState = EBeginStep;
543 	s_routine_id = gtk_timeout_add( 25, watchbsp_routine, this );
544 }
545 
546 
547 #if defined( WIN32 )
548 #define ENGINE_ATTRIBUTE "engine_win32"
549 #define MP_ENGINE_ATTRIBUTE "mp_engine_win32"
550 #elif defined( __linux__ ) || (defined ( __FreeBSD__ ) || defined(__DragonFly__))
551 #define ENGINE_ATTRIBUTE "engine_linux"
552 #define MP_ENGINE_ATTRIBUTE "mp_engine_linux"
553 #elif defined( __APPLE__ )
554 #define ENGINE_ATTRIBUTE "engine_macos"
555 #define MP_ENGINE_ATTRIBUTE "mp_engine_macos"
556 #else
557 #error "unsupported platform"
558 #endif
559 
560 class RunEngineConfiguration
561 {
562 public:
563 const char* executable;
564 const char* mp_executable;
565 bool do_sp_mp;
566 
RunEngineConfiguration()567 RunEngineConfiguration() :
568 	executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
569 	mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
570 	do_sp_mp = !string_empty( mp_executable );
571 }
572 };
573 
GlobalGameDescription_string_write_mapparameter(StringOutputStream & string,const char * mapname)574 inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
575 	if ( g_pGameDescription->mGameType == "q2"
576 		 || g_pGameDescription->mGameType == "heretic2" ) {
577 		string << ". +exec radiant.cfg +map " << mapname;
578 	}
579 	else
580 	{
581 		string << "+set sv_pure 0 ";
582 		// TTimo: a check for vm_* but that's all fine
583 		//cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
584 		const char* fs_game = gamename_get();
585 		if ( !string_equal( fs_game, basegame_get() ) ) {
586 			string << "+set fs_game " << fs_game << " ";
587 		}
588 		if ( g_pGameDescription->mGameType == "wolf" ) {
589 			//|| g_pGameDescription->mGameType == "et")
590 			if ( string_equal( gamemode_get(), "mp" ) ) {
591 				// MP
592 				string << "+devmap " << mapname;
593 			}
594 			else
595 			{
596 				// SP
597 				string << "+set nextmap \"spdevmap " << mapname << "\"";
598 			}
599 		}
600 		else
601 		{
602 			string << "+devmap " << mapname;
603 		}
604 	}
605 }
606 
607 
RoutineProcessing()608 void CWatchBSP::RoutineProcessing(){
609 	switch ( m_eState )
610 	{
611 	case EBeginStep:
612 		// timeout: if we don't get an incoming connection fast enough, go back to idle
613 		if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
614 			gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  "The connection timed out, assuming the build process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", eMB_OK );
615 			EndMonitoringLoop();
616 #if 0
617 			if ( m_bBSPPlugin ) {
618 				// status == 1 : didn't get the connection
619 				g_BSPFrontendTable.m_pfnEndListen( 1 );
620 			}
621 #endif
622 			return;
623 		}
624 #ifdef _DEBUG
625 		// some debug checks
626 		if ( !m_pListenSocket ) {
627 			globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
628 			return;
629 		}
630 #endif
631 		// we are not connected yet, accept any incoming connection
632 		m_pInSocket = Net_Accept( m_pListenSocket );
633 		if ( m_pInSocket ) {
634 			globalOutputStream() << "Connected.\n";
635 			// prepare the message info struct for diving in
636 			memset( &m_message_info, 0, sizeof( message_info_t ) );
637 			// a dumb flag to make sure we init the push parser context when first getting a msg
638 			m_bNeedCtxtInit = true;
639 			m_eState = EWatching;
640 		}
641 		break;
642 	case EWatching:
643 	{
644 #ifdef _DEBUG
645 		// some debug checks
646 		if ( !m_pInSocket ) {
647 			globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
648 			return;
649 		}
650 #endif
651 
652 		int ret = Net_Wait( m_pInSocket, 0, 0 );
653 		if ( ret == -1 ) {
654 			globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
655 			globalOutputStream() << "Terminating the connection.\n";
656 			EndMonitoringLoop();
657 			return;
658 		}
659 
660 		if ( ret == 1 ) {
661 			// the socket has been identified, there's something (message or disconnection)
662 			// see if there's anything in input
663 			ret = Net_Receive( m_pInSocket, &msg );
664 			if ( ret > 0 ) {
665 				//        unsigned int size = msg.size; //++timo just a check
666 				strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
667 				if ( m_bNeedCtxtInit ) {
668 					m_xmlParserCtxt = NULL;
669 					m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
670 
671 					if ( m_xmlParserCtxt == NULL ) {
672 						globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
673 						EndMonitoringLoop();
674 					}
675 					m_bNeedCtxtInit = false;
676 				}
677 				else
678 				{
679 					xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
680 				}
681 			}
682 			else
683 			{
684 				message_flush( &m_message_info );
685 				// error or connection closed/reset
686 				// NOTE: if we get an error down the XML stream we don't reach here
687 				Net_Disconnect( m_pInSocket );
688 				m_pInSocket = NULL;
689 				globalOutputStream() << "Connection closed.\n";
690 #if 0
691 				if ( m_bBSPPlugin ) {
692 					EndMonitoringLoop();
693 					// let the BSP plugin know that the job is done
694 					g_BSPFrontendTable.m_pfnEndListen( 0 );
695 					return;
696 				}
697 #endif
698 				// move to next step or finish
699 				m_iCurrentStep++;
700 				if ( m_iCurrentStep < m_pCmd->len ) {
701 					DoEBeginStep();
702 				}
703 				else
704 				{
705 					// launch the engine .. OMG
706 					if ( g_WatchBSP_RunQuake ) {
707 #if 0
708 						// do we enter sleep mode before?
709 						if ( g_WatchBSP_DoSleep ) {
710 							globalOutputStream() << "Going into sleep mode..\n";
711 							g_pParentWnd->OnSleep();
712 						}
713 #endif
714 						globalOutputStream() << "Running engine...\n";
715 						StringOutputStream cmd( 256 );
716 						// build the command line
717 						cmd << EnginePath_get();
718 						// this is game dependant
719 
720 						RunEngineConfiguration engineConfig;
721 
722 						if ( engineConfig.do_sp_mp ) {
723 							if ( string_equal( gamemode_get(), "mp" ) ) {
724 								cmd << engineConfig.mp_executable;
725 							}
726 							else
727 							{
728 								cmd << engineConfig.executable;
729 							}
730 						}
731 						else
732 						{
733 							cmd << engineConfig.executable;
734 						}
735 
736 						StringOutputStream cmdline;
737 
738 						GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
739 
740 						globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
741 
742 						// execute now
743 						if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
744 							StringOutputStream msg;
745 							msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
746 							globalOutputStream() << msg.c_str();
747 							gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR );
748 						}
749 					}
750 					EndMonitoringLoop();
751 				}
752 			}
753 		}
754 	}
755 	break;
756 	default:
757 		break;
758 	}
759 }
760 
str_ptr_array_clone(GPtrArray * array)761 GPtrArray* str_ptr_array_clone( GPtrArray* array ){
762 	GPtrArray* cloned = g_ptr_array_sized_new( array->len );
763 	for ( guint i = 0; i < array->len; ++i )
764 	{
765 		g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
766 	}
767 	return cloned;
768 }
769 
DoMonitoringLoop(GPtrArray * pCmd,const char * sBSPName)770 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
771 	m_sBSPName = string_clone( sBSPName );
772 	if ( m_eState != EIdle ) {
773 		globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
774 		// prompt the user, should we cancel the current process and go ahead?
775 		if ( gtk_MessageBox( GTK_WIDGET( MainFrame_getWindow() ),  "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
776 							 "Build process monitoring", eMB_YESNO ) == eIDYES ) {
777 			// disconnect and set EIdle state
778 			Reset();
779 		}
780 	}
781 	m_pCmd = str_ptr_array_clone( pCmd );
782 	m_iCurrentStep = 0;
783 	DoEBeginStep();
784 }
785 
ExternalListen()786 void CWatchBSP::ExternalListen(){
787 	m_bBSPPlugin = true;
788 	DoEBeginStep();
789 }
790 
791 // the part of the watchbsp interface we export to plugins
792 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
793 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
QERApp_Listen()794 void QERApp_Listen(){
795 	// open the listening socket
796 	GetWatchBSP()->ExternalListen();
797 }
798