486 lines
18 KiB
C++
486 lines
18 KiB
C++
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2011-2015 Joerg Henrichs
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "addons/news_manager.hpp"
|
|
|
|
#include "config/user_config.hpp"
|
|
#include "io/file_manager.hpp"
|
|
#include "online/http_request.hpp"
|
|
#include "online/request_manager.hpp"
|
|
#include "states_screens/addons_screen.hpp"
|
|
#include "states_screens/main_menu_screen.hpp"
|
|
#include "utils/string_utils.hpp"
|
|
#include "utils/time.hpp"
|
|
#include "utils/translation.hpp"
|
|
|
|
#include <iostream>
|
|
|
|
using namespace Online;
|
|
|
|
NewsManager *NewsManager::m_news_manager=NULL;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
NewsManager::NewsManager() : m_news(std::vector<NewsMessage>())
|
|
{
|
|
m_current_news_message = -1;
|
|
m_error_message.setAtomic("");
|
|
m_force_refresh = false;
|
|
init(false);
|
|
} // NewsManage
|
|
|
|
// ---------------------------------------------------------------------------
|
|
NewsManager::~NewsManager()
|
|
{
|
|
} // ~NewsManager
|
|
|
|
// ---------------------------------------------------------------------------
|
|
/** This function initialises the data for the news manager. It starts a
|
|
* separate thread to execute downloadNews() - which (if necessary) downloads
|
|
* the news.xml file and updates the list of news messages. It also
|
|
* initialises the addons manager (which can trigger another download of
|
|
* news.xml).
|
|
* \param force_refresh Re-download news.xml, even if
|
|
*/
|
|
void NewsManager::init(bool force_refresh)
|
|
{
|
|
m_force_refresh = force_refresh;
|
|
|
|
// The rest (which potentially involves downloading news.xml) is handled
|
|
// in a separate thread, so that the GUI remains responsive. It is only
|
|
// started if internet access is enabled, else nothing is done in the
|
|
// thread anyway (and the addons menu is disabled as a result).
|
|
if(UserConfigParams::m_internet_status==RequestManager::IPERM_ALLOWED)
|
|
{
|
|
pthread_attr_t attr;
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
|
// Should be the default, but just in case:
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
//pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
|
|
pthread_t thread_id;
|
|
int error = pthread_create(&thread_id, &attr,
|
|
&NewsManager::downloadNews, this);
|
|
if (error)
|
|
{
|
|
Log::warn("news", "Could not create thread, error=%d", error);
|
|
// In this case just execute the downloading code with this thread
|
|
downloadNews(this);
|
|
}
|
|
pthread_attr_destroy(&attr);
|
|
}
|
|
|
|
} //init
|
|
|
|
// ---------------------------------------------------------------------------
|
|
/** This function submits request which will download the news.xml file
|
|
* if necessary. It is running in its own thread, so we can use blocking
|
|
* download calls without blocking the GUI.
|
|
* \param obj This is 'this' object, passed on during pthread creation.
|
|
*/
|
|
void* NewsManager::downloadNews(void *obj)
|
|
{
|
|
NewsManager *me = (NewsManager*)obj;
|
|
me->clearErrorMessage();
|
|
|
|
std::string xml_file = file_manager->getAddonsFile("news.xml");
|
|
bool news_exists = file_manager->fileExists(xml_file);
|
|
|
|
// The news message must be updated if either it has never been updated,
|
|
// or if the time of the last update was more than news_frequency ago,
|
|
// or because a 'refresh' was explicitly requested by the user, or no
|
|
// news.xml file exists.
|
|
bool download = ( UserConfigParams::m_news_last_updated==0 ||
|
|
UserConfigParams::m_news_last_updated
|
|
+UserConfigParams::m_news_frequency
|
|
< StkTime::getTimeSinceEpoch() ||
|
|
me->m_force_refresh ||
|
|
!news_exists )
|
|
&& UserConfigParams::m_internet_status==RequestManager::IPERM_ALLOWED;
|
|
|
|
const XMLNode *xml = NULL;
|
|
|
|
if(!download && news_exists)
|
|
{
|
|
// If (so far) we don't need to download, there should be an existing
|
|
// file. Try to read this, and do some basic checks
|
|
xml = new XMLNode(xml_file);
|
|
// A proper news file has at least a version number, mtime, frequency
|
|
// and an include node (which contains addon data) defined. If this is
|
|
// not the case, assume that it is an invalid download, or a corrupt
|
|
// local file. Try downloading again after resetting the news server
|
|
// back to the default.
|
|
int version=-1;
|
|
if( !xml->get("version", &version) || version!=1 ||
|
|
!xml->get("mtime", &version) ||
|
|
!xml->getNode("include") ||
|
|
!xml->get("frequency", &version) )
|
|
{
|
|
delete xml;
|
|
xml = NULL;
|
|
download = true;
|
|
} // if xml not consistemt
|
|
} // if !download
|
|
|
|
if(download)
|
|
{
|
|
core::stringw error_message("");
|
|
|
|
HTTPRequest *download_req = new HTTPRequest("news.xml");
|
|
download_req->setAddonsURL("news.xml");
|
|
|
|
// Initialise the online portion of the addons manager.
|
|
if(UserConfigParams::logAddons())
|
|
Log::info("addons", "Downloading news.");
|
|
download_req->executeNow();
|
|
|
|
if(download_req->hadDownloadError())
|
|
{
|
|
// Assume that the server address is wrong. And retry
|
|
// with the default server address again (just in case
|
|
// that a redirect went wrong, or a wrong/incorrect
|
|
// address somehow made its way into the config file.
|
|
delete download_req;
|
|
|
|
// We need a new object, since the state of the old
|
|
// download request is now done.
|
|
download_req = new HTTPRequest("news.xml");
|
|
UserConfigParams::m_server_addons.revertToDefaults();
|
|
|
|
// make sure the new server address is actually used
|
|
download_req->setAddonsURL("news.xml");
|
|
download_req->executeNow();
|
|
|
|
if(download_req->hadDownloadError())
|
|
{
|
|
// This message must be translated dynamically in the main menu.
|
|
// If it would be translated here, it wouldn't be translated
|
|
// if the language is changed in the menu!
|
|
error_message = N_("Error downloading news: '%s'.");
|
|
const char *const curl_error = download_req->getDownloadErrorMessage();
|
|
error_message = StringUtils::insertValues(error_message, curl_error);
|
|
addons_manager->setErrorState();
|
|
me->setErrorMessage(error_message);
|
|
Log::error("news", core::stringc(error_message).c_str());
|
|
} // hadDownloadError
|
|
} // hadDownloadError
|
|
|
|
if(!download_req->hadDownloadError())
|
|
UserConfigParams::m_news_last_updated = StkTime::getTimeSinceEpoch();
|
|
delete download_req;
|
|
|
|
// No download error, update the last_updated time value, and delete
|
|
// the potentially loaded xml file
|
|
delete xml;
|
|
xml = NULL;
|
|
} // hadDownloadError
|
|
|
|
if(xml) delete xml;
|
|
xml = NULL;
|
|
|
|
// Process new.xml now.
|
|
if(file_manager->fileExists(xml_file))
|
|
{
|
|
xml = new XMLNode(xml_file);
|
|
me->checkRedirect(xml);
|
|
me->updateNews(xml, xml_file);
|
|
addons_manager->init(xml, me->m_force_refresh);
|
|
delete xml;
|
|
}
|
|
|
|
// We can't finish stk (esp. delete the file manager) before
|
|
// this part of the code is reached (since otherwise the file
|
|
// manager might be access after it was deleted).
|
|
me->setCanBeDeleted();
|
|
pthread_exit(NULL);
|
|
return 0; // prevent warning
|
|
} // downloadNews
|
|
|
|
// ---------------------------------------------------------------------------
|
|
/** Checks if a redirect is received, causing a new server to be used for
|
|
* downloading addons.
|
|
* \param xml XML data structure containing the redirect information.
|
|
*/
|
|
void NewsManager::checkRedirect(const XMLNode *xml)
|
|
{
|
|
std::string new_server;
|
|
int result = xml->get("redirect", &new_server);
|
|
if(result==1 && new_server!="")
|
|
{
|
|
if(UserConfigParams::logAddons())
|
|
{
|
|
Log::info("[Addons]", "Current server: '%s'\n [Addons] New server: '%s'",
|
|
UserConfigParams::m_server_addons.c_str(), new_server.c_str());
|
|
}
|
|
UserConfigParams::m_server_addons = new_server;
|
|
}
|
|
|
|
std::string hw_report_server;
|
|
if(xml->get("hw-report-server", &hw_report_server)==1 && hw_report_server.size()>0)
|
|
{
|
|
Log::info("hw report", "New server at '%s'.", hw_report_server.c_str());
|
|
UserConfigParams::m_server_hw_report = hw_report_server;
|
|
}
|
|
|
|
float polling;
|
|
if(xml->get("menu-polling-interval", &polling))
|
|
{
|
|
RequestManager::get()->setMenuPollingInterval(polling);
|
|
}
|
|
if(xml->get("game-polling-interval", &polling))
|
|
{
|
|
RequestManager::get()->setGamePollingInterval(polling);
|
|
}
|
|
|
|
} // checkRedirect
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Updates the 'news' string to be displayed in the main menu.
|
|
* \param xml The XML data from the news file.
|
|
* \param filename The filename of the news xml file. Only needed
|
|
* in case of an error (e.g. the file might be corrupted)
|
|
* - the file will be deleted so that on next start of stk it
|
|
* will be updated again.
|
|
*/
|
|
void NewsManager::updateNews(const XMLNode *xml, const std::string &filename)
|
|
{
|
|
|
|
m_all_news_messages = "";
|
|
const core::stringw message_divider=" +++ ";
|
|
// This function is also called in case of a reinit, so
|
|
// we have to delete existing news messages here first.
|
|
m_news.lock();
|
|
m_news.getData().clear();
|
|
m_news.unlock();
|
|
bool error = true;
|
|
int frequency=0;
|
|
if(xml->get("frequency", &frequency))
|
|
UserConfigParams::m_news_frequency = frequency;
|
|
|
|
for(unsigned int i=0; i<xml->getNumNodes(); i++)
|
|
{
|
|
const XMLNode *node = xml->getNode(i);
|
|
if(node->getName()!="message") continue;
|
|
core::stringw news;
|
|
node->get("content", &news);
|
|
int id=-1;
|
|
node->get("id", &id);
|
|
bool important=false;
|
|
node->get("important", &important);
|
|
|
|
std::string cond;
|
|
node->get("condition", &cond);
|
|
if(!conditionFulfilled(cond))
|
|
continue;
|
|
m_news.lock();
|
|
{
|
|
|
|
if(!important)
|
|
m_all_news_messages += m_all_news_messages.size()>0
|
|
? message_divider + news
|
|
: news;
|
|
else
|
|
// Define this if news messages should be removed
|
|
// after being shown a certain number of times.
|
|
{
|
|
NewsMessage n(news, id, important);
|
|
m_news.getData().push_back(n);
|
|
}
|
|
} // m_news.lock()
|
|
m_news.unlock();
|
|
|
|
error = false;
|
|
}
|
|
if(error)
|
|
{
|
|
// In case of an error (e.g. the file only contains
|
|
// an error message from the server), delete the file
|
|
// so that it is not read again (and this will force
|
|
// a new read on the next start, instead of waiting
|
|
// for some time).
|
|
file_manager->removeFile(filename);
|
|
NewsMessage n(_("Can't access stkaddons server..."), -1);
|
|
m_news.lock();
|
|
m_news.getData().push_back(n);
|
|
|
|
m_all_news_messages="";
|
|
m_news.unlock();
|
|
}
|
|
} // updateNews
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Add a news message. This is used to add error messages, e.g. for problems
|
|
* when downloading addons.
|
|
* \param s The news message to add.
|
|
*/
|
|
void NewsManager::addNewsMessage(const core::stringw &s)
|
|
{
|
|
NewsMessage n(s, -1);
|
|
m_news.lock();
|
|
m_news.getData().push_back(n);
|
|
m_news.unlock();
|
|
} // addNewsMessage
|
|
// ----------------------------------------------------------------------------
|
|
/** Returns the important message with the smallest id that has not been
|
|
* shown, or NULL if no important (not shown before) message exists atm. The
|
|
* user config is updated to store the last important message id shown.
|
|
*/
|
|
const core::stringw NewsManager::getImportantMessage()
|
|
{
|
|
int index = -1;
|
|
m_news.lock();
|
|
for(unsigned int i=0; i<m_news.getData().size(); i++)
|
|
{
|
|
const NewsMessage &m = m_news.getData()[i];
|
|
//
|
|
if(m.isImportant() &&
|
|
m.getMessageId()>UserConfigParams::m_last_important_message_id &&
|
|
(index == -1 ||
|
|
m.getMessageId() < m_news.getData()[index].getMessageId() ) )
|
|
{
|
|
index = i;
|
|
} // if new unshown important message with smaller message id
|
|
}
|
|
core::stringw message("");
|
|
if(index>=0)
|
|
{
|
|
const NewsMessage &m = m_news.getData()[index];
|
|
message = m.getNews();
|
|
UserConfigParams::m_last_important_message_id = m.getMessageId();
|
|
|
|
}
|
|
m_news.unlock();
|
|
|
|
return message;
|
|
} // getImportantMessage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Returns the next loaded news message. It will 'wrap around', i.e.
|
|
* if there is only one message it will be returned over and over again.
|
|
* To be used by the the main menu to get the next news message after
|
|
* one message was scrolled off screen.
|
|
*/
|
|
const core::stringw NewsManager::getNextNewsMessage()
|
|
{
|
|
// Only display error message in case of a problem.
|
|
if(m_error_message.getAtomic().size()>0)
|
|
return _(m_error_message.getAtomic().c_str());
|
|
|
|
m_news.lock();
|
|
if(m_all_news_messages.size()>0)
|
|
{
|
|
// Copy the news message while it is locked.
|
|
core::stringw anm = m_all_news_messages;
|
|
m_news.unlock();
|
|
return anm;
|
|
}
|
|
|
|
if(m_news.getData().size()==0)
|
|
{
|
|
// Lock
|
|
m_news.unlock();
|
|
return "";
|
|
}
|
|
|
|
core::stringw m("");
|
|
{
|
|
m_current_news_message++;
|
|
if(m_current_news_message >= (int)m_news.getData().size())
|
|
m_current_news_message = 0;
|
|
|
|
m = m_news.getData()[m_current_news_message].getNews();
|
|
}
|
|
m_news.unlock();
|
|
return _(m.c_str());
|
|
} // getNextNewsMessage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Checks if the given condition list are all fulfilled.
|
|
* The conditions must be separated by ";", and each condition
|
|
* must be of the form "type comp version".
|
|
* Type must be 'stkversion'
|
|
* comp must be one of "<", "=", ">"
|
|
* version must be a valid STK version string
|
|
* \param cond The list of conditions
|
|
* \return True if all conditions are true.
|
|
*/
|
|
bool NewsManager::conditionFulfilled(const std::string &cond)
|
|
{
|
|
std::vector<std::string> cond_list;
|
|
cond_list = StringUtils::split(cond, ';');
|
|
for(unsigned int i=0; i<cond_list.size(); i++)
|
|
{
|
|
std::vector<std::string> cond = StringUtils::split(cond_list[i],' ');
|
|
if(cond.size()!=3)
|
|
{
|
|
Log::warn("NewsManager", "Invalid condition '%s' - assumed to "
|
|
"be true.", cond_list[i].c_str());
|
|
continue;
|
|
}
|
|
// Check for stkversion comparisons
|
|
// ================================
|
|
if(cond[0]=="stkversion")
|
|
{
|
|
int news_version = StringUtils::versionToInt(cond[2]);
|
|
int stk_version = StringUtils::versionToInt(STK_VERSION);
|
|
if(cond[1]=="=")
|
|
{
|
|
if(stk_version!=news_version) return false;
|
|
continue;
|
|
}
|
|
if(cond[1]=="<")
|
|
{
|
|
if(stk_version>=news_version) return false;
|
|
continue;
|
|
}
|
|
if(cond[1]==">")
|
|
{
|
|
if(stk_version<=news_version) return false;
|
|
continue;
|
|
}
|
|
Log::warn("NewsManager", "Invalid comparison in condition '%s' - "
|
|
"assumed true.", cond_list[i].c_str());
|
|
}
|
|
// Check for addons not installed
|
|
// ==============================
|
|
else if(cond[1]=="not" && cond[2]=="installed")
|
|
{
|
|
// The addons_manager cannot be accessed, since it's
|
|
// being initialised after the news manager. So a simple
|
|
// test is made to see if the directory exists. It is
|
|
// necessary to check for karts and tracks separately,
|
|
// since it's not possible to know if the addons is
|
|
// a kart or a track.
|
|
const std::string dir=file_manager->getAddonsDir();
|
|
if(file_manager->fileExists(dir+"/karts/"+cond[0]))
|
|
return false;
|
|
if(file_manager->fileExists(dir+"/tracks/"+cond[0]))
|
|
return false;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
Log::warn("NewsManager", "Invalid condition '%s' - assumed to "
|
|
"be true.", cond_list[i].c_str());
|
|
continue;
|
|
}
|
|
|
|
} // for i < cond_list
|
|
return true;
|
|
} // conditionFulfilled
|
|
|
|
// ----------------------------------------------------------------------------
|