fix invalid use of <glib/...> headers
[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 #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
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
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:
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         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 }
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 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>
388 inline MessageOutputStream& operator<<( MessageOutputStream& ostream, const T& t ){
389         return ostream_write( ostream, t );
390 }
391
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
397 static void saxComment( void *ctx, const xmlChar *msg ){
398         globalOutputStream() << "XML comment: " << reinterpret_cast<const char*>( msg ) << "\n";
399 }
400
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
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
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;
471 static gint watchbsp_routine( gpointer data ){
472         reinterpret_cast<CWatchBSP*>( data )->RoutineProcessing();
473         return TRUE;
474 }
475
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
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
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__ )
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
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
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
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
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
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
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)
794 void QERApp_Listen(){
795         // open the listening socket
796         GetWatchBSP()->ExternalListen();
797 }