#include "autoupdate.h"
#include
#include
#include
#include
#include
#include
using std::string;
using std::vector;
#define CLIENT_EXEC "DDNet"
#define SERVER_EXEC "DDNet-Server"
#if defined(CONF_FAMILY_WINDOWS)
#define PLAT_EXT ".exe"
#elif defined(CONF_FAMILY_UNIX)
#define PLAT_EXT ""
#endif
#define PLAT_CLIENT_EXEC CLIENT_EXEC PLAT_EXT
#define PLAT_SERVER_EXEC SERVER_EXEC PLAT_EXT
CAutoUpdate::CAutoUpdate()
{
m_pClient = NULL;
m_pStorage = NULL;
m_pFetcher = NULL;
m_State = CLEAN;
m_Percent = 0;
}
void CAutoUpdate::Init()
{
m_pClient = Kernel()->RequestInterface();
m_pStorage = Kernel()->RequestInterface();
m_pFetcher = Kernel()->RequestInterface();
}
void CAutoUpdate::ProgressCallback(CFetchTask *pTask, void *pUser)
{
CAutoUpdate *pUpdate = (CAutoUpdate *)pUser;
str_copy(pUpdate->m_Status, pTask->Dest(), sizeof(pUpdate->m_Status));
pUpdate->m_Percent = pTask->Progress();
}
void CAutoUpdate::CompletionCallback(CFetchTask *pTask, void *pUser)
{
CAutoUpdate *pUpdate = (CAutoUpdate *)pUser;
if(!str_comp(pTask->Dest(), "update.json")){
pUpdate->m_State = GOT_MANIFEST;
}
else if(!str_comp(pTask->Dest(), pUpdate->m_aLastFile)){
if(pUpdate->m_ClientUpdate)
pUpdate->ReplaceExecutable();
pUpdate->m_State = NEED_RESTART;
}
delete pTask;
}
void CAutoUpdate::FetchFile(const char *pFile, const char *pDestPath)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "https://learath2.info/%s", pFile);
if(!pDestPath)
pDestPath = pFile;
CFetchTask *Task = new CFetchTask;
m_pFetcher->QueueAdd(Task, aBuf, pDestPath, 2, this, &CAutoUpdate::CompletionCallback, &CAutoUpdate::ProgressCallback);
}
void CAutoUpdate::Update()
{
switch(m_State){
case GOT_MANIFEST:
PerformUpdate();
default:
return;
}
}
void CAutoUpdate::AddNewFile(const char *pFile)
{
//Check if already on the download list
for(vector::iterator it = m_AddedFiles.begin(); it < m_AddedFiles.end(); ++it)
if(!str_comp(it->c_str(), pFile))
return;
m_AddedFiles.push_back(string(pFile));
}
void CAutoUpdate::AddRemovedFile(const char *pFile)
{
//First remove from to be downloaded list
for(vector::iterator it = m_AddedFiles.begin(); it < m_AddedFiles.end(); ++it){
if(!str_comp(it->c_str(), pFile)){
m_AddedFiles.erase(it);
break;
}
}
m_RemovedFiles.push_back(string(pFile));
}
void CAutoUpdate::ReplaceExecutable()
{
dbg_msg("autoupdate", "Replacing" PLAT_CLIENT_EXEC);
//Replace running executable by renaming twice...
m_pStorage->RenameFile(PLAT_CLIENT_EXEC, "DDNet.old", 2);
m_pStorage->RenameFile("DDNet.tmp", PLAT_CLIENT_EXEC, 2);
}
void CAutoUpdate::ParseUpdate()
{
IOHANDLE File = m_pStorage->OpenFile("update.json", IOFLAG_READ, IStorage::TYPE_ALL);
if(File){
char aBuf[4096*4];
mem_zero(aBuf, sizeof (aBuf));
io_read(File, aBuf, sizeof(aBuf));
io_close(File);
json_value *pVersions = json_parse(aBuf);
if(pVersions && pVersions->type == json_array){
for(int i = 0; i < json_array_length(pVersions); i++){
const json_value *pTemp;
const json_value *pCurrent = json_array_get(pVersions, i);
if(str_comp(json_string_get(json_object_get(pCurrent, "version")), GAME_RELEASE_VERSION)){
if(json_boolean_get(json_object_get(pCurrent, "client")))
m_ClientUpdate = true;
if(json_boolean_get(json_object_get(pCurrent, "server")))
m_ServerUpdate = true;
if((pTemp = json_object_get(pCurrent, "download"))->type == json_array){
for(int j = 0; j < json_array_length(pTemp); j++)
AddNewFile(json_string_get(json_array_get(pTemp, j)));
}
if((pTemp = json_object_get(pCurrent, "remove"))->type == json_array){
for(int j = 0; j < json_array_length(pTemp); j++)
AddRemovedFile(json_string_get(json_array_get(pTemp, j)));
}
}
else
break;
}
}
}
}
void CAutoUpdate::InitiateUpdate()
{
m_State = GETTING_MANIFEST;
FetchFile("update.json");
}
void CAutoUpdate::IgnoreUpdate()
{
m_State = IGNORED;
}
void CAutoUpdate::PerformUpdate()
{
m_State = PARSING_UPDATE;
dbg_msg("autoupdate", "Parsing update.json");
ParseUpdate();
m_State = DOWNLOADING;
const char *aLastFile;
if(m_ClientUpdate)
aLastFile = "DDNet.tmp";
else if(!m_AddedFiles.empty())
aLastFile= m_AddedFiles.front().c_str();
else
aLastFile = "";
str_copy(m_aLastFile, aLastFile, sizeof(m_aLastFile));
while(!m_AddedFiles.empty()){
FetchFile(m_AddedFiles.back().c_str());
m_AddedFiles.pop_back();
}
while(!m_RemovedFiles.empty()){
m_pStorage->RemoveFile(m_RemovedFiles.back().c_str(), IStorage::TYPE_SAVE);
m_RemovedFiles.pop_back();
}
if(m_ServerUpdate)
FetchFile(PLAT_SERVER_EXEC);
if(m_ClientUpdate)
FetchFile(PLAT_CLIENT_EXEC, "DDNet.tmp");
}