4824d23e645021fa064d6950c227bfc65b42f0e1
[xonotic/netradiant.git] / radiant / watchbsp.cpp
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
40 #include "cmdlib.h"
41 #include "convert.h"
42 #include "string/string.h"
43 #include "stream/stringstream.h"
44
45 #include "gtkutil/messagebox.h"
46 #include "xmlstuff.h"
47 #include "console.h"
48 #include "preferences.h"
49 #include "points.h"
50 #include "feedback.h"
51 #include "mainframe.h"
52 #include "sockets.h"
53
54 void message_flush( message_info_t* self ){
55         Sys_Print( self->msg_level, self->m_buffer, self->m_length );
56         self->m_length = 0;
57 }
58
59 void message_print( message_info_t* self, const char* characters, std::size_t length ){
60         const char* end = characters + length;
61         while ( characters != end )
62         {
63                 std::size_t space = message_info_t::bufsize - 1 - self->m_length;
64                 if ( space == 0 ) {
65                         message_flush( self );
66                 }
67                 else
68                 {
69                         std::size_t size = std::min( space, std::size_t( end - characters ) );
70                         memcpy( self->m_buffer + self->m_length, characters, size );
71                         self->m_length += size;
72                         characters += size;
73                 }
74         }
75 }
76
77
78 #include <glib.h>
79 #include <uilib/uilib.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:
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         }
131         virtual ~CWatchBSP(){
132                 EndMonitoringLoop();
133                 Net_Shutdown();
134
135                 g_timer_destroy( m_pTimer );
136         }
137
138         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 );
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
178 void Build_constructPreferences( PreferencesPage& page ){
179         ui::CheckButton monitorbsp = page.appendCheckBox( "", "Enable Build Process Monitoring", g_WatchBSP_Enabled );
180         ui::CheckButton leakstop = page.appendCheckBox( "", "Stop Compilation on Leak", g_WatchBSP_LeakStop );
181         ui::CheckButton runengine = page.appendCheckBox( "", "Run Engine After Compile", g_WatchBSP_RunQuake );
182         ui::CheckButton 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 }
187 void Build_constructPage( PreferenceGroup& group ){
188         PreferencesPage page( group.createPage( "Build", "Build Preferences" ) );
189         Build_constructPreferences( page );
190 }
191 void Build_registerPreferencesPage(){
192         PreferencesDialog_addSettingsPage( FreeCaller1<PreferenceGroup&, Build_constructPage>() );
193 }
194
195 #include "preferencesystem.h"
196 #include "stringio.h"
197
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
211 void BuildMonitor_Destroy(){
212         delete g_pWatchBSP;
213 }
214
215 CWatchBSP *GetWatchBSP(){
216         return g_pWatchBSP;
217 }
218
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
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
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
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:
364         MessageOutputStream( message_info_t* data ) : m_data( data ){
365         }
366
367         std::size_t write( const char* buffer, std::size_t length ){
368                 if ( m_data->pGeometry != 0 ) {
369                         m_data->pGeometry->saxCharacters( m_data, reinterpret_cast<const xmlChar*>( buffer ), int(length) );
370                 }
371                 else
372                 {
373                         if ( m_data->ignore_depth == 0 ) {
374                                 // output the message using the level
375                                 message_print( m_data, buffer, length );
376                                 // if this message has error level flag, we mark the depth to stop the compilation when we get out
377                                 // we don't set the msg level if we don't stop on leak
378                                 if ( m_data->msg_level == 3 ) {
379                                         m_data->stop_depth = m_data->recurse - 1;
380                                 }
381                         }
382                 }
383
384                 return length;
385         }
386 };
387
388 template<typename T>
389 inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
390         return ostream_write( ostream, t );
391 }
392
393 static void saxCharacters( message_info_t *data, const xmlChar *ch, int len ){
394         MessageOutputStream ostream( data );
395         ostream << StringRange( reinterpret_cast<const char*>( ch ), reinterpret_cast<const char*>( ch + len ) );
396 }
397
398 static void saxComment( void *ctx, const xmlChar *msg ){
399         globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
400 }
401
402 static void saxWarning( void *ctx, const char *msg, ... ){
403         char saxMsgBuffer[4096];
404         va_list args;
405
406         va_start( args, msg );
407         vsprintf( saxMsgBuffer, msg, args );
408         va_end( args );
409         globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n";
410 }
411
412 static void saxError( void *ctx, const char *msg, ... ){
413         char saxMsgBuffer[4096];
414         va_list args;
415
416         va_start( args, msg );
417         vsprintf( saxMsgBuffer, msg, args );
418         va_end( args );
419         globalErrorStream() << "XML error: " << saxMsgBuffer << "\n";
420 }
421
422 static void saxFatal( void *ctx, const char *msg, ... ){
423         char buffer[4096];
424
425         va_list args;
426
427         va_start( args, msg );
428         vsprintf( buffer, msg, args );
429         va_end( args );
430         globalErrorStream() << "XML fatal error: " << buffer << "\n";
431 }
432
433 static xmlSAXHandler saxParser = {
434         0, /* internalSubset */
435         0, /* isStandalone */
436         0, /* hasInternalSubset */
437         0, /* hasExternalSubset */
438         0, /* resolveEntity */
439         0, /* getEntity */
440         0, /* entityDecl */
441         0, /* notationDecl */
442         0, /* attributeDecl */
443         0, /* elementDecl */
444         0, /* unparsedEntityDecl */
445         0, /* setDocumentLocator */
446         0, /* startDocument */
447         0, /* endDocument */
448         (startElementSAXFunc)saxStartElement, /* startElement */
449         (endElementSAXFunc)saxEndElement, /* endElement */
450         0, /* reference */
451         (charactersSAXFunc)saxCharacters, /* characters */
452         0, /* ignorableWhitespace */
453         0, /* processingInstruction */
454         (commentSAXFunc)saxComment, /* comment */
455         (warningSAXFunc)saxWarning, /* warning */
456         (errorSAXFunc)saxError, /* error */
457         (fatalErrorSAXFunc)saxFatal, /* fatalError */
458         0,
459         0,
460         0,
461         0,
462         0,
463         0,
464         0,
465         0
466 };
467
468 // ------------------------------------------------------------------------------------------------
469
470
471 guint s_routine_id;
472 static gint watchbsp_routine( gpointer data ){
473         reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
474         return TRUE;
475 }
476
477 void CWatchBSP::Reset(){
478         if ( m_pInSocket ) {
479                 Net_Disconnect( m_pInSocket );
480                 m_pInSocket = NULL;
481         }
482         if ( m_pListenSocket ) {
483                 Net_Disconnect( m_pListenSocket );
484                 m_pListenSocket = NULL;
485         }
486         if ( m_xmlInputBuffer ) {
487                 xmlFreeParserInputBuffer( m_xmlInputBuffer );
488                 m_xmlInputBuffer = NULL;
489         }
490         m_eState = EIdle;
491         if ( s_routine_id ) {
492                 g_source_remove( s_routine_id );
493         }
494 }
495
496 bool CWatchBSP::SetupListening(){
497 #ifdef _DEBUG
498         if ( m_pListenSocket ) {
499                 globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n";
500                 return false;
501         }
502 #endif
503         globalOutputStream() << "Setting up\n";
504         Net_Setup();
505         m_pListenSocket = Net_ListenSocket( 39000 );
506         if ( m_pListenSocket == NULL ) {
507                 return false;
508         }
509         globalOutputStream() << "Listening...\n";
510         return true;
511 }
512
513 void CWatchBSP::DoEBeginStep(){
514         Reset();
515         if ( SetupListening() == false ) {
516                 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";
517                 globalOutputStream() << msg;
518                 MainFrame_getWindow().alert( msg, "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
519                 return;
520         }
521         // set the timer for timeouts and step cancellation
522         g_timer_reset( m_pTimer );
523         g_timer_start( m_pTimer );
524
525         if ( !m_bBSPPlugin ) {
526                 globalOutputStream() << "=== running build command ===\n"
527                                                          << static_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) ) << "\n";
528
529                 if ( !Q_Exec( NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true, false ) ) {
530                         StringOutputStream msg( 256 );
531                         msg << "Failed to execute the following command: ";
532                         msg << reinterpret_cast<const char*>( g_ptr_array_index( m_pCmd, m_iCurrentStep ) );
533                         msg << "\nCheck that the file exists and that you don't run out of system resources.\n";
534                         globalOutputStream() << msg.c_str();
535                         MainFrame_getWindow().alert( msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
536                         return;
537                 }
538                 // re-initialise the debug window
539                 if ( m_iCurrentStep == 0 ) {
540                         g_DbgDlg.Init();
541                 }
542         }
543         m_eState = EBeginStep;
544         s_routine_id = g_timeout_add( 25, watchbsp_routine, this );
545 }
546
547
548 #if defined( WIN32 )
549 #define ENGINE_ATTRIBUTE "engine_win32"
550 #define MP_ENGINE_ATTRIBUTE "mp_engine_win32"
551 #elif defined( __linux__ ) || defined ( __FreeBSD__ )
552 #define ENGINE_ATTRIBUTE "engine_linux"
553 #define MP_ENGINE_ATTRIBUTE "mp_engine_linux"
554 #elif defined( __APPLE__ )
555 #define ENGINE_ATTRIBUTE "engine_macos"
556 #define MP_ENGINE_ATTRIBUTE "mp_engine_macos"
557 #else
558 #error "unsupported platform"
559 #endif
560
561 class RunEngineConfiguration
562 {
563 public:
564         const char* executable;
565         const char* mp_executable;
566         bool do_sp_mp;
567
568         RunEngineConfiguration() :
569                 executable( g_pGameDescription->getRequiredKeyValue( ENGINE_ATTRIBUTE ) ),
570                 mp_executable( g_pGameDescription->getKeyValue( MP_ENGINE_ATTRIBUTE ) ){
571                 do_sp_mp = !string_empty( mp_executable );
572         }
573 };
574
575 inline void GlobalGameDescription_string_write_mapparameter( StringOutputStream& string, const char* mapname ){
576         if ( g_pGameDescription->mGameType == "q2"
577                  || g_pGameDescription->mGameType == "heretic2" ) {
578                 string << ". +exec radiant.cfg +map " << mapname;
579         }
580         else
581         {
582                 string << "+set sv_pure 0 ";
583                 // TTimo: a check for vm_* but that's all fine
584                 //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 ";
585                 const char* fs_game = gamename_get();
586                 if ( !string_equal( fs_game, basegame_get() ) ) {
587                         string << "+set fs_game " << fs_game << " ";
588                 }
589                 if ( g_pGameDescription->mGameType == "wolf" ) {
590                         //|| g_pGameDescription->mGameType == "et")
591                         if ( string_equal( gamemode_get(), "mp" ) ) {
592                                 // MP
593                                 string << "+devmap " << mapname;
594                         }
595                         else
596                         {
597                                 // SP
598                                 string << "+set nextmap \"spdevmap " << mapname << "\"";
599                         }
600                 }
601                 else
602                 {
603                         string << "+devmap " << mapname;
604                 }
605         }
606 }
607
608
609 void CWatchBSP::RoutineProcessing(){
610         switch ( m_eState )
611         {
612         case EBeginStep:
613                 // timeout: if we don't get an incoming connection fast enough, go back to idle
614                 if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) {
615                         MainFrame_getWindow().alert(  "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", ui::alert_type::OK );
616                         EndMonitoringLoop();
617 #if 0
618                         if ( m_bBSPPlugin ) {
619                                 // status == 1 : didn't get the connection
620                                 g_BSPFrontendTable.m_pfnEndListen( 1 );
621                         }
622 #endif
623                         return;
624                 }
625 #ifdef _DEBUG
626                 // some debug checks
627                 if ( !m_pListenSocket ) {
628                         globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n";
629                         return;
630                 }
631 #endif
632                 // we are not connected yet, accept any incoming connection
633                 m_pInSocket = Net_Accept( m_pListenSocket );
634                 if ( m_pInSocket ) {
635                         globalOutputStream() << "Connected.\n";
636                         // prepare the message info struct for diving in
637                         memset( &m_message_info, 0, sizeof( message_info_t ) );
638                         // a dumb flag to make sure we init the push parser context when first getting a msg
639                         m_bNeedCtxtInit = true;
640                         m_eState = EWatching;
641                 }
642                 break;
643         case EWatching:
644         {
645 #ifdef _DEBUG
646                 // some debug checks
647                 if ( !m_pInSocket ) {
648                         globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n";
649                         return;
650                 }
651 #endif
652
653                 int ret = Net_Wait( m_pInSocket, 0, 0 );
654                 if ( ret == -1 ) {
655                         globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n";
656                         globalOutputStream() << "Terminating the connection.\n";
657                         EndMonitoringLoop();
658                         return;
659                 }
660
661                 if ( ret == 1 ) {
662                         // the socket has been identified, there's something (message or disconnection)
663                         // see if there's anything in input
664                         ret = Net_Receive( m_pInSocket, &msg );
665                         if ( ret > 0 ) {
666                                 //        unsigned int size = msg.size; //++timo just a check
667                                 strcpy( m_xmlBuf, NMSG_ReadString( &msg ) );
668                                 if ( m_bNeedCtxtInit ) {
669                                         m_xmlParserCtxt = NULL;
670                                         m_xmlParserCtxt = xmlCreatePushParserCtxt( &saxParser, &m_message_info, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), NULL );
671
672                                         if ( m_xmlParserCtxt == NULL ) {
673                                                 globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n";
674                                                 EndMonitoringLoop();
675                                         }
676                                         m_bNeedCtxtInit = false;
677                                 }
678                                 else
679                                 {
680                                         xmlParseChunk( m_xmlParserCtxt, m_xmlBuf, static_cast<int>( strlen( m_xmlBuf ) ), 0 );
681                                 }
682                         }
683                         else
684                         {
685                                 message_flush( &m_message_info );
686                                 // error or connection closed/reset
687                                 // NOTE: if we get an error down the XML stream we don't reach here
688                                 Net_Disconnect( m_pInSocket );
689                                 m_pInSocket = NULL;
690                                 globalOutputStream() << "Connection closed.\n";
691 #if 0
692                                 if ( m_bBSPPlugin ) {
693                                         EndMonitoringLoop();
694                                         // let the BSP plugin know that the job is done
695                                         g_BSPFrontendTable.m_pfnEndListen( 0 );
696                                         return;
697                                 }
698 #endif
699                                 // move to next step or finish
700                                 m_iCurrentStep++;
701                                 if ( m_iCurrentStep < m_pCmd->len ) {
702                                         DoEBeginStep();
703                                 }
704                                 else
705                                 {
706                                         // launch the engine .. OMG
707                                         if ( g_WatchBSP_RunQuake ) {
708 #if 0
709                                                 // do we enter sleep mode before?
710                                                 if ( g_WatchBSP_DoSleep ) {
711                                                         globalOutputStream() << "Going into sleep mode..\n";
712                                                         g_pParentWnd->OnSleep();
713                                                 }
714 #endif
715                                                 globalOutputStream() << "Running engine...\n";
716                                                 StringOutputStream cmd( 256 );
717                                                 // build the command line
718                                                 cmd << EnginePath_get();
719                                                 // this is game dependant
720
721                                                 RunEngineConfiguration engineConfig;
722
723                                                 if ( engineConfig.do_sp_mp ) {
724                                                         if ( string_equal( gamemode_get(), "mp" ) ) {
725                                                                 cmd << engineConfig.mp_executable;
726                                                         }
727                                                         else
728                                                         {
729                                                                 cmd << engineConfig.executable;
730                                                         }
731                                                 }
732                                                 else
733                                                 {
734                                                         cmd << engineConfig.executable;
735                                                 }
736
737                                                 StringOutputStream cmdline;
738
739                                                 GlobalGameDescription_string_write_mapparameter( cmdline, m_sBSPName );
740
741                                                 globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n";
742
743                                                 // execute now
744                                                 if ( !Q_Exec( cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false, false ) ) {
745                                                         StringOutputStream msg;
746                                                         msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str();
747                                                         globalOutputStream() << msg.c_str();
748                                                         MainFrame_getWindow().alert( msg.c_str(), "Build monitoring", ui::alert_type::OK, ui::alert_icon::Error );
749                                                 }
750                                         }
751                                         EndMonitoringLoop();
752                                 }
753                         }
754                 }
755         }
756         break;
757         default:
758                 break;
759         }
760 }
761
762 GPtrArray* str_ptr_array_clone( GPtrArray* array ){
763         GPtrArray* cloned = g_ptr_array_sized_new( array->len );
764         for ( guint i = 0; i < array->len; ++i )
765         {
766                 g_ptr_array_add( cloned, g_strdup( (char*)g_ptr_array_index( array, i ) ) );
767         }
768         return cloned;
769 }
770
771 void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ){
772         m_sBSPName = string_clone( sBSPName );
773         if ( m_eState != EIdle ) {
774                 globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n";
775                 // prompt the user, should we cancel the current process and go ahead?
776                 if ( MainFrame_getWindow().alert( "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?",
777                                                          "Build process monitoring", ui::alert_type::YESNO ) == ui::alert_response::YES ) {
778                         // disconnect and set EIdle state
779                         Reset();
780                 }
781         }
782         m_pCmd = str_ptr_array_clone( pCmd );
783         m_iCurrentStep = 0;
784         DoEBeginStep();
785 }
786
787 void CWatchBSP::ExternalListen(){
788         m_bBSPPlugin = true;
789         DoEBeginStep();
790 }
791
792 // the part of the watchbsp interface we export to plugins
793 // NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level
794 // for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final)
795 void QERApp_Listen(){
796         // open the listening socket
797         GetWatchBSP()->ExternalListen();
798 }