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