campo-sirio/server/lurch.cpp

714 lines
20 KiB
C++
Raw Normal View History

#include "baseserv.h"
#include <wx/config.h>
#include <wx/filename.h>
#include <wx/process.h>
#include <wx/timer.h>
///////////////////////////////////////////////////////////
// TLurchServer declaration
///////////////////////////////////////////////////////////
//classe TProcessHashMap derivata da wxHashMap (<28> l'equivalente di TAssoc_array)
WX_DECLARE_HASH_MAP( wxString, wxProcess*, wxStringHash, wxStringEqual, TProcessHashMap );
class TLurchServer : public TBaseServerApp
{
TProcessHashMap m_hmProcMap;
wxSemaphore m_Semaphore;
wxTimer m_PingTimer;
wxArrayString m_aServers, m_aAutoRun;
DECLARE_EVENT_TABLE();
protected:
virtual const wxChar* GetAppName() const;
virtual bool Initialization();
virtual bool Deinitialization();
void OnTimer(wxTimerEvent& evt);
void OnEndProcess(wxProcessEvent& evt);
TXmlItem& AddMiniForm(TXmlItem& tr, const wxChar* action, const wxChar* app, const wxChar* prompt) const;
void EnumerateVariables(const wxString& strApp, wxArrayString& arr) const;
const wxArrayString& GetServersList() const ;
const wxArrayString& GetAutoRunList() const;
const TProcessHashMap& GetRunningServers() const { return m_hmProcMap; }
bool PingProcess(const wxString& strApp);
void StopProcess(const wxString& strApp);
wxString StartProcess(const wxString& strApp);
TXmlItem& AddTableRow(TXmlItem& table, const wxChar* prompt, const wxString& value) const;
public:
void GenerateFile(wxString& strFilename);
bool IsMagicName(wxString& strFilename) const;
bool IsCgiName(wxString strFilename) const;
void ProcessHttpGet(wxString cmd, wxSocketBase& outs);
void ProcessHttpPost(wxString cmd, wxSocketBase& outs);
bool ForgetProcess(const wxString& strApp);
//metodi riguardanti l'interfaccia html
void ProcessFormStart(const THashTable& args, wxSocketBase& sock);
void ProcessFormStop(const THashTable& args, wxSocketBase& sock);
void ProcessFormRestart(const THashTable& args, wxSocketBase& sock);
void ProcessFormConfig(const THashTable& args, wxSocketBase& sock);
void ProcessFormUpdate(THashTable& args, wxSocketBase& sock);
void CallCgi(wxString& strFileName, wxSocketBase& sock);
TLurchServer();
};
//////////////////////////////////////////////////////////
// TLurchServer implementation
///////////////////////////////////////////////////////////
BEGIN_EVENT_TABLE(TLurchServer, TBaseServerApp)
EVT_TIMER(wxID_ANY, TLurchServer::OnTimer)
EVT_END_PROCESS(wxID_ANY, TLurchServer::OnEndProcess)
END_EVENT_TABLE()
bool TLurchServer::PingProcess(const wxString& strApp)
{
bool bPinged = false;
const unsigned short nPort = GetConfigInt("Port", 0, strApp);
if (nPort > 0)
{
int nTimeOut = m_PingTimer.GetInterval()/3; // msec
if (nTimeOut < 1000) nTimeOut = 1000; else
if (nTimeOut > 5000) nTimeOut = 5000;
wxIPV4address ipAddress;
//ipAddress.AnyAddress(); // NON funziona con ESET
ipAddress.LocalHost(); // Pare funzionare sempre
ipAddress.Service(nPort);
wxSocketClient sSock(wxSOCKET_NOWAIT);
sSock.Connect(ipAddress, false);
if (sSock.WaitOnConnect(0, nTimeOut))
{
sSock.Discard(); // Tralascia eventuali dati in arrivo non richiesti
const wxString strPing = wxT("PING\n");
sSock.Write(strPing, strPing.Len());
if (sSock.WaitForWrite(0, nTimeOut))
{
::wxMilliSleep(nTimeOut); // Triste necessita' :-( Aspetto qui ...
char buffer[8]; memset(buffer, 0, sizeof(buffer));
sSock.Read(buffer, 4);
if (sSock.WaitForRead(0, 1)) // ... ma poi non aspetto qua :-)
bPinged = wxStrncmp(buffer, wxT("PONG"), 4) == 0;
sSock.Discard(); // Tralascia ulteriori dati in arrivo
}
else
{
wxString strMsg;
strMsg << _("Ping Host - ") << ipAddress.Hostname() << ":" << nPort << _(" - Write Error ") << sSock.LastError() ;
WriteLog(strMsg);
}
}
else
{
wxString strMsg;
strMsg << _("Ping Host - ") << ipAddress.Hostname() << ":" << nPort << _(" - Connection Error ") << sSock.LastError() ;
WriteLog(strMsg);
}
/*
wxDatagramSocket dSock(ipAddress, wxSOCKET_NOWAIT);
ipAddress.Service(1884);
dSock.SendTo(ipAddress, "PING\n", 5);
*/
}
return bPinged;
}
void TLurchServer::OnTimer(wxTimerEvent& evt)
{
const wxSemaError se = IsRunning() ? m_Semaphore.TryWait() : wxSEMA_MISC_ERROR;
if (se == wxSEMA_NO_ERROR)
{
const wxArrayString& aServers = GetAutoRunList();
const size_t nServers = aServers.GetCount();
if (nServers > 0) // Is anybody out there?
{
const int nRandomIndex = ::rand() % nServers;
const wxString& strApp = aServers[nRandomIndex]; // Usually "Authorization"
if (!PingProcess(strApp))
{
if (!strApp.IsEmpty()) // Dummy test
{
const wxSingleInstanceChecker sic(strApp);
if (sic.IsAnotherRunning())
{
wxProcess* pProcess = m_hmProcMap[strApp];
wxKillError ke = pProcess->Kill(pProcess->GetPid());
if (ke == wxKILL_OK)
WriteLog(strApp + _(" killed!"));
else
{
wxString strMsg;
strMsg << _("Error ") << ke << _(" killing ") << strApp;
WriteLog(strMsg);
}
wxSleep(2);
}
}
StartProcess(strApp);
}
}
m_Semaphore.Post();
}
else
evt.Skip();
}
void TLurchServer::OnEndProcess(wxProcessEvent& evt)
{
TProcessHashMap::const_iterator it;
for (it = m_hmProcMap.begin(); it != m_hmProcMap.end(); ++it)
{
const wxProcess* p = it->second;
if (p->GetPid() == evt.GetPid())
{
ForgetProcess(it->first);
break;
}
}
}
const wxChar* TLurchServer::GetAppName() const
{ return wxT("Lurch"); }
const wxArrayString& TLurchServer::GetServersList() const
{
if (m_aServers.IsEmpty())
{
wxFileInputStream ini(GetConfigName());
const size_t size = ini.GetSize();
wxChar* buff = new wxChar[size];
ini.Read(buff, size);
const char* aperta = strchr(buff, '[');
while (aperta != NULL)
{
char* chiusa = (char*)strchr(aperta+1, ']');
if (chiusa != NULL)
{
*chiusa = '\0';
wxString str = aperta+1;
((wxArrayString&)m_aServers).Add(str);
aperta = strchr(chiusa+1, '[');
}
else
break;
}
}
return m_aServers;
}
const wxArrayString& TLurchServer::GetAutoRunList() const
{
if (m_aAutoRun.IsEmpty())
{
const wxArrayString& as = GetServersList();
for (size_t i = 0; i < as.GetCount(); i++)
{
const bool bAutorun = GetConfigBool("Autorun", false, as[i]);
if (bAutorun)
((wxArrayString&)m_aAutoRun).Add(as[i]);
}
}
return m_aAutoRun;
}
TXmlItem& TLurchServer::AddMiniForm(TXmlItem& tr, const wxChar* action, const wxChar* app, const wxChar* prompt) const
{
TXmlItem& td = tr.AddChild("td").SetAttr("width", "10%");
td.SetAttr("align", "center");
td.SetAttr("valign", "center"); // Lo ignora ma insisto
TXmlItem& form = td.AddChild("form");
form.SetAttr("action", action);
TXmlItem& name = form.AddChild("input");
name.SetAttr("type", "hidden"); name.SetAttr("name", "App");
name.SetAttr("value", app);
TXmlItem& submit = form.AddChild("input");
submit.SetAttr("type", "submit");
submit.SetAttr("value", prompt);
return submit;
}
TXmlItem& TLurchServer::AddTableRow(TXmlItem& table, const wxChar* prompt, const wxString& value) const
{
TXmlItem& tr = table.AddChild("tr");
tr.AddChild("td").AddEnclosedText(prompt);
tr.AddChild("td").AddEnclosedText(value);
return tr;
}
void TLurchServer::GenerateFile(wxString& strFilename)
{
TXmlItem html;
TXmlItem& body = CreatePageBody(html);
if (strFilename == "index")
{
TXmlItem& table = body.AddChild("table");
table.SetAttr("border", "1"); table.SetAttr("width", "100%");
strFilename = GetTempFilename();
const wxString strLurchName = GetAppName();
const wxArrayString& arr = GetServersList();
for (size_t i = 0; i < arr.GetCount(); i++)
{
const wxString& strApp = arr[i];
const bool bLurch = strApp == strLurchName;
const wxString strHost = GetConfigString("Host", wxGetHostName(), strApp);
const int nPort = GetConfigInt("Port", 3883, strApp);
const wxString strIcon = GetConfigString("Icon", "euro.gif", strApp);
const wxSingleInstanceChecker sic(strApp);
const bool bRunning = sic.IsAnotherRunning();
TXmlItem& tr = table.AddChild("tr");
TXmlItem& td0 = tr.AddChild("td");
td0.SetAttr("width", "10%"); td0.SetAttr("align", "center");
TXmlItem& a = td0.AddChild("a");
if (!bLurch && bRunning)
{
a.SetAttr("href", wxString::Format("http://%s:%d/index.htm", strHost.c_str(), nPort));
a.SetAttr("target", "_blank");
}
TXmlItem& img = a.AddChild("img");
img.SetAttr("src", strIcon); img.SetAttr("border", 0L); img.SetAttr("alt", arr[i]);
const bool bAutoRun = bLurch || GetAutoRunList().Index(arr[i]) != wxNOT_FOUND;
TXmlItem& buttStart = AddMiniForm(tr, "start.cgi", arr[i], _("Start"));
if (bRunning || bAutoRun)
buttStart.SetAttr(wxT("type"), wxT("hidden"));
TXmlItem& buttStop = AddMiniForm(tr, "kill.cgi", arr[i], _("Stop"));
if (!bLurch && (!bRunning || bAutoRun))
buttStop.SetAttr(wxT("type"), wxT("hidden"));
TXmlItem& buttRestart = AddMiniForm(tr, "restart.cgi", arr[i], _("Restart"));
if (!(bRunning && !bLurch))
buttRestart.SetAttr(wxT("type"), wxT("hidden"));
TXmlItem& a3 = tr.AddChild("td").AddChild("a");
if (!bLurch && bRunning)
{
a3.SetAttr("href", wxString::Format("http://%s:%d/index.htm", strHost.c_str(), nPort));
a3.SetAttr("target", "_blank");
}
a3 << arr[i] << " Server";
}
body.AddChild("hr");
TXmlItem& panel = body.AddChild("table");
panel.SetAttr("border", "1"); panel.SetAttr("width", "100%");
panel.AddChild("caption").AddEnclosedText("Options");
AddTableRow(panel, wxT("Working directory"), wxGetCwd());
wxString strHost; strHost << wxGetHostName() << wxT(":") << GetDefaultPort();
AddTableRow(panel, wxT("Host Name"), strHost);
int y, v, t, p; GetVersionInfo(y, v, t, p);
wxString strVer; strVer.Printf(wxT("%d.%d.%d"), v, t, p);
AddTableRow(panel, wxT("Version"), strVer);
wxString strFreq; strFreq << m_PingTimer.GetInterval()/1000;
AddTableRow(panel, wxT("Ping Frequency"), strFreq);
html.Save(strFilename);
}
}
bool TLurchServer::IsMagicName(wxString& strFilename) const
{
wxString strName;
wxSplitPath(strFilename, NULL, &strName, NULL);
strName.MakeLower();
if (strName == "index")
{
strFilename = strName;
return true;
}
if (strName == "log")
{
strFilename = GetLogFileName();
}
return false;
}
bool TLurchServer::IsCgiName(wxString strFilename) const
{
const int q = strFilename.Find('?');
if (q > 0)
strFilename.Truncate(q);
wxString strExt;
wxSplitPath(strFilename, NULL, NULL, &strExt);
strExt.MakeLower();
return strExt == "cgi" || strExt == "exe";
}
wxString TLurchServer::StartProcess(const wxString& strApp)
{
bool ok = false;
wxString strMessage;
if (!strApp.IsEmpty())
{
const wxSingleInstanceChecker sic(strApp);
ok = !sic.IsAnotherRunning();
}
if (ok)
{
const wxString strRun = GetConfigString("Run", "", strApp);
if (wxFileExists(strRun))
{
wxFileName fnPath;
fnPath.SetPath(GetServerPath());
fnPath.SetFullName(strRun);
wxProcess* pProcess = wxProcess::Open(fnPath.GetFullPath());
if (pProcess == NULL)
{
strMessage.Printf(_("Can't run %s executable (%s)"), strApp.c_str(), strRun.c_str());
}
else
{
m_hmProcMap[strApp] = pProcess; // memorizza il numero del processo che ha lanciato per poterlo usare in fase di controllo
strMessage = wxEmptyString;
::wxSleep(1); // Lascia il tempo di inizializzarsi prima di compinciare a pingarlo!
}
}
else
strMessage.Printf(_("Can't find %s executable (%s)"), strApp.c_str(), strRun.c_str());
}
else
strMessage.Printf(_("%s il already running"), strApp.c_str());
if (strMessage.IsEmpty())
WriteLog(strApp + _(" started."));
else
WriteLog(strMessage);
return strMessage;
}
void TLurchServer::ProcessFormStart(const THashTable& args, wxSocketBase& sock)
{
const wxString strApp = args.Get("App");
if (!strApp.IsEmpty()) // Dummy test
{
const wxString strMessage = StartProcess(strApp);
if (strMessage.IsEmpty())
MessageBox(_("Server Started"), strApp, sock);
else
MessageBox(_("ERROR"), strMessage, sock);
}
}
bool TLurchServer::ForgetProcess(const wxString& strApp)
{
WriteLog(strApp + _(" terminated."));
return m_hmProcMap.erase(strApp) != 0;
}
void TLurchServer::StopProcess(const wxString& strApp)
{
const wxString strHost = GetConfigString("Host", "localhost", strApp);
const int nPort = GetConfigInt("Port", 0, strApp);
if (nPort > 0)
{
wxIPV4address addr;
addr.Hostname(strHost);
addr.Service(nPort);
wxSocketClient sock;
if (sock.Connect(addr))
{
// Prima provo con le buone inviando una richiesta di stop
const wxString str = "GET /stop.cgi HTTP/1.1\r\n\r\n";
sock.Write(str, str.Length());
if (addr.IsLocalHost())
{
const wxSingleInstanceChecker sic(strApp);
int i;
for (i = 5; i >= 0 && sic.IsAnotherRunning(); i--)
wxSleep(1);
// Se le buone non hanno funzionato ... passo alle cattive
if (i < 0)
{
wxProcess* p = m_hmProcMap[strApp];
if (p != NULL)
{
p->Kill(p->GetPid());
ForgetProcess(strApp);
}
}
}
}
}
}
void TLurchServer::ProcessFormStop(const THashTable& args, wxSocketBase& sock)
{
if (m_Semaphore.Wait() == wxSEMA_NO_ERROR)
{
const wxString strApp = args.Get("App");
if (strApp == GetAppName() || strApp.IsEmpty()) // Stop myself!
{
wxString strCmd = wxT("stop.cgi"); // Exits main loop;
TBaseServerApp::CanProcessCommand(strCmd, sock);
}
else
{
StopProcess(strApp);
MessageBox("Server stopped", strApp, sock);
}
m_Semaphore.Post();
}
}
void TLurchServer::ProcessFormRestart(const THashTable& args, wxSocketBase& sock)
{
const wxString strApp = args.Get("App");
if (!strApp.IsEmpty() && strApp != GetAppName())
{
if (m_Semaphore.Wait() == wxSEMA_NO_ERROR)
{
StopProcess(strApp);
::wxSleep(2);
ProcessFormStart(args, sock);
m_Semaphore.Post();
}
}
}
void TLurchServer::EnumerateVariables(const wxString& strApp, wxArrayString& arr) const
{
wxFileInputStream inf(GetConfigName());
wxString strParagraph = wxString::Format("[%s]", strApp.c_str());
wxString str;
bool bFound = false;
while (inf.Ok())
{
inf >> str;
if (str == strParagraph)
{
bFound = true;
break;
}
}
if (bFound)
{
while (inf.Ok())
{
inf >> str;
if (str.IsEmpty() || str[0u] == '[')
break;
const int nEqual = str.Find('=');
if (nEqual > 0)
{
str.Truncate(nEqual);
str.Trim(false);
str.Trim(true);
arr.Add(str);
}
}
arr.Sort();
}
}
void TLurchServer::ProcessFormConfig(const THashTable& args, wxSocketBase& sock)
{
const wxString strApp = args.Get("App");
wxArrayString arr;
EnumerateVariables(strApp, arr);
TXmlItem html;
TXmlItem& body = CreatePageBody(html);
TXmlItem& form = body.AddChild("form");
form.SetAttr("action", "update.cgi").SetAttr("method", "post");
TXmlItem& table = form.AddChild("table").SetAttr("width", "100%").SetAttr("border", "1");
table.AddChild("caption").AddChild("h2") << strApp;
TXmlItem& thead = table.AddChild("thead");
thead.AddChild("th").SetAttr("width", "15%") << "Property";
thead.AddChild("th").SetAttr("width", "85%") << "Value";
for (size_t v = 0; v < arr.GetCount(); v++)
{
TXmlItem& tr = table.AddChild("tr");
tr.AddChild("td") << arr[v];
TXmlItem& input = tr.AddChild("td").AddChild("input");
input.SetAttr("type", "text"); input.SetAttr("name", arr[v]);
input.SetAttr("size", "80"); input.SetAttr("maxlength", "256");
input.SetAttr("value", GetConfigString(arr[v], "", strApp));
}
TXmlItem& app = form.AddChild("input").SetAttr("type", "hidden");
app.SetAttr("name", "App"); app.SetAttr("value", strApp);
TXmlItem& submit = form.AddChild("br").AddChild("center").AddChild("input");
submit.SetAttr("type", "submit"); submit.SetAttr("value", "Update Parameters");
body.AddChild("br");
AddLinkButton(body.AddChild("center"), "Return to main page", "/");
const wxString strFilename = GetTempFilename();
html.Save(strFilename);
SendFile(strFilename, sock);
}
void TLurchServer::ProcessFormUpdate(THashTable& args, wxSocketBase& sock)
{
const wxString strApp = args.Get("App");
args.BeginFind();
for (wxHashTable::Node* pNode = args.Next(); pNode; pNode = args.Next())
{
const wxString strKey = pNode->GetKeyString();
if (strKey != "App")
{
const wxString strVal = args.Get(strKey);
SetConfigString(strKey, strVal, strApp);
}
}
const wxString msg = wxString::Format("%s parameters updated", strApp.c_str());
MessageBox("Success!", msg, sock);
}
void TLurchServer::CallCgi(wxString& strFileName, wxSocketBase& sock)
{
wxString strName, strExt, strArgs;
const int q = strFileName.Find('?');
if (q > 0)
{
strArgs = strFileName.Mid(q+1);
strFileName.Truncate(q);
}
wxSplitPath(strFileName, NULL, &strName, &strExt);
THashTable hashArgs(13);
ParseArguments(strArgs, hashArgs);
if (strExt == "cgi")
{
strName.MakeLower();
if (strName == "start")
ProcessFormStart(hashArgs, sock); else
if (strName == "kill")
ProcessFormStop(hashArgs, sock); else
if (strName == "restart")
ProcessFormRestart(hashArgs, sock); else
if (strName == "config")
ProcessFormConfig(hashArgs, sock); else
if (strName == "update")
ProcessFormUpdate(hashArgs, sock);
}
}
void TLurchServer::ProcessHttpGet(wxString cmd, wxSocketBase& outs)
{
const int stop = cmd.Find(" HTTP");
wxString str = cmd.Mid(4, stop-4).Trim();
if (str == "/")
str += "index.htm";
wxString strFilename = GetDocumentRoot() + str;
if (IsCgiName(strFilename))
CallCgi(strFilename, outs);
else
{
if (IsMagicName(strFilename))
GenerateFile(strFilename);
SendFile(strFilename, outs);
}
}
void TLurchServer::ProcessHttpPost(wxString cmd, wxSocketBase& outs)
{
const int stop = cmd.Find(" HTTP");
wxString strFileName = cmd.Mid(5, stop-5).Trim();
wxString strName, args;
wxSplitPath(strFileName, NULL, &strName, NULL);
const int pos = cmd.Find("\r\n\r\n");
if (pos > 0)
args = cmd.Mid(pos+4);
THashTable hashArgs(17);
ParseArguments(args, hashArgs);
if (strName == "update")
ProcessFormUpdate(hashArgs, outs);
}
bool TLurchServer::Initialization()
{
const wxArrayString& arr = GetAutoRunList();
const size_t nAuto = arr.GetCount();
if (nAuto > 0)
{
for (size_t i = 0; i < nAuto; i++)
StartProcess(arr[i]);
const int nFreq = GetConfigInt("PingFreq");
if (nFreq > 0)
{
m_PingTimer.Start(nFreq * 1000); // sec to msec
m_Semaphore.Post(); // GREEN!
}
}
return true;
}
bool TLurchServer::Deinitialization()
{
m_PingTimer.Stop(); // Stop ping process!
// kill all autorun processes even if I didn't start them
const wxArrayString& ar = GetAutoRunList();
for (size_t i = 0; i < ar.GetCount(); i++)
StopProcess(ar[i]);
// kill all still running (die hard) processes
const TProcessHashMap& hmServers = GetRunningServers();
if (!hmServers.empty())
{
// Fill a safe array of names before killing the processes stored in the hash map
wxArrayString aRunner; // Array of the names of the runners
TProcessHashMap::const_iterator it;
for (it = hmServers.begin(); it != hmServers.end(); ++it)
aRunner.Add(it->first);
for (size_t i = 0; i < aRunner.GetCount(); i++)
StopProcess(aRunner[i]);
}
return true;
}
// Istanziare l'applicazione principale
IMPLEMENT_APP(TLurchServer)
TLurchServer::TLurchServer()
: m_Semaphore(0 /* RED! */, 1 /* mutex like behaviour */)
{ }