/////////////////////////////////////////////////////////////////// //*-------------------------------------------------------------*// //| Part of the Game Jolt API C++ Library (http://gamejolt.com) |// //*-------------------------------------------------------------*// //| Released under the zlib License |// //| More information available in the readme file |// //*-------------------------------------------------------------*// /////////////////////////////////////////////////////////////////// #include "gjAPI.h" #include #include #include std::vector gjAPI::s_asLog; // **************************************************************** /* constructor */ gjAPI::gjInterUser::gjInterUser(gjAPI* pAPI, gjNetwork* pNetwork)noexcept : m_pAPI (pAPI) , m_pNetwork (pNetwork) { // create NULL user for secure object handling gjData pNullData; pNullData["id"] = "0"; pNullData["username"] = "NOT FOUND"; pNullData["type"] = "Guest"; pNullData["avatar_url"] = GJ_API_AVATAR_DEFAULT; m_apUser[0] = new gjUser(pNullData, m_pAPI); // create guest user for secure object handling gjData pGuestData; pGuestData["id"] = "-1"; pGuestData["username"] = "Guest"; pGuestData["type"] = "Guest"; pGuestData["avatar_url"] = GJ_API_AVATAR_DEFAULT; m_apUser[-1] = new gjUser(pGuestData, m_pAPI); } // **************************************************************** /* destructor */ gjAPI::gjInterUser::~gjInterUser() { // delete all users FOR_EACH(it, m_apUser) SAFE_DELETE(it->second) // clear container m_apUser.clear(); } // **************************************************************** /* access user objects directly (may block) */ gjUser* gjAPI::gjInterUser::GetUser(const int& iID) { gjUserPtr pOutput; if(this->__CheckCache(iID, &pOutput) == GJ_OK) return pOutput; if(this->FetchUserNow(iID, &pOutput) == GJ_OK) return pOutput; return m_apUser[0]; } gjUser* gjAPI::gjInterUser::GetUser(const std::string& sName) { gjUserPtr pOutput; if(this->__CheckCache(sName, &pOutput) == GJ_OK) return pOutput; if(this->FetchUserNow(sName, &pOutput) == GJ_OK) return pOutput; return m_apUser[0]; } // **************************************************************** /* access main user object directly (may block) */ gjUser* gjAPI::gjInterUser::GetMainUser() { if(!m_pAPI->IsUserConnected()) return m_apUser[0]; return this->GetUser(m_pAPI->GetUserName()); } // **************************************************************** /* delete all cached user objects */ void gjAPI::gjInterUser::ClearCache() { // save NULL user and guest user gjUser* pNull = m_apUser[0]; m_apUser.erase(0); gjUser* pGuest = m_apUser[-1]; m_apUser.erase(-1); // delete users FOR_EACH(it, m_apUser) SAFE_DELETE(it->second) // clear container m_apUser.clear(); m_apUser[0] = pNull; m_apUser[-1] = pGuest; } // **************************************************************** /* check for cached user objects */ int gjAPI::gjInterUser::__CheckCache(const int& iID, gjUserPtr* ppOutput) { // retrieve cached user if(m_apUser.count(iID)) { if(ppOutput) (*ppOutput) = m_apUser[iID]; return GJ_OK; } return GJ_NO_DATA_FOUND; } int gjAPI::gjInterUser::__CheckCache(const std::string& sName, gjUserPtr* ppOutput) { // retrieve cached user FOR_EACH(it, m_apUser) { if(it->second->GetName() == sName) { if(ppOutput) (*ppOutput) = it->second; return GJ_OK; } } return GJ_NO_DATA_FOUND; } // **************************************************************** /* process user data and cache user objects */ int gjAPI::gjInterUser::__Process(const std::string& sData, void* pAdd, gjUserPtr* ppOutput) { // parse output gjDataList aaReturn; if(m_pAPI->ParseRequestKeypair(sData, &aaReturn) != GJ_OK) { gjAPI::ErrorLogAdd("API Error: could not parse user"); if(ppOutput) (*ppOutput) = m_apUser[0]; return GJ_REQUEST_FAILED; } // create and cache user object gjUser* pNewUser = new gjUser(aaReturn[0], m_pAPI); const int iID = pNewUser->GetID(); if(m_apUser.count(iID)) { SAFE_DELETE(pNewUser) pNewUser = m_apUser[iID]; } else m_apUser[iID] = pNewUser; if(ppOutput) (*ppOutput) = pNewUser; return pNewUser ? GJ_OK : GJ_NO_DATA_FOUND; } // **************************************************************** /* constructor */ gjAPI::gjInterTrophy::gjInterTrophy(gjAPI* pAPI, gjNetwork* pNetwork)noexcept : m_iCache (0) , m_pAPI (pAPI) , m_pNetwork (pNetwork) { // create NULL trophy for secure object handling gjData pNullData; pNullData["id"] = "0"; pNullData["title"] = "NOT FOUND"; pNullData["difficulty"] = "Bronze"; pNullData["image_url"] = GJ_API_TROPHY_DEFAULT_1; m_apTrophy[0] = new gjTrophy(pNullData, m_pAPI); // reserve some memory m_aiSort.reserve(GJ_API_RESERVE_TROPHY); m_aiSecret.reserve(GJ_API_RESERVE_TROPHY); m_aiHidden.reserve(GJ_API_RESERVE_TROPHY); // retrieve offline-cached trophy data this->__LoadOffCache(); } // **************************************************************** /* destructor */ gjAPI::gjInterTrophy::~gjInterTrophy() { // delete all trophies FOR_EACH(it, m_apTrophy) SAFE_DELETE(it->second) // clear containers m_apTrophy.clear(); m_aiSort.clear(); m_aiSecret.clear(); m_aiHidden.clear(); } // **************************************************************** /* access trophy objects directly (may block) */ gjTrophy* gjAPI::gjInterTrophy::GetTrophy(const int& iID) { if(!m_pAPI->IsUserConnected() && m_iCache == 0) return m_apTrophy[0]; if(m_apTrophy.size() <= 1) { // wait for prefetching if(GJ_API_PREFETCH) m_pNetwork->Wait(2); if(m_apTrophy.size() <= 1) { gjTrophyList apOutput; this->FetchTrophiesNow(0, &apOutput); } } return m_apTrophy.count(iID) ? m_apTrophy[iID] : m_apTrophy[0]; } // **************************************************************** /* delete all cached trophy objects */ void gjAPI::gjInterTrophy::ClearCache(const bool& bFull) { const bool bRemoveAll = bFull || !GJ_API_OFFCACHE_TROPHY; if(bRemoveAll) { // save NULL trophy gjTrophy* pNull = m_apTrophy[0]; m_apTrophy.erase(0); // delete trophies FOR_EACH(it, m_apTrophy) SAFE_DELETE(it->second) // clear container m_apTrophy.clear(); m_apTrophy[0] = pNull; } // set cache status m_iCache = bRemoveAll ? 0 : 1; } // **************************************************************** /* define layout of the returned trophy list */ void gjAPI::gjInterTrophy::SetSort(const int* piIDList, const size_t& iNum) { if(iNum) { // clear sort list m_aiSort.clear(); // add IDs to sort list for(size_t i = 0; i < iNum; ++i) m_aiSort.push_back(piIDList[i]); } // apply sort attribute FOR_EACH(it, m_apTrophy) it->second->__SetSort(0); for(size_t i = 0; i < m_aiSort.size(); ++i) if(m_apTrophy.count(m_aiSort[i])) m_apTrophy[m_aiSort[i]]->__SetSort(int(i+1)); } // **************************************************************** /* define secret trophy objects */ void gjAPI::gjInterTrophy::SetSecret(const int* piIDList, const size_t& iNum) { if(iNum) { // clear secret list m_aiSecret.clear(); // add IDs to secret list for(size_t i = 0; i < iNum; ++i) m_aiSecret.push_back(piIDList[i]); } // apply secret attribute FOR_EACH(it, m_apTrophy) it->second->__SetSecret(false); FOR_EACH(it, m_aiSecret) if(m_apTrophy.count(*it)) m_apTrophy[*it]->__SetSecret(true); } // **************************************************************** /* define hidden trophy objects */ void gjAPI::gjInterTrophy::SetHidden(const int* piIDList, const size_t& iNum) { if(iNum) { // clear hidden list m_aiHidden.clear(); // add IDs to hidden list for(size_t i = 0; i < iNum; ++i) m_aiHidden.push_back(piIDList[i]); } // apply hidden attribute and remove all hidden trophy objects FOR_EACH(it, m_aiHidden) if(m_apTrophy.count(*it)) m_apTrophy.erase(m_apTrophy.find(*it)); } // **************************************************************** /* check for cached trophy objects */ int gjAPI::gjInterTrophy::__CheckCache(const int& iAchieved, gjTrophyList* papOutput) { // retrieve cached trophies if(m_apTrophy.size() > 1) { if(papOutput) { gjTrophyList apConvert; apConvert.reserve(GJ_API_RESERVE_TROPHY); // add sorted trophies FOR_EACH(it, m_aiSort) if(m_apTrophy.count(*it)) apConvert.push_back(m_apTrophy[*it]); // add missing unsorted trophies FOR_EACH(it, m_apTrophy) { if(it->first) { if(std::find(apConvert.begin(), apConvert.end(), it->second) == apConvert.end()) apConvert.push_back(it->second); } } // check for achieved status FOR_EACH(it, apConvert) { gjTrophy* pTrophy = (*it); if((iAchieved > 0 && pTrophy->IsAchieved()) || (iAchieved < 0 && !pTrophy->IsAchieved()) || !iAchieved) (*papOutput).push_back(pTrophy); } } return GJ_OK; } return GJ_NO_DATA_FOUND; } // **************************************************************** /* process trophy data and cache trophy objects */ int gjAPI::gjInterTrophy::__Process(const std::string& sData, void* pAdd, gjTrophyList* papOutput) { // parse output gjDataList aaReturn; if(m_pAPI->ParseRequestKeypair(sData, &aaReturn) != GJ_OK) { gjAPI::ErrorLogAdd("API Error: could not parse trophies"); return GJ_REQUEST_FAILED; } // offline-cache trophy data if(!aaReturn.empty()) this->__SaveOffCache(sData); if(m_iCache == 0) m_iCache = 2; // create and cache trophy objects FOR_EACH(it, aaReturn) { gjTrophy* pNewTrophy = new gjTrophy(*it, m_pAPI); const int iID = pNewTrophy->GetID(); if(m_apTrophy.count(iID)) { *m_apTrophy[iID] = *pNewTrophy; SAFE_DELETE(pNewTrophy) } else m_apTrophy[iID] = pNewTrophy; } // apply attributes this->SetSort (NULL, 0); this->SetSecret(NULL, 0); this->SetHidden(NULL, 0); return (this->__CheckCache(P_TO_I(pAdd), papOutput) == GJ_OK) ? GJ_OK : GJ_NO_DATA_FOUND; } // **************************************************************** /* save trophy data to a cache file */ void gjAPI::gjInterTrophy::__SaveOffCache(const std::string& sData) { if(!GJ_API_OFFCACHE_TROPHY) return; if(m_iCache != 0) return; // open cache file std::FILE* pFile = std::fopen(GJ_API_OFFCACHE_NAME, "w"); if(pFile) { // write data and close cache file std::fputs("[TROPHY]\n", pFile); std::fputs(sData.c_str(), pFile); std::fclose(pFile); } } // **************************************************************** /* load trophy data from a cache file */ void gjAPI::gjInterTrophy::__LoadOffCache() { if(!GJ_API_OFFCACHE_TROPHY) return; if(m_iCache != 0) return; // open cache file std::FILE* pFile = std::fopen(GJ_API_OFFCACHE_NAME, "r"); if(pFile) { // read trophy header char acHeader[32]; std::fscanf(pFile, "%31[^\n]%*c", acHeader); // read trophy data std::string sData; while(true) { char acLine[1024]; std::fscanf(pFile, "%1023[^\n]%*c", acLine); if(std::feof(pFile)) break; if(std::strlen(acLine) > 1) { sData += acLine; sData += '\n'; } } // close cache file std::fclose(pFile); if(!sData.empty()) { // flag offline caching and load offline-cached trophies m_iCache = 1; this->__Process(sData, NULL, NULL); } } } // **************************************************************** /* constructor */ gjAPI::gjInterScore::gjInterScore(gjAPI* pAPI, gjNetwork* pNetwork)noexcept : m_pAPI (pAPI) , m_pNetwork (pNetwork) { // create NULL score table for secure object handling gjData pNullData; pNullData["id"] = "0"; pNullData["name"] = "NOT FOUND"; m_apScoreTable[0] = new gjScoreTable(pNullData, m_pAPI); } // **************************************************************** /* destructor */ gjAPI::gjInterScore::~gjInterScore() { // delete all score tables and scores entries FOR_EACH(it, m_apScoreTable) SAFE_DELETE(it->second) // clear container m_apScoreTable.clear(); } // **************************************************************** /* access score table objects directly (may block) */ gjScoreTable* gjAPI::gjInterScore::GetScoreTable(const int &iID) { if(m_apScoreTable.size() <= 1) { // wait for prefetching if(GJ_API_PREFETCH) m_pNetwork->Wait(2); if(m_apScoreTable.size() <= 1) { gjScoreTableMap apOutput; this->FetchScoreTablesNow(&apOutput); } } gjScoreTable* pPrimary = gjScoreTable::GetPrimary(); return iID ? (m_apScoreTable.count(iID) ? m_apScoreTable[iID] : m_apScoreTable[0]) : (pPrimary ? pPrimary : m_apScoreTable[0]); } // **************************************************************** /* delete all cached score table objects and score entries */ void gjAPI::gjInterScore::ClearCache() { // save NULL score table gjScoreTable* pNull = m_apScoreTable[0]; m_apScoreTable.erase(0); // delete score tables and scores entries FOR_EACH(it, m_apScoreTable) SAFE_DELETE(it->second) // clear container m_apScoreTable.clear(); m_apScoreTable[0] = pNull; } // **************************************************************** /* check for cached score table objects */ int gjAPI::gjInterScore::__CheckCache(gjScoreTableMap* papOutput) { // retrieve cached score tables if(m_apScoreTable.size() > 1) { if(papOutput) { FOR_EACH(it, m_apScoreTable) if(it->first) (*papOutput)[it->first] = it->second; } return GJ_OK; } return GJ_NO_DATA_FOUND; } // **************************************************************** /* process score table data and cache score table objects */ int gjAPI::gjInterScore::__Process(const std::string& sData, void* pAdd, gjScoreTableMap* papOutput) { // parse output gjDataList aaReturn; if(m_pAPI->ParseRequestKeypair(sData, &aaReturn) != GJ_OK) { gjAPI::ErrorLogAdd("API Error: could not parse score tables"); return GJ_REQUEST_FAILED; } // create and cache score tables FOR_EACH(it, aaReturn) { gjScoreTable* pNewScoreTable = new gjScoreTable(*it, m_pAPI); const int iID = pNewScoreTable->GetID(); if(m_apScoreTable.count(iID)) SAFE_DELETE(pNewScoreTable) else m_apScoreTable[iID] = pNewScoreTable; } return (this->__CheckCache(papOutput) == GJ_OK) ? GJ_OK : GJ_NO_DATA_FOUND; } // **************************************************************** /* constructor */ gjAPI::gjInterDataStore::gjInterDataStore(const int& iType, gjAPI* pAPI, gjNetwork* pNetwork)noexcept : m_iType (iType) , m_pAPI (pAPI) , m_pNetwork (pNetwork) { } // **************************************************************** /* destructor */ gjAPI::gjInterDataStore::~gjInterDataStore() { this->ClearCache(); } // **************************************************************** /* create and access data store items directly */ gjDataItem* gjAPI::gjInterDataStore::GetDataItem(const std::string& sKey) { // create new data store item if(!m_apDataItem.count(sKey)) { gjData asDataItemData; asDataItemData["key"] = sKey; m_apDataItem[sKey] = new gjDataItem(asDataItemData, m_iType, m_pAPI); } return m_apDataItem.count(sKey) ? m_apDataItem[sKey] : NULL; } // **************************************************************** /* delete all cached data store items */ void gjAPI::gjInterDataStore::ClearCache() { // delete data store items FOR_EACH(it, m_apDataItem) SAFE_DELETE(it->second) // clear container m_apDataItem.clear(); } // **************************************************************** /* check for cached data store items */ int gjAPI::gjInterDataStore::__CheckCache(gjDataItemMap* papOutput) { // retrieve cached data store items if(!m_apDataItem.empty()) { if(papOutput) { FOR_EACH(it, m_apDataItem) (*papOutput)[it->first] = it->second; } return GJ_OK; } return GJ_NO_DATA_FOUND; } // **************************************************************** /* process data store data and cache data store items */ int gjAPI::gjInterDataStore::__Process(const std::string& sData, void* pAdd, gjDataItemMap* papOutput) { // parse output gjDataList aaReturn; if(m_pAPI->ParseRequestKeypair(sData, &aaReturn) != GJ_OK) { gjAPI::ErrorLogAdd("API Error: could not parse data store items"); return GJ_REQUEST_FAILED; } // create and cache data store items FOR_EACH(it, aaReturn) { gjDataItem* pNewDataItem = new gjDataItem(*it, m_iType, m_pAPI); const std::string& sKey = pNewDataItem->GetKey(); if(m_apDataItem.count(sKey)) { SAFE_DELETE(pNewDataItem) pNewDataItem = m_apDataItem[sKey]; } else m_apDataItem[sKey] = pNewDataItem; if(papOutput) (*papOutput)[sKey] = pNewDataItem; } return aaReturn.empty() ? GJ_NO_DATA_FOUND : GJ_OK; } // **************************************************************** /* constructor */ gjAPI::gjInterFile::gjInterFile(gjAPI* pAPI, gjNetwork* pNetwork)noexcept : m_pAPI (pAPI) , m_pNetwork (pNetwork) { // reserve some memory m_asFile.reserve(GJ_API_RESERVE_FILE); } // **************************************************************** /* destructor */ gjAPI::gjInterFile::~gjInterFile() { this->ClearCache(); } // **************************************************************** /* delete all cached file paths */ void gjAPI::gjInterFile::ClearCache() { // clear container m_asFile.clear(); } // **************************************************************** /* check for cached files */ int gjAPI::gjInterFile::__CheckCache(const std::string& sPath) { // compare cached file paths FOR_EACH(it, m_asFile) { if(sPath == (*it)) return GJ_OK; } return GJ_NO_DATA_FOUND; } // **************************************************************** /* process downloaded file */ int gjAPI::gjInterFile::__Process(const std::string& sData, void* pAdd, std::string* psOutput) { // save path of the file if(this->__CheckCache(sData) != GJ_OK) m_asFile.push_back(sData); if(psOutput) (*psOutput) = sData; return GJ_OK; } // **************************************************************** /* constructor */ gjAPI::gjAPI(const int iGameID, const std::string sGamePrivateKey)noexcept : m_iGameID (iGameID) , m_sGamePrivateKey (sGamePrivateKey) , m_sUserName ("") , m_sUserToken ("") , m_iNextPing (0) , m_bActive (false) , m_bConnected (false) , m_sProcUserName ("") , m_sProcUserToken ("") { // init error log gjAPI::ErrorLogReset(); // pre-process the game ID m_sProcGameID = gjAPI::UtilIntToString(m_iGameID); // create network object m_pNetwork = new gjNetwork(this); // create sub-interface objects m_pInterUser = new gjInterUser(this, m_pNetwork); m_pInterTrophy = new gjInterTrophy(this, m_pNetwork); m_pInterScore = new gjInterScore(this, m_pNetwork); m_pInterDataStoreGlobal = new gjInterDataStore(0, this, m_pNetwork); m_pInterDataStoreUser = new gjInterDataStore(1, this, m_pNetwork); m_pInterFile = new gjInterFile(this, m_pNetwork); // prefetch score tables if(GJ_API_PREFETCH && iGameID) m_pInterScore->FetchScoreTablesCall(GJ_NETWORK_NULL_THIS(gjScoreTableMap)); } // **************************************************************** /* destructor */ gjAPI::~gjAPI() { // logout last user this->Logout(); // delete network object SAFE_DELETE(m_pNetwork) // delete sub-interface objects SAFE_DELETE(m_pInterUser) SAFE_DELETE(m_pInterTrophy) SAFE_DELETE(m_pInterScore) SAFE_DELETE(m_pInterDataStoreGlobal) SAFE_DELETE(m_pInterDataStoreUser) SAFE_DELETE(m_pInterFile) } // **************************************************************** /* explicitly initialize the object */ void gjAPI::Init(const int& iGameID, const std::string& sGamePrivateKey) { // save game data m_iGameID = iGameID; m_sGamePrivateKey = sGamePrivateKey; // pre-process the game ID m_sProcGameID = gjAPI::UtilIntToString(m_iGameID); // prefetch score tables if(GJ_API_PREFETCH && iGameID) m_pInterScore->FetchScoreTablesCall(GJ_NETWORK_NULL_THIS(gjScoreTableMap)); } // **************************************************************** /* main update function of the library */ void gjAPI::Update() { // update network object m_pNetwork->Update(); if(!this->IsUserConnected()) return; if(m_iNextPing) { // update ping for the user session const time_t iCurTime = time(NULL); if(iCurTime >= m_iNextPing) { m_iNextPing = iCurTime + GJ_API_PING_TIME; this->__PingSession(m_bActive); } } } // **************************************************************** /* logout with specific user */ int gjAPI::Logout() { if(!this->IsUserConnected()) return GJ_NOT_CONNECTED; // clear user specific data m_pInterTrophy->ClearCache(false); m_pInterDataStoreUser->ClearCache(); // close the user session if(m_iNextPing) this->__CloseSession(); // clear main user data m_sUserName = ""; m_sUserToken = ""; m_sProcUserName = ""; m_sProcUserToken = ""; // clear connection m_bConnected = false; return GJ_OK; } // **************************************************************** /* parse a valid response string in keypair format */ int gjAPI::ParseRequestKeypair(const std::string& sInput, gjDataList* paaOutput) { if(!paaOutput) return GJ_INVALID_INPUT; gjData aData; std::istringstream sStream(sInput); std::string sToken; // loop through input string while(std::getline(sStream, sToken)) { // remove redundant characters gjAPI::UtilTrimString(&sToken); if(sToken.empty()) continue; // separate key and value const size_t iPos = sToken.find(':'); const std::string sKey = sToken.substr(0, iPos); const std::string sValue = sToken.substr(iPos + 2, sToken.length() - iPos - 3); // next data block on same key if(aData.count(sKey.c_str())) { paaOutput->push_back(aData); aData.clear(); } // create key and save value aData[sKey.c_str()] = sValue; } // insert last data block and check size if(!aData.empty()) paaOutput->push_back(aData); if(paaOutput->empty()) { paaOutput->push_back(aData); gjAPI::ErrorLogAdd("API Error: string parsing failed"); return GJ_INVALID_INPUT; } // check for failed request if(paaOutput->front()["success"] != "true") { gjAPI::ErrorLogAdd("API Error: request was unsuccessful"); gjAPI::ErrorLogAdd("API Error: " + paaOutput->front()["message"]); return GJ_REQUEST_FAILED; } return GJ_OK; } // **************************************************************** /* parse a valid response string in Dump format */ int gjAPI::ParseRequestDump(const std::string& sInput, std::string* psOutput) { if(!psOutput) return GJ_INVALID_INPUT; // read status const std::string sStatus = sInput.substr(0, sInput.find_first_of(13)); // read data (*psOutput) = sInput.substr(sStatus.length()+2); // check for failed request if(sStatus != "SUCCESS") { gjAPI::ErrorLogAdd("API Error: request was unsuccessful"); gjAPI::ErrorLogAdd("API Error: " + (*psOutput)); return GJ_REQUEST_FAILED; } return GJ_OK; } // **************************************************************** /* delete all cached objects */ void gjAPI::ClearCache() { // clear cache of all sub-interfaces m_pInterUser->ClearCache(); m_pInterTrophy->ClearCache(true); m_pInterScore->ClearCache(); m_pInterDataStoreGlobal->ClearCache(); m_pInterDataStoreUser->ClearCache(); m_pInterFile->ClearCache(); } // **************************************************************** /* escape a string for proper url calling */ std::string gjAPI::UtilEscapeString(const std::string& sString) { std::string sOutput = ""; // loop through input string for(size_t i = 0; i < sString.length(); ++i) { // check the character type if ( (48 <= sString[i] && sString[i] <= 57) || // 0-9 (65 <= sString[i] && sString[i] <= 90) || // ABC...XYZ (97 <= sString[i] && sString[i] <= 122) || // abc...xyz ( sString[i] == '~' || sString[i] == '.' || sString[i] == '-' || sString[i] == '_' ) ) { // add valid character sOutput += sString[i]; } else { // convert character to hexadecimal value sOutput += "%" + gjAPI::UtilCharToHex(sString[i]); } } return sOutput; } // **************************************************************** /* trim a standard string on both sides */ void gjAPI::UtilTrimString(std::string* psInput) { const size_t iFirst = psInput->find_first_not_of(" \n\r\t"); if(iFirst != std::string::npos) psInput->erase(0, iFirst); const size_t iLast = psInput->find_last_not_of(" \n\r\t"); if(iLast != std::string::npos) psInput->erase(iLast+1); } // **************************************************************** /* convert a character into his hexadecimal value */ std::string gjAPI::UtilCharToHex(const char& cChar) { int iValue = (int)cChar; if(iValue < 0) iValue += 256; char acBuffer[8]; std::sprintf(acBuffer, "%02X", iValue); return acBuffer; } // **************************************************************** /* simply convert an integer into a string */ std::string gjAPI::UtilIntToString(const int& iInt) { char acBuffer[32]; std::sprintf(acBuffer, "%d", iInt); return acBuffer; } // **************************************************************** /* create a folder hierarchy */ void gjAPI::UtilCreateFolder(const std::string& sFolder) { size_t iPos = 0; // loop through path while((iPos = sFolder.find_first_of("/\\", iPos+2)) != std::string::npos) { const std::string sSubFolder = sFolder.substr(0, iPos); // create subfolder #if defined(_GJ_WINDOWS_) CreateDirectoryA(sSubFolder.c_str(), NULL); #else mkdir(sSubFolder.c_str(), S_IRWXU); #endif } } // **************************************************************** /* get timestamp as string */ std::string gjAPI::UtilTimestamp(const time_t iTime) { // format the time value tm* pFormat = std::localtime(&iTime); // create output char acBuffer[16]; std::sprintf(acBuffer, "%02d:%02d:%02d", pFormat->tm_hour, pFormat->tm_min, pFormat->tm_sec); return acBuffer; } // **************************************************************** /* reset error log */ void gjAPI::ErrorLogReset() { if(GJ_API_LOGFILE) { // remove error log file if empty if(s_asLog.empty()) std::remove(GJ_API_LOGFILE_NAME); } } // **************************************************************** /* add error log entry */ void gjAPI::ErrorLogAdd(const std::string& sMsg) { const std::string sTimeMsg = "[" + gjAPI::UtilTimestamp() + "] " + sMsg; // add message s_asLog.push_back(sTimeMsg); if(GJ_API_LOGFILE) { // add message to error log file std::FILE* pFile = std::fopen(GJ_API_LOGFILE_NAME, "a"); if(pFile) { std::fprintf(pFile, "%s\n", sTimeMsg.c_str()); std::fclose(pFile); } } #if defined(_GJ_DEBUG_) // print message to terminal std::cerr << "(!GJ) " << sTimeMsg << std::endl; #endif } // **************************************************************** /* open the user session */ int gjAPI::__OpenSession() { if(!this->IsUserConnected()) return GJ_NOT_CONNECTED; // send non-blocking open request if(m_pNetwork->SendRequest("/sessions/open/" "?game_id=" + m_sProcGameID + "&username=" + m_sProcUserName + "&user_token=" + m_sProcUserToken, NULL, this, &gjAPI::Null, NULL, GJ_NETWORK_NULL_THIS(std::string))) return GJ_REQUEST_FAILED; // init session attributes m_iNextPing = std::time(NULL) + GJ_API_PING_TIME; m_bActive = true; return GJ_OK; } // **************************************************************** /* ping the user session */ int gjAPI::__PingSession(const bool& bActive) { if(!this->IsUserConnected()) return GJ_NOT_CONNECTED; // use active status const std::string sActive = bActive ? "active" : "idle"; // send non-blocking ping request if(m_pNetwork->SendRequest("/sessions/ping/" "?game_id=" + m_sProcGameID + "&username=" + m_sProcUserName + "&user_token=" + m_sProcUserToken + "&status=" + sActive, NULL, this, &gjAPI::Null, NULL, GJ_NETWORK_NULL_THIS(std::string))) return GJ_REQUEST_FAILED; return GJ_OK; } // **************************************************************** /* close the user session */ int gjAPI::__CloseSession() { if(!this->IsUserConnected()) return GJ_NOT_CONNECTED; // send non-blocking close request if(m_pNetwork->SendRequest("/sessions/close/" "?game_id=" + m_sProcGameID + "&username=" + m_sProcUserName + "&user_token=" + m_sProcUserToken, NULL, this, &gjAPI::Null, NULL, GJ_NETWORK_NULL_THIS(std::string))) return GJ_REQUEST_FAILED; // clear session attributes m_iNextPing = 0; return GJ_OK; } // **************************************************************** /* callback for login with specific user */ int gjAPI::__LoginCallback(const std::string& sData, void* pAdd, int* pbOutput) { // check for success gjDataList aaReturn; if(this->ParseRequestKeypair(sData, &aaReturn) != GJ_OK) { gjAPI::ErrorLogAdd("API Error: could not authenticate user <" + m_sUserName + ">"); // clear main user data m_sUserName = ""; m_sUserToken = ""; m_sProcUserName = ""; m_sProcUserToken = ""; // determine error type const int iError = std::strcmp(SAFE_MAP_GET(aaReturn[0], "success").c_str(), "false") ? GJ_NETWORK_ERROR : GJ_REQUEST_FAILED; if(pbOutput) (*pbOutput) = iError; return pbOutput ? GJ_OK : iError; } // set connection m_bConnected = true; // open the user session if(pAdd) this->__OpenSession(); // prefetch user data if(GJ_API_PREFETCH) { m_pInterUser->FetchUserCall(0, GJ_NETWORK_NULL_THIS(gjUserPtr)); m_pInterTrophy->FetchTrophiesCall(0, GJ_NETWORK_NULL_THIS(gjTrophyList)); m_pInterDataStoreUser->FetchDataItemsCall(GJ_NETWORK_NULL_THIS(gjDataItemMap)); } if(pbOutput) (*pbOutput) = GJ_OK; return GJ_OK; }