diff --git a/src/engine/autoupdate.h b/src/engine/autoupdate.h new file mode 100644 index 000000000..355f1af7b --- /dev/null +++ b/src/engine/autoupdate.h @@ -0,0 +1,27 @@ +#ifndef ENGINE_AUTOUPDATE_H +#define ENGINE_AUTOUPDATE_H + +#include "kernel.h" + +class IAutoUpdate : public IInterface +{ + MACRO_INTERFACE("autoupdate", 0) +public: + enum + { + IGNORED = -1, + CLEAN, + GETTING_MANIFEST, + GOT_MANIFEST, + PARSING_UPDATE, + DOWNLOADING, + NEED_RESTART, + }; + + virtual void Update() = 0; + virtual void InitiateUpdate() = 0; + virtual void IgnoreUpdate() = 0; + +}; + +#endif diff --git a/src/engine/client/autoupdate.cpp b/src/engine/client/autoupdate.cpp new file mode 100644 index 000000000..654e72d44 --- /dev/null +++ b/src/engine/client/autoupdate.cpp @@ -0,0 +1,169 @@ +#include "autoupdate.h" +#include +#include +#include +#include +#include +#include + +using std::string; +using std::vector; + +#if defined(CONF_FAMILY_WINDOWS) + #define PLAT_EXEC_NAME "DDNet.exe" +#elif defined(CONF_FAMILY_UNIX) + #define PLAT_EXEC_NAME "DDNet" +#endif + +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(const char *pDest, void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr) +{ + CAutoUpdate *pUpdate = (CAutoUpdate *)pUser; + str_copy(pUpdate->m_Status, pDest, sizeof(m_Status)); + pUpdate->m_Percent = (100*DlCurr)/(DlTotal ? DlTotal : 1); +} + +void CAutoUpdate::CompletionCallback(const char *pDest, void *pUser) +{ + CAutoUpdate *pUpdate = (CAutoUpdate *)pUser; + if(!str_comp(pDest, "update.json")){ + pUpdate->m_State = GOT_MANIFEST; + } + else if(!str_comp(pDest, pUpdate->m_aLastFile)){ + if(pUpdate->m_ClientUpdate) + pUpdate->ReplaceExecutable(); + pUpdate->m_State = NEED_RESTART; + } +} + +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; + m_pFetcher->QueueAdd(aBuf, pDestPath, &CAutoUpdate::CompletionCallback, &CAutoUpdate::ProgressCallback, this); +} + +void CAutoUpdate::Update() +{ + switch(m_State){ + case GOT_MANIFEST: + PerformUpdate(); + default: + return; + } +} + +void CAutoUpdate::AddNewFile(const char *pFile) +{ + 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_EXEC_NAME); + m_pStorage->RenameFile(PLAT_EXEC_NAME, "DDNet.old", 2); + m_pStorage->RenameFile("DDNet.tmp", PLAT_EXEC_NAME, 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; + + if(m_ClientUpdate) + str_copy(m_aLastFile, "DDNet.tmp", sizeof(m_aLastFile)); + else + str_copy(m_aLastFile, m_AddedFiles.front().c_str(), 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_ClientUpdate) + FetchFile(PLAT_EXEC_NAME, "DDNet.tmp"); +} \ No newline at end of file diff --git a/src/engine/client/autoupdate.h b/src/engine/client/autoupdate.h new file mode 100644 index 000000000..064657be6 --- /dev/null +++ b/src/engine/client/autoupdate.h @@ -0,0 +1,46 @@ +#ifndef ENGINE_CLIENT_AUTOUPDATE_H +#define ENGINE_CLIENT_AUTOUPDATE_H + +#include +#include +#include + +class CAutoUpdate : public IAutoUpdate +{ + class IClient *m_pClient; + class IStorage *m_pStorage; + class IFetcher *m_pFetcher; + + int m_State; + char m_Status[256]; + int m_Percent; + char m_aLastFile[256]; + + bool m_ClientUpdate; + bool m_ServerUpdate; + + std::vector m_AddedFiles; + std::vector m_RemovedFiles; + + void AddNewFile(const char *pFile); + void AddRemovedFile(const char *pFile); + void FetchFile(const char *pFile, const char *pDestPath = 0); + + void ParseUpdate(); + void PerformUpdate(); + void ReplaceExecutable(); + +public: + CAutoUpdate(); + static void ProgressCallback(const char *pDest, void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr); + static void CompletionCallback(const char *pDest, void *pUser); + + + virtual void InitiateUpdate(); + void IgnoreUpdate(); + void Init(); + virtual void Update(); + void Restart(); +}; + +#endif \ No newline at end of file diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index b2667898f..4d015eefe 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -59,6 +59,7 @@ #include "friends.h" #include "serverbrowser.h" #include "fetcher.h" +#include "autoupdate.h" #include "client.h" #include @@ -1165,9 +1166,7 @@ void CClient::ProcessConnlessPacket(CNetChunk *pPacket) // assume version is out of date when version-data doesn't match if(!VersionMatch) - { str_copy(m_aVersionStr, aVersion, sizeof(m_aVersionStr)); - } // request the news CNetChunk Packet; @@ -2474,6 +2473,7 @@ void CClient::RegisterInterfaces() Kernel()->RegisterInterface(static_cast(&m_DemoPlayer)); Kernel()->RegisterInterface(static_cast(&m_ServerBrowser)); Kernel()->RegisterInterface(static_cast(&m_Fetcher)); + Kernel()->RegisterInterface(static_cast(&m_AutoUpdate)); Kernel()->RegisterInterface(static_cast(&m_Friends)); } @@ -2489,6 +2489,7 @@ void CClient::InitInterfaces() m_pMap = Kernel()->RequestInterface(); m_pMasterServer = Kernel()->RequestInterface(); m_pFetcher = Kernel()->RequestInterface(); + m_pAutoUpdate = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage); @@ -2496,6 +2497,7 @@ void CClient::InitInterfaces() m_ServerBrowser.SetBaseInfo(&m_NetClient[2], m_pGameClient->NetVersion()); m_Fetcher.Init(); + m_AutoUpdate.Init(); m_Friends.Init(); @@ -2632,6 +2634,8 @@ void CClient::Run() if(Input()->Update()) break; // SDL_QUIT + AutoUpdate()->Update(); + // update sound Sound()->Update(); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 5228b4ee7..a99930d45 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -64,6 +64,7 @@ class CClient : public IClient, public CDemoPlayer::IListner IConsole *m_pConsole; IStorage *m_pStorage; IFetcher *m_pFetcher; + IAutoUpdate *m_pAutoUpdate; IEngineMasterServer *m_pMasterServer; enum @@ -78,6 +79,7 @@ class CClient : public IClient, public CDemoPlayer::IListner class CDemoEditor m_DemoEditor; class CServerBrowser m_ServerBrowser; class CFetcher m_Fetcher; + class CAutoUpdate m_AutoUpdate; class CFriends m_Friends; class CMapChecker m_MapChecker; @@ -202,6 +204,7 @@ public: IEngineMasterServer *MasterServer() { return m_pMasterServer; } IStorage *Storage() { return m_pStorage; } IFetcher *Fetcher() { return m_pFetcher; } + IAutoUpdate *AutoUpdate() { return m_pAutoUpdate; } CClient(); diff --git a/src/game/client/component.h b/src/game/client/component.h index 211ecc6c1..6a4b1ec7c 100644 --- a/src/game/client/component.h +++ b/src/game/client/component.h @@ -28,6 +28,7 @@ protected: class IServerBrowser *ServerBrowser() const { return m_pClient->ServerBrowser(); } class CLayers *Layers() const { return m_pClient->Layers(); } class CCollision *Collision() const { return m_pClient->Collision(); } + class IAutoUpdate *AutoUpdate() const { return m_pClient->AutoUpdate(); } public: virtual ~CComponent() {} class CGameClient *GameClient() const { return m_pClient; } diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 934f1c4f1..921e8d670 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -123,6 +124,7 @@ void CGameClient::OnConsoleInit() m_pServerBrowser = Kernel()->RequestInterface(); m_pEditor = Kernel()->RequestInterface(); m_pFriends = Kernel()->RequestInterface(); + m_pAutoUpdate = Kernel()->RequestInterface(); // setup pointers m_pBinds = &::gs_Binds; diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 1990503e6..ad790aad1 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -95,6 +95,7 @@ class CGameClient : public IGameClient class IServerBrowser *m_pServerBrowser; class IEditor *m_pEditor; class IFriends *m_pFriends; + class IAutoUpdate *m_pAutoUpdate; CLayers m_Layers; class CCollision m_Collision; @@ -135,6 +136,7 @@ public: class CCollision *Collision() { return &m_Collision; }; class IEditor *Editor() { return m_pEditor; } class IFriends *Friends() { return m_pFriends; } + class IAutoUpdate *AutoUpdate() { return m_pAutoUpdate; } int NetobjNumCorrections() { return m_NetObjHandler.NumObjCorrections(); } const char *NetobjCorrectedOn() { return m_NetObjHandler.CorrectedObjOn(); }