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