// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdpch.h"
#include "login_progress_post_thread.h"
#include "game_share/login_registry.h"
#include "nel/misc/system_info.h"
using namespace NLMISC;
///////////////////////
// Utility functions //
///////////////////////
//===========================================================================================
static std::string uint64ToHex(uint64 size)
{
char data[256];
sprintf(data, "%"NL_I64"X", size);
return std::string(data);
}
//===========================================================================================
static std::string sizeToHumanStd(uint64 size)
{
double total = (double) size;
std::string unit;
if (total >= 1024.0*1024.0*1024.0)
{
total /= 1024.0*1024*1024;
unit = "GB";
}
else if (total >= 1024*1024.0)
{
total /= 1024.0*1024;
unit = "MB";
}
else if (total >= 1024.0)
{
total = int(total/1024.0);
unit = "KB";
}
else
{
unit = "B";
}
static char buffer[256];
if ( total > 100.0)
{
sprintf(buffer, "%0.0f", (double)total);
}
else if (total > 10.0)
{
sprintf(buffer, "%0.1f", (double)total);
}
else
{
sprintf(buffer, "%0.2f", (double)total);
}
std::string ret(buffer);
ret+=unit;
return ret;
}
//===========================================================================================
static std::string getVideoInfoDeviceName()
{
uint64 version;
std::string ret;
bool ok = CSystemInfo::getVideoInfo(ret, version);
if (ok)
{
return ret.c_str();
}
return "";
}
//===========================================================================================
static uint64 getVideoDriverVersion()
{
static std::string ret;
uint64 version=0;
ret = "";
bool ok = CSystemInfo::getVideoInfo(ret, version);
if (ok)
{
return version;
}
return 0;
}
//===========================================================================================
static std::string extractToken(const std::string& res, const std::string& token)
{
std::string ret("");
std::string tokenBegin = std::string("<") + token + ">";
std::string::size_type tokenBeginLen = tokenBegin.size();
std::string tokenEnd = std::string("") + token + ">";
std::string::size_type tokenEndLen = tokenBegin.size();
std::string::size_type begin = res.find(tokenBegin);
std::string::size_type end = begin != std::string::npos && res.size() > tokenBeginLen + tokenEndLen ? res.find(tokenEnd, begin+tokenBeginLen) : std::string::npos;
if (begin != std::string::npos && end != std::string::npos)
{
ret = res.substr(begin+tokenBeginLen, end - (begin+tokenBeginLen));
}
return ret;
}
/////////////////////////
// login progress task //
/////////////////////////
typedef std::deque TStepQueue;
class CLoginProgressTask : public NLMISC::IRunnable
{
public:
std::string StartupHost;
std::string StartupPage;
CSynchronized StepQueue;
bool StopWanted;
public:
//============================================================================================
CLoginProgressTask() : StepQueue("")
{
StopWanted = false;
}
//============================================================================================
void clearQueue()
{
CSynchronized::CAccessor access(&StepQueue);
access.value().clear();
}
//============================================================================================
virtual void run()
{
try
{
// init and send hardware info if first time
std::string productInstallId = CLoginRegistry::getProductInstallId();
if (productInstallId.empty())
{
throw NLMISC::Exception("Couldn't retrieve product install id");
}
std::string res = sendMsg(std::string("login") + "&install_id=" + productInstallId);
std::string userId = extractToken(res, "user_id");
if (userId.empty())
{
res = sendMsg(std::string("init") + "&install_id=" + productInstallId + buildSysInfoString());
userId = extractToken(res, "user_id");
}
if (userId.empty())
{
throw NLMISC::Exception("Couldn't retrieve user id");
}
// from now, wait each step from the client
for (;;)
{
CLoginStep loginStep;
bool newStep = false;
{
CSynchronized::CAccessor access(&StepQueue);
if (!access.value().empty())
{
loginStep = access.value().front();
access.value().pop_front();
newStep = true;
}
if (newStep)
{
if (loginStep.Step == (uint)LoginStep_Stop)
{
break;
}
uint currentStep = CLoginRegistry::getLoginStep();
// during install program some messages can be launch multiple times (eg "install_update&percent=17" that indicates that 17 percent of the installation is already done)
bool isInInstallation = InstallStep_StartDownload <= loginStep.Step && loginStep.Step <= InstallStep_StopInstall;
bool hasBeenInstalled = currentStep > InstallStep_StopInstall;
if ((isInInstallation&&!hasBeenInstalled) || (loginStep.Step > CLoginRegistry::getLoginStep()) )
{
std::string ret= sendMsg(loginStep.PostString + "&user_id=" + userId);
// If we reached this point, server has correctly received the step -> commit current progress in registry
// Only send msg for player that have never installed the software
CLoginRegistry::setLoginStep(loginStep.Step);
// Async Ret enable the player to Know via pulling the return of the sendMsg
if (loginStep.AsyncRet && loginStep.AsyncSent)
{
(*loginStep.AsyncRet) = ret;
(*loginStep.AsyncSent) = true;
}
}
}
}
nlSleep(1000);
if (StopWanted) break;
}
}
catch (const std::exception &e)
{
nlwarning(e.what());
}
catch(...)
{
clearQueue();
throw;
}
clearQueue();
}
//============================================================================================
std::string sendMsg(const std::string &msg)
{
CHttpClient httpClient;
std::string ret;
if (!httpClient.connect("http://" + StartupHost))
{
throw NLMISC::Exception("Can't connect to http server");
}
//std::string postString = "http://" + StartupHost + StartupPage + "?cmd=log&msg=" + msg;
//nlwarning("POST STRING = %s", postString.c_str());
if (!httpClient.sendPost("http://" + StartupHost + StartupPage, "cmd=log&msg=" + msg))
{
throw NLMISC::Exception("Post failed");
}
if (!httpClient.receive(ret))
{
throw NLMISC::Exception("Receive failed");
}
httpClient.disconnect();
return ret;
}
//============================================================================================
std::string buildSysInfoString()
{
std::string sis;
sis = "&os=" + CSystemInfo::getOS();
sis += "&proc=" + CSystemInfo::getProc();
sis += "&memory=" + sizeToHumanStd(CSystemInfo::totalPhysicalMemory());
sis += "&video_card=" + getVideoInfoDeviceName();
sis += "&driver_version=" + uint64ToHex(getVideoDriverVersion());
/*
sis = "&os=win2k";
sis += "&proc=p4";
sis += "&memory=1024";
sis += "&video_card=ati";
sis += "&driver_version=1";
*/
return sis;
}
};
//===========================================================================================
CLoginProgressPostThread::CLoginProgressPostThread()
{
// Construct
_Thread = NULL;
_Task = NULL;
}
//===========================================================================================
CLoginProgressPostThread::~CLoginProgressPostThread()
{
release();
}
//===========================================================================================
void CLoginProgressPostThread::init(const std::string &startupHost, const std::string &startupPage)
{
nlassert(!_Thread);
CLoginProgressTask *lpt = new CLoginProgressTask;
// Its not the real Startup Host this values comes from the InstallStatsUrl value of the config file
lpt->StartupHost = startupHost;
lpt->StartupPage = startupPage;
_Task = lpt;
_Thread = IThread::create(lpt);
if (!_Thread)
{
release();
return;
}
_Thread->start();
}
//===========================================================================================
void CLoginProgressPostThread::release()
{
if (_Task)
{
safe_cast(_Task)->StopWanted = true;
}
if (_Thread && _Thread->isRunning()) _Thread->wait();
delete _Task;
delete _Thread;
_Task = NULL;
_Thread = NULL;
}
//===========================================================================================
void CLoginProgressPostThread::step(const CLoginStep &ls)
{
if (!_Task) return;
if (!_Thread->isRunning()) return;
CLoginProgressTask *lpt = safe_cast(_Task);
{
CSynchronized::CAccessor access(&lpt->StepQueue);
access.value().push_back(ls);
}
}
//===========================================================================================
void CLoginProgressPostThread::init(NLMISC::CConfigFile &configFile)
{
std::string installStartupPage;
std::string installStartupHost;
static std::string httpStr = "http://";
if (configFile.getVarPtr("InstallStatsUrl") )
{
static std::string httpStr = "http://";
static std::string::size_type httpStrSize = httpStr.size();
std::string tmp = configFile.getVarPtr("InstallStatsUrl")->asString(0);
std::string::size_type it= tmp.find(httpStr);
if (it != std::string::npos)
{
std::string::size_type hostPageSeparator = tmp.find("/", httpStrSize);
if (hostPageSeparator != std::string::npos)
{
installStartupPage = tmp.substr(hostPageSeparator); //keep the leading slash
installStartupHost = tmp.substr(httpStrSize, hostPageSeparator - httpStrSize); // dont keep the last slah
}
}
}
else
{
nlwarning("Error the InstallStatsUrl is not in the client_default.cfg.");
}
init(installStartupHost, installStartupPage);
}