1) Moved news message handling into a separate object.

2) Added prioritised request list to network manager.
3) Fixed ticket 217 (crash at shutdown), which happens if 
   the background thread downloading the icons is still
   active when STK is quit (and then accessing managers
   that have been deleted).
4) Handled partial downloads correctly: files are now
   downloaded with ".part" appended to the filename,
   and only upon a successful download renamed to 
   the original name.


git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/main/trunk@8552 178a84e3-b1eb-0310-8ba1-8eac791a3b58
This commit is contained in:
hikerstk 2011-05-12 23:51:00 +00:00
parent 11e925f867
commit 498a0a104e
18 changed files with 969 additions and 783 deletions

View File

@ -18,6 +18,10 @@ supertuxkart_SOURCES = \
addons/addons_manager.hpp \
addons/network_http.cpp \
addons/network_http.hpp \
addons/news_manager.cpp \
addons/news_manager.hpp \
addons/request.cpp \
addons/request.hpp \
addons/zip.cpp \
addons/zip.hpp \
animations/animation_base.hpp \

View File

@ -28,6 +28,7 @@
#include <vector>
#include "addons/network_http.hpp"
#include "addons/request.hpp"
#include "addons/zip.hpp"
#include "graphics/irr_driver.hpp"
#include "io/file_manager.hpp"
@ -45,18 +46,16 @@ AddonsManager* addons_manager = 0;
* later from a separate thread in network_http (once network_http is setup).
*/
AddonsManager::AddonsManager() : m_addons_list(std::vector<Addon>() ),
m_icon_queue(std::vector<std::string>() ),
m_state(STATE_INIT)
{
m_file_installed = file_manager->getAddonsFile("addons_installed.xml");
} // AddonsManager
// ----------------------------------------------------------------------------
/** This initialises the online portion of the addons manager. It uses the
* downloaded list of available addons. This is called by network_http before
* it goes into command-receiving mode, so we can't use any asynchronous calls
* here (though this is being called from a separate thread anyway, so the
* here (though this is being called from a separate thread , so the
* main GUI is not blocked anyway). This function will update the state
* variable
*/
@ -131,82 +130,36 @@ void AddonsManager::initOnline(const XMLNode *xml)
m_state.set(STATE_READY);
pthread_t id;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&id, &attr, downloadIcons, this);
downloadIcons();
} // initOnline
// ----------------------------------------------------------------------------
/** A separate thread to download all necessary icons (i.e. icons that are
* either missing or have been updated since they were downloaded).
/** Download all necessary icons (i.e. icons that are either missing or have
* been updated since they were downloaded).
*/
void *AddonsManager::downloadIcons(void *obj)
void *AddonsManager::downloadIcons()
{
AddonsManager *me=(AddonsManager*)obj;
me->m_icon_queue.lock();
me->m_icon_queue.getData().clear();
me->m_icon_queue.unlock();
for(unsigned int i=0; i<me->m_addons_list.getData().size(); i++)
for(unsigned int i=0; i<m_addons_list.getData().size(); i++)
{
const Addon &addon = me->m_addons_list.getData()[i];
Addon &addon = m_addons_list.getData()[i];
const std::string &icon = addon.getIconBasename();
if(addon.iconNeedsUpdate())
const std::string &icon_full
= file_manager->getAddonsFile("icons/"+icon);
if(addon.iconNeedsUpdate() ||
!file_manager->fileExists(icon_full))
{
me->m_icon_queue.lock();
me->m_icon_queue.getData().push_back(addon.getId());
me->m_icon_queue.unlock();
continue;
}
std::string icon_path=file_manager->getAddonsFile("icons/"+icon);
if(!file_manager->fileExists(icon_path))
{
me->m_icon_queue.lock();
me->m_icon_queue.getData().push_back(addon.getId());
me->m_icon_queue.unlock();
continue;
}
me->m_addons_list.getData()[i].setIconReady();
} // for i<m_addons_list.size()
me->m_icon_queue.lock();
int count = me->m_icon_queue.getData().size();
me->m_icon_queue.unlock();
while(count!=0)
{
me->m_icon_queue.lock();
std::vector<std::string> &icon_queue = me->m_icon_queue.getData();
unsigned int indx = me->getAddonIndex(icon_queue[0]);
Addon *addon = &(me->m_addons_list.getData()[indx]);
icon_queue.erase(icon_queue.begin());
count --;
me->m_icon_queue.unlock();
const std::string &url = addon->getIconURL();
const std::string &icon = addon->getIconBasename();
std::string save = "icons/"+icon;
if(network_http->downloadFileSynchron(url, save))
{
addon->setIconReady();
const std::string &url = addon.getIconURL();
const std::string &icon = addon.getIconBasename();
std::string save = "icons/"+icon;
Request *r = network_http->downloadFileAsynchron(url, save,
/*priority*/1,
/*manage_mem*/false);
r->setAddonIconNotification(&addon);
}
else
{
printf("[addons] Could not download icon '%s' from '%s'.\n",
icon.c_str(), url.c_str());
}
} // while count!=0
m_addons_list.getData()[i].setIconReady();
} // for i<m_addons_list.size()
// Now check if we have any entries in m_addons_list, that is not
// in the online list anymore
for(unsigned int i=0; i<me->m_addons_list.getData().size(); i++)
{
if(!me->m_addons_list.getData()[i].iconReady())
printf("[addons] No icon for '%s'.\n",
me->m_addons_list.getData()[i].getId().c_str());
}
me->saveInstalled();
return NULL;
} // downloadIcons
@ -221,9 +174,7 @@ bool AddonsManager::onlineReady()
} // onlineReady
// ----------------------------------------------------------------------------
/** Loads the installed addons from .../addons/addons_installed.xml. This is
* called before network_http is constructed (so no need to protect
* m_addons_list with a mutex).
/** Loads the installed addons from .../addons/addons_installed.xml.
*/
void AddonsManager::loadInstalledAddons()
{

View File

@ -42,10 +42,6 @@ private:
/** List of loaded icons. */
std::vector<std::string> m_icon_list;
/** Queue of icons to download. This queue is used by the
* GUI to increase priority of icons that are needed now. */
Synchronised<std::vector<std::string> > m_icon_queue;
/** Which state the addons manager is:
* INIT: Waiting to download the list of addons.
* READY: List is downloaded, manager is ready.
@ -54,36 +50,27 @@ private:
// Synchronise the state between threads (e.g. GUI and update thread)
Synchronised<STATE_TYPE> m_state;
void saveInstalled(const std::string &type="");
void loadInstalledAddons();
static void *downloadIcons(void *obj);
void saveInstalled(const std::string &type="");
void loadInstalledAddons();
void *downloadIcons();
public:
AddonsManager();
void initOnline(const XMLNode *xml);
bool onlineReady();
AddonsManager();
void initOnline(const XMLNode *xml);
bool onlineReady();
/** Marks addon as not being available. */
void setErrorState() { m_state.set(STATE_ERROR); }
void setErrorState() { m_state.set(STATE_ERROR); }
const Addon* getAddon(const std::string &id) const;
int getAddonIndex(const std::string &id) const;
bool install(const Addon &addon);
bool uninstall(const Addon &addon);
// ------------------------------------------------------------------------
/** Returns the list of addons (installed and uninstalled). */
unsigned int getNumAddons() const
{
return m_addons_list.getData().size();
}
unsigned int getNumAddons() const { return m_addons_list.getData().size();}
// ------------------------------------------------------------------------
/** Returns the i-th addons. */
const Addon& getAddon(unsigned int i) { return m_addons_list.getData()[i];}
const Addon* getAddon(const std::string &id) const;
int getAddonIndex(const std::string &id) const;
bool install(const Addon &addon);
/** Uninstall the selected addon. This method will remove all the
* directory of the addon.*/
bool uninstall(const Addon &addon);
/** Get the install state (if it is the download, unzip...)*/
const std::string& getDownloadStateAsStr() const;
};

View File

@ -31,7 +31,8 @@
# include <math.h>
#endif
#include "addons/news_manager.hpp"
#include "addons/request.hpp"
#include "config/user_config.hpp"
#include "io/file_manager.hpp"
#include "states_screens/addons_screen.hpp"
@ -47,7 +48,7 @@
# include <unistd.h>
#endif
NetworkHttp *network_http;
NetworkHttp *network_http=NULL;
// ----------------------------------------------------------------------------
/** Create a thread that handles all network functions independent of the
@ -61,29 +62,33 @@ NetworkHttp *network_http;
* since the user might trigger another save in the menu (potentially
* ending up with an corrupted file).
*/
NetworkHttp::NetworkHttp() : m_news(std::vector<NewsMessage>()),
m_progress(-1.0f), m_abort(false)
NetworkHttp::NetworkHttp() : m_abort(false),
m_all_requests(std::vector<Request*>())
{
m_current_news_message = -1;
// Don't even start the network threads if networking is disabled.
if(UserConfigParams::m_internet_status!=NetworkHttp::IPERM_ALLOWED)
if(UserConfigParams::m_internet_status!=NetworkHttp::IPERM_ALLOWED )
return;
pthread_mutex_init(&m_mutex_command, NULL);
pthread_cond_init(&m_cond_command, NULL);
curl_global_init(CURL_GLOBAL_ALL);
m_curl_session = curl_easy_init();
// Abort if curl error occurred.
if(!m_curl_session)
return;
// Initialise the variables for cancelling the network thread.
pthread_mutex_init(&m_mutex_quit, NULL);
pthread_cond_init(&m_cond_quit, NULL);
pthread_cond_init(&m_cond_request, NULL);
// Since there are no threads at this stage, just init
// m_command without mutex.
m_command = HC_SLEEP;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
m_thread_id = new pthread_t();
int error=pthread_create(m_thread_id, &attr, &NetworkHttp::mainLoop, this);
Request *request = new Request(Request::HC_INIT, 9999);
m_all_requests.lock();
m_all_requests.getData().push_back(request);
m_all_requests.unlock();
int error = pthread_create(m_thread_id, &attr,
&NetworkHttp::mainLoop, this);
if(error)
{
delete m_thread_id;
@ -103,94 +108,64 @@ void *NetworkHttp::mainLoop(void *obj)
{
NetworkHttp *me=(NetworkHttp*)obj;
// 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.
bool download = UserConfigParams::m_news_last_updated==0 ||
UserConfigParams::m_news_last_updated
+UserConfigParams::m_news_frequency
< Time::getTimeSinceEpoch();
if(!download)
{
// If there is no old news message file, force a new download
std::string xml_file = file_manager->getAddonsFile("news.xml");
if(!file_manager->fileExists(xml_file))
download=true;
}
// Initialise the online portion of the addons manager.
if(download && UserConfigParams::logAddons())
printf("[addons] Downloading list.\n");
if(!download || me->downloadFileSynchron("news.xml"))
{
std::string xml_file = file_manager->getAddonsFile("news.xml");
if(download)
UserConfigParams::m_news_last_updated = Time::getTimeSinceEpoch();
const XMLNode *xml = new XMLNode(xml_file);
me->checkRedirect(xml);
me->updateNews(xml, xml_file);
#ifdef ADDONS_MANAGER
me->loadAddonsList(xml, xml_file);
if(UserConfigParams::logAddons())
printf("[addons] Addons manager list downloaded\n");
#endif
}
else
{
#ifdef ADDONS_MANAGER
addons_manager->setErrorState();
if(UserConfigParams::logAddons())
printf("[addons] Can't download addons list.\n");
#endif
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
// Wait in the main loop till a command is received
pthread_mutex_lock(&me->m_mutex_command);
// Handle the case that STK is cancelled before the while loop
// is entered. If this would happen, this thread hangs in
// pthread_cond_wait (since cond_signal was done before the wait),
// and stk hangs since the thread can't join.
if(me->m_command==HC_QUIT)
me->m_all_requests.lock();
while(me->m_all_requests.getData().size() == 0 ||
me->m_all_requests.getData()[0]->getCommand() != Request::HC_QUIT )
{
return NULL;
}
while(1)
{
pthread_cond_wait(&me->m_cond_command, &me->m_mutex_command);
switch(me->m_command)
bool empty = me->m_all_requests.getData().size()==0;
// Wait in cond_wait for a request to arrive. The 'while' is necessary
// since "spurious wakeups from the pthread_cond_wait ... may occur"
// (pthread_cond_wait man page)!
while(empty)
{
case HC_QUIT:
pthread_exit(NULL);
break;
case HC_SLEEP:
case HC_INIT:
break;
case HC_NEWS:
assert(false);
break;
case HC_DOWNLOAD_FILE:
me->downloadFileInternal(me->m_url, me->m_save_filename,
/*is_asynchron*/true );
} // switch(m_command)
me->m_command = HC_SLEEP;
} // while 1
pthread_mutex_unlock(&me->m_mutex_command);
if(UserConfigParams::logAddons())
printf("[addons] No request, sleeping.\n");
// Signal that we are quitting properly.
if(UserConfigParams::logAddons())
printf("[addons] Signaling that network thread is quitting.\n");
pthread_mutex_lock(&me->m_mutex_quit);
pthread_cond_broadcast(&me->m_cond_quit);
pthread_mutex_unlock(&me->m_mutex_quit);
if(UserConfigParams::logAddons())
printf("[addons] Network thread is quitting.\n");
pthread_cond_wait(&me->m_cond_request,
me->m_all_requests.getMutex());
empty = me->m_all_requests.getData().size()==0;
}
// Get the first (=highest priority) request and remove it from the
// queue. Only this code actually removes requests from the queue,
// so it is certain that even
Request *request = me->m_all_requests.getData()[0];
me->m_all_requests.getData().erase(me->m_all_requests.getData().begin());
me->m_all_requests.unlock();
if(UserConfigParams::logAddons())
{
if(request->getCommand()==Request::HC_DOWNLOAD_FILE)
printf("[addons] Executing download '%s' to '%s' priority %d.\n",
request->getURL().c_str(), request->getSavePath().c_str(),
request->getPriority());
else
printf("[addons] Executing command '%d' priority %d.\n",
request->getCommand(), request->getPriority());
}
if(request->getCommand()==Request::HC_QUIT)
break;
switch(request->getCommand())
{
case Request::HC_QUIT: break;
case Request::HC_INIT: me->init(); break;
case Request::HC_NEWS: assert(false); break;
case Request::HC_DOWNLOAD_FILE:
me->downloadFileInternal(request);
break;
} // switch(request->getCommand())
if(request->manageMemory())
delete request;
// We have to lock here so that we can access m_all_requests
// in the while condition at the top of the loop
me->m_all_requests.lock();
} // while !quit
if(UserConfigParams::logAddons())
printf("[addons] Network thread terminating.\n");
me->m_thread_id = 0;
return NULL;
} // mainLoop
@ -211,138 +186,86 @@ NetworkHttp::~NetworkHttp()
cancelDownload();
if(UserConfigParams::logAddons())
printf("[addons] Trying to lock command mutex.\n");
pthread_mutex_lock(&m_mutex_command);
m_all_requests.lock();
{
m_command=HC_QUIT;
pthread_cond_signal(&m_cond_command);
Request *r = new Request(Request::HC_QUIT, 999);
// Insert the Quit request as the first element of the queue.
m_all_requests.getData().insert(m_all_requests.getData().begin(), r);
pthread_cond_signal(&m_cond_request);
}
pthread_mutex_unlock(&m_mutex_command);
m_all_requests.unlock();
if(UserConfigParams::logAddons())
printf("[addons] Command mutex unlocked.\n");
if(m_thread_id)
{
if(UserConfigParams::logAddons())
printf("[addons] Trying to stop network thread.\n");
pthread_mutex_lock(&m_mutex_quit);
timespec timeout;
timeout.tv_nsec = 0;
timeout.tv_sec = 1;
int error = pthread_cond_timedwait(&m_cond_quit, &m_mutex_quit,
&timeout);
pthread_mutex_unlock(&m_mutex_quit);
if(error==ETIMEDOUT)
{
printf("[addons] Timeout waiting for joining. Cancelling.\n");
int e = pthread_cancel(*m_thread_id);
printf("[addons] Cancelled network thread. Return code: %d\n", e);
}
else
{
if(UserConfigParams::logAddons())
printf("[addons] Trying to cancel network thread.\n");
pthread_join(*m_thread_id, NULL);
if(UserConfigParams::logAddons())
printf("[addons] Network thread joined.\n");
}
printf("[addons] Cancelling network thread.\n");
int e = pthread_cancel(*m_thread_id);
printf("[addons] Cancelled network thread. Return code: %d\n", e);
delete m_thread_id;
}
pthread_mutex_destroy(&m_mutex_command);
pthread_cond_destroy(&m_cond_command);
pthread_mutex_destroy(&m_mutex_quit);
pthread_cond_destroy(&m_cond_quit);
pthread_cond_destroy(&m_cond_request);
curl_easy_cleanup(m_curl_session);
m_curl_session = NULL;
curl_global_cleanup();
} // ~NetworkHttp
// ---------------------------------------------------------------------------
/** 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.
/** Initialises the online part of the network manager. It downloads the
* news.xml file from the server (if the frequency of downloads makes this
* necessary), and (again if necessary) the addons.xml file.
* \return 0 if an error happened and no online connection will be available,
* 1 otherwise.
*/
void NetworkHttp::checkRedirect(const XMLNode *xml)
int NetworkHttp::init()
{
std::string new_server;
int result = xml->get("redirect", &new_server);
if(result==1 && new_server!="")
// 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.
bool download = UserConfigParams::m_news_last_updated==0 ||
UserConfigParams::m_news_last_updated
+UserConfigParams::m_news_frequency
< Time::getTimeSinceEpoch();
if(!download)
{
if(UserConfigParams::logAddons())
{
std::cout << "[Addons] Current server: "
<< (std::string)UserConfigParams::m_server_addons
<< std::endl
<< "[Addons] New server: " << new_server << std::endl;
}
UserConfigParams::m_server_addons = new_server;
// If there is no old news message file, force a new download
std::string xml_file = file_manager->getAddonsFile("news.xml");
if(!file_manager->fileExists(xml_file))
download=true;
}
} // 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 NetworkHttp::updateNews(const XMLNode *xml, const std::string &filename)
{
bool error = true;
int frequency=0;
if(xml->get("frequency", &frequency))
UserConfigParams::m_news_frequency = frequency;
// Initialise the online portion of the addons manager.
if(download && UserConfigParams::logAddons())
printf("[addons] Downloading list.\n");
for(unsigned int i=0; i<xml->getNumNodes(); i++)
Request r(Request::HC_DOWNLOAD_FILE, 9999, false,
"news.xml", "news.xml");
if(!download || downloadFileInternal(&r))
{
const XMLNode *node = xml->getNode(i);
if(node->getName()!="message") continue;
std::string news;
node->get("content", &news);
int id=-1;
node->get("id", &id);
std::string cond;
node->get("condition", &cond);
if(!conditionFulfilled(cond))
continue;
m_news.lock();
{
// Define this if news messages should be removed
// after being shown a certain number of times.
#undef NEWS_MESSAGE_REMOVAL
#ifdef NEWS_MESSAGE_REMOVAL
// Only add the news if it's not supposed to be ignored.
if(id>UserConfigParams::m_ignore_message_id)
std::string xml_file = file_manager->getAddonsFile("news.xml");
if(download)
UserConfigParams::m_news_last_updated = Time::getTimeSinceEpoch();
const XMLNode *xml = new XMLNode(xml_file);
news_manager->init();
#ifdef ADDONS_MANAGER
loadAddonsList(xml, xml_file);
#endif
{
NewsMessage n(core::stringw(news.c_str()), id);
m_news.getData().push_back(n);
}
}
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_news.unlock();
}
#ifdef NEWS_MESSAGE_REMOVAL
else
updateMessageDisplayCount();
{
#ifdef ADDONS_MANAGER
addons_manager->setErrorState();
if(UserConfigParams::logAddons())
printf("[addons] Can't download addons list.\n");
return 0;
#endif
} // updateNews
}
return 1;
} // init
// ----------------------------------------------------------------------------
/** Checks the last modified date and if necessary updates the
@ -368,10 +291,7 @@ void NetworkHttp::loadAddonsList(const XMLNode *xml,
if(addon_list_url.size()==0)
{
file_manager->removeFile(filename);
NewsMessage n("Can't access stkaddons server...", -1);
m_news.lock();
m_news.getData().push_back(n);
m_news.unlock();
news_manager->addNewsMessage(_("Can't access stkaddons server..."));
return;
}
@ -383,7 +303,9 @@ void NetworkHttp::loadAddonsList(const XMLNode *xml,
download = true;
}
if(!download || downloadFileSynchron(addon_list_url, "addons.xml"))
Request r(Request::HC_DOWNLOAD_FILE, 9999, false,
addon_list_url, "addons.xml");
if(!download || downloadFileInternal(&r))
{
std::string xml_file = file_manager->getAddonsFile("addons.xml");
if(download)
@ -399,318 +321,66 @@ void NetworkHttp::loadAddonsList(const XMLNode *xml,
} // loadAddonsList
// ----------------------------------------------------------------------------
/** 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.
/** Download a file. The file name isn't absolute, the server in the config
* will be added to file. The file is downloaded with a ".part" extention,
* and the file is renamed after it was downloaded successfully.
* \param request The request object containing the url and the path where
* the file is saved to.
*/
const core::stringw NetworkHttp::getNextNewsMessage()
bool NetworkHttp::downloadFileInternal(Request *request)
{
if(m_news.getData().size()==0)
return "";
std::string full_save =
file_manager->getAddonsFile(request->getSavePath());
core::stringw m("");
m_news.lock();
if(UserConfigParams::logAddons())
printf("[addons] Downloading '%s' as '%s').\n",
request->getURL().c_str(), request->getSavePath().c_str());
std::string full_url = request->getURL();
if(full_url.substr(0, 5)!="http:" && full_url.substr(0, 4)!="ftp:")
full_url = (std::string)UserConfigParams::m_server_addons
+ "/" + full_url;
curl_easy_setopt(m_curl_session, CURLOPT_URL, full_url.c_str());
std::string uagent = (std::string)"SuperTuxKart/" + STK_VERSION;
curl_easy_setopt(m_curl_session, CURLOPT_USERAGENT, uagent.c_str());
curl_easy_setopt(m_curl_session, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(m_curl_session, CURLOPT_PROGRESSDATA, request);
FILE * fout = fopen((full_save+".part").c_str(), "wb");
//from and out
curl_easy_setopt(m_curl_session, CURLOPT_WRITEDATA, fout );
curl_easy_setopt(m_curl_session, CURLOPT_WRITEFUNCTION, fwrite);
curl_easy_setopt(m_curl_session, CURLOPT_PROGRESSFUNCTION,
&NetworkHttp::progressDownload);
curl_easy_setopt(m_curl_session, CURLOPT_NOPROGRESS, 0);
int status = curl_easy_perform(m_curl_session);
fclose(fout);
if(status==CURLE_OK)
{
// Check if we have a message that was finished being
// displayed --> increase display count.
if(m_current_news_message>-1)
if(UserConfigParams::logAddons())
printf("[addons] Download successful.\n");
int ret = rename((full_save+".part").c_str(), full_save.c_str());
// In case of an error, set the status to indicate this
if(ret!=0)
{
#ifdef NEWS_MESSAGE_REMOVAL
NewsMessage &n = m_news.getData()[m_current_news_message];
n.increaseDisplayCount();
#endif
// If the message is being displayed often enough,
// ignore it from now on.
#ifdef NEWS_MESSAGE_REMOVAL
if(n.getDisplayCount()>stk_config->m_max_display_news)
{
// Messages have sequential numbers, so we only store
// the latest message id (which is the current one)
UserConfigParams::m_ignore_message_id = n.getMessageId();
m_news.getData().erase(m_news.getData().begin()
+m_current_news_message );
}
#endif
updateUserConfigFile();
//
if(m_news.getData().size()==0)
{
m_news.unlock();
return "";
}
}
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;
} // getNextNewsMessage
// ----------------------------------------------------------------------------
/** Saves the information about which message was being displayed how often
* to the user config file. It dnoes not actually save the user config
* file, this is left to the main program (user config is saved at
* the exit of the program).
* Note that this function assumes that m_news is already locked!
*/
void NetworkHttp::updateUserConfigFile() const
{
#ifdef NEWS_MESSAGE_REMOVAL
std::ostringstream o;
for(unsigned int i=0; i<m_news.getData().size(); i++)
{
const NewsMessage &n=m_news.getData()[i];
o << n.getMessageId() << ":"
<< n.getDisplayCount() << " ";
}
UserConfigParams::m_display_count = o.str();
#else
// Always set them to be empty to avoid any
// invalid data that might create a problem later.
UserConfigParams::m_display_count = "";
UserConfigParams::m_ignore_message_id = -1;
#endif
} // updateUserConfigFile
// ----------------------------------------------------------------------------
/** Checks if the given condition list are all fulfilled.
* The conditions must be seperated 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 NetworkHttp::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)
{
printf("Invalid condition '%s' - assumed to be true.\n",
cond_list[i].c_str());
continue;
}
if(cond[0]=="stkversion")
{
int news_version = versionToInt(cond[2]);
int stk_version = 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;
}
printf("Invalid comparison in condition '%s' - assumed true.\n",
cond_list[i].c_str());
if(UserConfigParams::logAddons())
printf("[addons] Could not rename downloaded file!\n");
status=CURLE_WRITE_ERROR;
}
else
{
printf("Invalid condition '%s' - assumed to be true.\n",
cond_list[i].c_str());
continue;
}
} // for i < cond_list
return true;
} // conditionFulfilled
// ----------------------------------------------------------------------------
/** Converts a version string (in the form of 'X.Y.Za-rcU' into an
* integer number.
* \param s The version string to convert.
*/
int NetworkHttp::versionToInt(const std::string &version_string)
{
// Special case: SVN
if(version_string=="SVN" || version_string=="svn")
// SVN version will be version 99.99.99i-rcJ
return 1000000*99
+ 10000*99
+ 100*99
+ 10* 9
+ 9;
std::string s=version_string;
// To guarantee that a release gets a higher version number than
// a release candidate, we assign a 'release_candidate' number
// of 9 to versions which are not a RC. We assert that any RC
// is less than 9 to guarantee the ordering.
int release_candidate=9;
if(sscanf(s.substr(s.length()-4, 4).c_str(), "-rc%d",
&release_candidate)==1)
{
s = s.substr(0, s.length()-4);
// Otherwise a RC can get a higher version number than
// the corresponding release! If this should ever get
// triggered, multiply all scaling factors above and
// below by 10, to get two digits for RC numbers.
assert(release_candidate<9);
request->notifyAddon();
}
int very_minor=0;
if(s[s.size()-1]>='a' && s[s.size()-1]<='z')
else
{
very_minor = s[s.size()-1]-'a'+1;
s = s.substr(0, s.size()-1);
printf("[addons] Problems downloading file - return code %d.\n",
status);
}
std::vector<std::string> l = StringUtils::split(s, '.');
while(l.size()<3)
l.push_back(0);
int version = 1000000*atoi(l[0].c_str())
+ 10000*atoi(l[1].c_str())
+ 100*atoi(l[2].c_str())
+ 10*very_minor
+ release_candidate;
if(version<=0)
printf("Invalid version string '%s'.\n", s.c_str());
return version;
} // versionToInt
// ----------------------------------------------------------------------------
/** Reads the information about which message was dislpayed how often from
* the user config file.
*/
void NetworkHttp::updateMessageDisplayCount()
{
#ifdef NEWS_MESSAGE_REMOVAL
m_news.lock();
std::vector<std::string> pairs =
StringUtils::split(UserConfigParams::m_display_count,' ');
for(unsigned int i=0; i<pairs.size(); i++)
{
std::vector<std::string> v = StringUtils::split(pairs[i], ':');
int id, count;
StringUtils::fromString(v[0], id);
StringUtils::fromString(v[1], count);
// Search all downloaded messages for this id.
for(unsigned int j=0; j<m_news.getData().size(); j++)
{
if(m_news.getData()[j].getMessageId()!=id)
continue;
m_news.getData()[j].setDisplayCount(count);
if(count>stk_config->m_max_display_news)
m_news.getData().erase(m_news.getData().begin()+j);
break;
} // for j <m_news.getData().size()
}
m_news.unlock();
#endif
} // updateMessageDisplayCount
// ----------------------------------------------------------------------------
size_t NetworkHttp::writeStr(char ptr [], size_t size, size_t nb_char,
std::string * stream)
{
*stream = std::string(ptr);
//needed, otherwise, the download failed
return nb_char;
} // writeStr
// ----------------------------------------------------------------------------
std::string NetworkHttp::downloadToStrInternal(std::string url)
{
m_abort.set(false);
CURL *session = curl_easy_init();
std::string full_url = (std::string)UserConfigParams::m_server_addons
+ "/" + url;
curl_easy_setopt(session, CURLOPT_URL, full_url.c_str());
std::string uagent = (std::string)"SuperTuxKart/" + STK_VERSION;
curl_easy_setopt(session, CURLOPT_USERAGENT, uagent.c_str());
curl_easy_setopt(session, CURLOPT_FOLLOWLOCATION, 1);
std::string fout;
//from and out
curl_easy_setopt(session, CURLOPT_WRITEDATA, &fout );
curl_easy_setopt(session, CURLOPT_WRITEFUNCTION, &NetworkHttp::writeStr);
int success = curl_easy_perform(session);
//stop curl
curl_easy_cleanup(session);
if (success == 0) return fout;
else return "";
} // downloadToStrInternal
// ----------------------------------------------------------------------------
/** Download a file. The file name isn't absolute, the server in the config
* will be added to file.
* \param progress_data is used to have the state of the download (in %)
*/
bool NetworkHttp::downloadFileInternal(const std::string &url,
const std::string &save_filename,
bool is_asynchron)
{
if(UserConfigParams::logAddons())
printf("[addons] Downloading '%s' as '%s').\n", url.c_str(),
file_manager->getAddonsFile(save_filename).c_str());
CURL *session = curl_easy_init();
std::string full_url = url;
if(url.substr(0, 5)!="http:" && url.substr(0, 4)!="ftp:")
full_url = (std::string)UserConfigParams::m_server_addons
+ "/" + url;
curl_easy_setopt(session, CURLOPT_URL, full_url.c_str());
std::string uagent = (std::string)"SuperTuxKart/" + STK_VERSION;
curl_easy_setopt(session, CURLOPT_USERAGENT, uagent.c_str());
curl_easy_setopt(session, CURLOPT_FOLLOWLOCATION, 1);
FILE * fout = fopen(file_manager->getAddonsFile(save_filename).c_str(),
"wb");
//from and out
curl_easy_setopt(session, CURLOPT_WRITEDATA, fout );
curl_easy_setopt(session, CURLOPT_WRITEFUNCTION, fwrite);
// FIXME: if a network problem prevent the first 'list' download
// to finish, the thread can not be cancelled.
// If we disable this test it works as expected, but I am not sure
// if there are any side effects if synchron downloads use the
// progress bar as well.
if(is_asynchron)
{
curl_easy_setopt(session, CURLOPT_PROGRESSFUNCTION,
&NetworkHttp::progressDownload);
// Necessary, oyherwise the progress function doesn't work
curl_easy_setopt(session, CURLOPT_NOPROGRESS, 0);
}
int success = curl_easy_perform(session);
//close the file where we downloaded the content
fclose(fout);
//stop curl
curl_easy_cleanup(session);
if(is_asynchron)
m_progress.set( (success==CURLE_OK) ? 1.0f : -1.0f );
return success==CURLE_OK;
request->setProgress( (status==CURLE_OK) ? 1.0f : -1.0f );
return status==CURLE_OK;
} // downloadFileInternal
// ----------------------------------------------------------------------------
@ -725,25 +395,6 @@ void NetworkHttp::cancelDownload()
m_abort.set(true);
} // cancelDownload
// ----------------------------------------------------------------------------
/** External interface to download a file synchronously, i.e. it will only
* return once the download is complete.
* \param url The file from the server to download.
* \param save The name to save the downloaded file under. Defaults to
* the name given in file.
*/
bool NetworkHttp::downloadFileSynchron(const std::string &url,
const std::string &save)
{
const std::string &save_filename = (save!="") ? save : url;
if(UserConfigParams::logAddons())
printf("[addons] Download synchron '%s' as '%s'.\n",
url.c_str(), save_filename.c_str());
return downloadFileInternal(url, save_filename,
/*is_asynchron*/false);
} // downloadFileSynchron
// ----------------------------------------------------------------------------
/** External interface to download a file asynchronously. This will wake up
* the thread and schedule it to download the file. The calling program has
@ -751,26 +402,50 @@ bool NetworkHttp::downloadFileSynchron(const std::string &url,
* \param url The file from the server to download.
* \param save The name to save the downloaded file under. Defaults to
* the name given in file.
* \param priority Priority of the request (must be <=99)
*/
void NetworkHttp::downloadFileAsynchron(const std::string &url,
const std::string &save)
Request *NetworkHttp::downloadFileAsynchron(const std::string &url,
const std::string &save,
int priority,
bool manage_memory)
{
m_progress.set(0.0f);
m_url = url;
m_save_filename = (save!="") ? save : url;
// Limit priorities to 99 so that important system requests
// (init and quit) will have highest priority.
assert(priority<=99);
Request *request = new Request(Request::HC_DOWNLOAD_FILE, priority,
manage_memory,
url, (save!="") ? save : url );
if(UserConfigParams::logAddons())
printf("[addons] Download asynchron '%s' as '%s'.\n",
url.c_str(), m_save_filename.c_str());
// Wake up the network http thread
pthread_mutex_lock(&m_mutex_command);
{
m_command = HC_DOWNLOAD_FILE;
pthread_cond_signal(&m_cond_command);
}
pthread_mutex_unlock(&m_mutex_command);
request->getURL().c_str(), request->getSavePath().c_str());
insertRequest(request);
return request;
} // downloadFileAsynchron
// ----------------------------------------------------------------------------
/** Inserts a request into the queue of all requests. The request will be
* sorted by priority.
* \param request The pointer to the new request to insert.
*/
void NetworkHttp::insertRequest(Request *request)
{
// Wake up the network http thread
m_all_requests.lock();
{
m_all_requests.getData().push_back(request);
unsigned int i=m_all_requests.getData().size()-1;
while(i>0 && *(m_all_requests.getData()[i])<*request)
{
m_all_requests.getData()[i] = m_all_requests.getData()[i-1];
i--;
}
m_all_requests.getData()[i] = request;
pthread_cond_signal(&m_cond_request);
}
m_all_requests.unlock();
} // insertRequest
// ----------------------------------------------------------------------------
/** Callback function from curl: inform about progress.
* \param clientp
@ -783,6 +458,7 @@ int NetworkHttp::progressDownload(void *clientp,
double download_total, double download_now,
double upload_total, double upload_now)
{
Request *request = (Request *)clientp;
// Check if we are asked to abort the download. If so, signal this
// back to libcurl by returning a non-zero status.
if(network_http->m_abort.get())
@ -805,17 +481,8 @@ int NetworkHttp::progressDownload(void *clientp,
// after checking curls return code!
f= download_total==0 ? 0 : 0.99f;
}
network_http->m_progress.set(f);
request->setProgress(f);
return 0;
} // progressDownload
// ----------------------------------------------------------------------------
/** Returns the progress of a download that has been started.
* \return <0 in case of an error, between 0 and smaller than 1 while the
* download is in progress, and 1.0f if the download has finished.
*/
float NetworkHttp::getProgress() const
{
return m_progress.get();
} // getProgress

View File

@ -23,30 +23,22 @@
#include <string>
#include <vector>
#ifdef WIN32
# include <winsock2.h>
#endif
#include <curl/curl.h>
#include "irrlicht.h"
using namespace irr;
#include "utils/synchronised.hpp"
class Request;
class XMLNode;
class NetworkHttp
{
public:
/** List of 'http commands' for this object:
* HC_SLEEP: No command, sleep
* HC_INIT: Object is being initialised
* HC_DOWNLOAD_FILE : download a file
* HC_QUIT: Stop loop and terminate thread.
* HC_NEWS: Update the news
*/
enum HttpCommands {HC_SLEEP,
HC_QUIT,
HC_INIT,
HC_DOWNLOAD_FILE,
HC_NEWS };
/** If stk has permission to access the internet (for news
* server etc).
* IPERM_NOT_ASKED: The user needs to be asked if he wants to
@ -57,108 +49,37 @@ public:
IPERM_ALLOWED =1,
IPERM_NOT_ALLOWED=2 };
private:
// A wrapper class to store news message together with
// a message id and a display count.
class NewsMessage
{
// The actual news message
core::stringw m_news;
// A message id used to store some information in the
// user config file.
int m_message_id;
// Counts how often a message has been displayed.
int m_display_count;
public:
NewsMessage(const core::stringw &m, int id)
{
m_news = m;
m_message_id = id;
m_display_count = 0;
} // NewsMessage
/** Returns the news message. */
const core::stringw& getNews() const {return m_news;}
/** Increases how often this message was being displayed. */
void increaseDisplayCount() {m_display_count++;}
/** Returns the news id. */
int getMessageId() const {return m_message_id;}
/** Returns the display count. */
int getDisplayCount() const {return m_display_count; }
/** Sets the display count for this message. */
void setDisplayCount(int n) {m_display_count = n; }
}; // NewsMessage
mutable Synchronised< std::vector<NewsMessage> > m_news;
/** Index of the current news message that is being displayed. */
int m_current_news_message;
/** Stores the news message display count from the user config file.
*/
std::vector<int> m_saved_display_count;
/** Which command to execute next. Access to this variable is guarded
* by m_mutex_command and m_cond_command. */
HttpCommands m_command;
/** A mutex for accessing m_commands. */
pthread_mutex_t m_mutex_command;
/** The list of pointes to all requests. */
mutable Synchronised< std::vector<Request*> > m_all_requests;
/** A conditional variable to wake up the main loop. */
pthread_cond_t m_cond_command;
/** A mutex used in cancelling the network thread with timeout. */
pthread_mutex_t m_mutex_quit;
/** Conidtional variable used in cancelling the network
* thread with timeout. */
pthread_cond_t m_cond_quit;
/** The file to download when a file download is triggered. */
std::string m_url;
/** The name and path under which to save the downloaded file. */
std::string m_save_filename;
/** Progress of a download in percent. It is guaranteed that
* this value only becomes 1.0f, if the download is completed.*/
Synchronised<float> m_progress;
pthread_cond_t m_cond_request;
/** Signal an abort in case that a download is still happening. */
Synchronised<bool> m_abort;
/** Thread id of the thread running in this object. */
pthread_t *m_thread_id;
pthread_t *m_thread_id;
/** The curl session. */
CURL *m_curl_session;
static void *mainLoop(void *obj);
void checkRedirect(const XMLNode *xml);
void updateNews(const XMLNode *xml,
const std::string &filename);
void updateUserConfigFile() const;
int init();
void loadAddonsList(const XMLNode *xml,
const std::string &filename);
std::string downloadToStrInternal(std::string url);
void updateMessageDisplayCount();
bool downloadFileInternal(const std::string &url,
const std::string &save_filename,
bool is_asynchron);
bool downloadFileInternal(Request *request);
static int progressDownload(void *clientp, double dltotal, double dlnow,
double ultotal, double ulnow);
bool conditionFulfilled(const std::string &cond);
int versionToInt(const std::string &s);
void insertRequest(Request *request);
public:
NetworkHttp();
~NetworkHttp();
static size_t writeStr(char str [], size_t size, size_t nb_char,
std::string * stream);
void downloadFileAsynchron(const std::string &url,
const std::string &save = "");
bool downloadFileSynchron(const std::string &url,
const std::string &save = "");
const core::stringw
getNextNewsMessage();
float getProgress() const;
Request *downloadFileAsynchron(const std::string &url,
const std::string &save = "",
int priority = 1,
bool manage_memory=true);
void cancelDownload();
}; // NetworkHttp

375
src/addons/news_manager.cpp Normal file
View File

@ -0,0 +1,375 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2011 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 "states_screens/addons_screen.hpp"
#include "states_screens/main_menu_screen.hpp"
#include "utils/string_utils.hpp"
#include "utils/time.hpp"
NewsManager *news_manager=NULL;
// ----------------------------------------------------------------------------
NewsManager::NewsManager() : m_news(std::vector<NewsMessage>())
{
m_current_news_message = -1;
} // NewsManage
// ---------------------------------------------------------------------------
NewsManager::~NewsManager()
{
} // ~NewsManager
// ---------------------------------------------------------------------------
/** Initialises the online part of the network manager. It downloads the
* news.xml file from the server (if the frequency of downloads makes this
* necessary), and (again if necessary) the addons.xml file.
* \return 0 if an error happened and no online connection will be available,
* 1 otherwise.
*/
void NewsManager::init()
{
UserConfigParams::m_news_last_updated = Time::getTimeSinceEpoch();
std::string xml_file = file_manager->getAddonsFile("news.xml");
const XMLNode *xml = new XMLNode(xml_file);
checkRedirect(xml);
updateNews(xml, xml_file);
} // init
// ---------------------------------------------------------------------------
/** 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())
{
std::cout << "[Addons] Current server: "
<< (std::string)UserConfigParams::m_server_addons
<< std::endl
<< "[Addons] New server: " << new_server << std::endl;
}
UserConfigParams::m_server_addons = new_server;
}
} // 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)
{
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;
std::string news;
node->get("content", &news);
int id=-1;
node->get("id", &id);
std::string cond;
node->get("condition", &cond);
if(!conditionFulfilled(cond))
continue;
m_news.lock();
{
// Define this if news messages should be removed
// after being shown a certain number of times.
#undef NEWS_MESSAGE_REMOVAL
#ifdef NEWS_MESSAGE_REMOVAL
// Only add the news if it's not supposed to be ignored.
if(id>UserConfigParams::m_ignore_message_id)
#endif
{
NewsMessage n(core::stringw(news.c_str()), id);
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_news.unlock();
}
#ifdef NEWS_MESSAGE_REMOVAL
else
updateMessageDisplayCount();
#endif
} // 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 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()
{
if(m_news.getData().size()==0)
return "";
core::stringw m("");
m_news.lock();
{
// Check if we have a message that was finished being
// displayed --> increase display count.
if(m_current_news_message>-1)
{
#ifdef NEWS_MESSAGE_REMOVAL
NewsMessage &n = m_news.getData()[m_current_news_message];
n.increaseDisplayCount();
#endif
// If the message is being displayed often enough,
// ignore it from now on.
#ifdef NEWS_MESSAGE_REMOVAL
if(n.getDisplayCount()>stk_config->m_max_display_news)
{
// Messages have sequential numbers, so we only store
// the latest message id (which is the current one)
UserConfigParams::m_ignore_message_id = n.getMessageId();
m_news.getData().erase(m_news.getData().begin()
+m_current_news_message );
}
#endif
updateUserConfigFile();
//
if(m_news.getData().size()==0)
{
m_news.unlock();
return "";
}
}
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;
} // getNextNewsMessage
// ----------------------------------------------------------------------------
/** Saves the information about which message was being displayed how often
* to the user config file. It dnoes not actually save the user config
* file, this is left to the main program (user config is saved at
* the exit of the program).
* Note that this function assumes that m_news is already locked!
*/
void NewsManager::updateUserConfigFile() const
{
#ifdef NEWS_MESSAGE_REMOVAL
std::ostringstream o;
for(unsigned int i=0; i<m_news.getData().size(); i++)
{
const NewsMessage &n=m_news.getData()[i];
o << n.getMessageId() << ":"
<< n.getDisplayCount() << " ";
}
UserConfigParams::m_display_count = o.str();
#else
// Always set them to be empty to avoid any
// invalid data that might create a problem later.
UserConfigParams::m_display_count = "";
UserConfigParams::m_ignore_message_id = -1;
#endif
} // updateUserConfigFile
// ----------------------------------------------------------------------------
/** Checks if the given condition list are all fulfilled.
* The conditions must be seperated 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)
{
printf("Invalid condition '%s' - assumed to be true.\n",
cond_list[i].c_str());
continue;
}
if(cond[0]=="stkversion")
{
int news_version = versionToInt(cond[2]);
int stk_version = 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;
}
printf("Invalid comparison in condition '%s' - assumed true.\n",
cond_list[i].c_str());
}
else
{
printf("Invalid condition '%s' - assumed to be true.\n",
cond_list[i].c_str());
continue;
}
} // for i < cond_list
return true;
} // conditionFulfilled
// ----------------------------------------------------------------------------
/** Converts a version string (in the form of 'X.Y.Za-rcU' into an
* integer number.
* \param s The version string to convert.
*/
int NewsManager::versionToInt(const std::string &version_string)
{
// Special case: SVN
if(version_string=="SVN" || version_string=="svn")
// SVN version will be version 99.99.99i-rcJ
return 1000000*99
+ 10000*99
+ 100*99
+ 10* 9
+ 9;
std::string s=version_string;
// To guarantee that a release gets a higher version number than
// a release candidate, we assign a 'release_candidate' number
// of 9 to versions which are not a RC. We assert that any RC
// is less than 9 to guarantee the ordering.
int release_candidate=9;
if(sscanf(s.substr(s.length()-4, 4).c_str(), "-rc%d",
&release_candidate)==1)
{
s = s.substr(0, s.length()-4);
// Otherwise a RC can get a higher version number than
// the corresponding release! If this should ever get
// triggered, multiply all scaling factors above and
// below by 10, to get two digits for RC numbers.
assert(release_candidate<9);
}
int very_minor=0;
if(s[s.size()-1]>='a' && s[s.size()-1]<='z')
{
very_minor = s[s.size()-1]-'a'+1;
s = s.substr(0, s.size()-1);
}
std::vector<std::string> l = StringUtils::split(s, '.');
while(l.size()<3)
l.push_back(0);
int version = 1000000*atoi(l[0].c_str())
+ 10000*atoi(l[1].c_str())
+ 100*atoi(l[2].c_str())
+ 10*very_minor
+ release_candidate;
if(version<=0)
printf("Invalid version string '%s'.\n", s.c_str());
return version;
} // versionToInt
// ----------------------------------------------------------------------------
/** Reads the information about which message was dislpayed how often from
* the user config file.
*/
void NewsManager::updateMessageDisplayCount()
{
#ifdef NEWS_MESSAGE_REMOVAL
m_news.lock();
std::vector<std::string> pairs =
StringUtils::split(UserConfigParams::m_display_count,' ');
for(unsigned int i=0; i<pairs.size(); i++)
{
std::vector<std::string> v = StringUtils::split(pairs[i], ':');
int id, count;
StringUtils::fromString(v[0], id);
StringUtils::fromString(v[1], count);
// Search all downloaded messages for this id.
for(unsigned int j=0; j<m_news.getData().size(); j++)
{
if(m_news.getData()[j].getMessageId()!=id)
continue;
m_news.getData()[j].setDisplayCount(count);
if(count>stk_config->m_max_display_news)
m_news.getData().erase(m_news.getData().begin()+j);
break;
} // for j <m_news.getData().size()
}
m_news.unlock();
#endif
} // updateMessageDisplayCount

View File

@ -0,0 +1,95 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2011 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.
#ifndef HEADER_NEWS_MANAGER_HPP
#define HEADER_NEWS_MANAGER_HPP
#include <string>
#include <vector>
#include "irrlicht.h"
using namespace irr;
#include "utils/synchronised.hpp"
class XMLNode;
class NewsManager
{
private:
// A wrapper class to store news message together with
// a message id and a display count.
class NewsMessage
{
// The actual news message
core::stringw m_news;
// A message id used to store some information in the
// user config file.
int m_message_id;
// Counts how often a message has been displayed.
int m_display_count;
public:
NewsMessage(const core::stringw &m, int id)
{
m_news = m;
m_message_id = id;
m_display_count = 0;
} // NewsMessage
/** Returns the news message. */
const core::stringw& getNews() const {return m_news;}
/** Increases how often this message was being displayed. */
void increaseDisplayCount() {m_display_count++;}
/** Returns the news id. */
int getMessageId() const {return m_message_id;}
/** Returns the display count. */
int getDisplayCount() const {return m_display_count; }
/** Sets the display count for this message. */
void setDisplayCount(int n) {m_display_count = n; }
}; // NewsMessage
mutable Synchronised< std::vector<NewsMessage> > m_news;
/** Index of the current news message that is being displayed. */
int m_current_news_message;
/** Stores the news message display count from the user config file.
*/
std::vector<int> m_saved_display_count;
void checkRedirect(const XMLNode *xml);
void updateNews(const XMLNode *xml,
const std::string &filename);
void updateUserConfigFile() const;
bool conditionFulfilled(const std::string &cond);
int versionToInt(const std::string &s);
void updateMessageDisplayCount();
public:
NewsManager();
~NewsManager();
const core::stringw
getNextNewsMessage();
void init();
void addNewsMessage(const core::stringw &s);
}; // NewsManager
extern NewsManager *news_manager;
#endif

61
src/addons/request.cpp Normal file
View File

@ -0,0 +1,61 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2011 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/request.hpp"
#include <assert.h>
#include "addons/addon.hpp"
Request::Request(HttpCommands command, int priority, bool manage_memory)
: m_progress(0)
{
m_command = command;
m_priority = priority;
m_url = "";
m_full_path = "";
m_manage_memory = manage_memory;
m_icon_addon = NULL;
m_progress.set(0);
} // Request
// ----------------------------------------------------------------------------
Request::Request(HttpCommands command, int priority, bool manage_memory,
const std::string &url, const std::string &save)
: m_progress(0)
{
m_command = command;
m_priority = priority;
m_url = url;
m_full_path = save;
m_icon_addon = NULL;
m_manage_memory = manage_memory;
m_progress.set(0);
} // Request
// ----------------------------------------------------------------------------
void Request::setAddonIconNotification(Addon *a)
{
m_icon_addon = a;
} // setADdonIconNotification
// ----------------------------------------------------------------------------
void Request::notifyAddon()
{
if(m_icon_addon)
m_icon_addon->setIconReady();
} // notifyAddon

105
src/addons/request.hpp Normal file
View File

@ -0,0 +1,105 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2011 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.
#ifndef HEADER_REQUEST_HPP
#define HEADER_REQUEST_HPP
#include <string>
#include "utils/synchronised.hpp"
class Addon;
/** Stores a download request. They will be sorted by priorities. */
class Request
{
public:
/** List of 'http commands' for this object:
* HC_SLEEP: No command, sleep
* HC_INIT: Object is being initialised
* HC_DOWNLOAD_FILE : download a file
* HC_QUIT: Stop loop and terminate thread.
* HC_NEWS: Update the news
*/
enum HttpCommands {HC_SLEEP,
HC_QUIT,
HC_INIT,
HC_DOWNLOAD_FILE,
HC_NEWS };
private:
/** The progress indicator. 0 till it is started and the first
* packet is downloaded. At the end eithe -1 (error) or 1
* (everything ok) at the end. */
Synchronised<float> m_progress;
/** The URL to download. */
std::string m_url;
/** Where to store the file (including file name). */
std::string m_full_path;
/** The priority of this request. The higher the value the more
important this request is. */
int m_priority;
/** The actual command to use. */
HttpCommands m_command;
/** True if the memory for this Request should be managed by
* network_http (i.e. this object is freed once the request
* is handled). Otherwise the memory is not freed, so it must
* be freed by the calling function. */
bool m_manage_memory;
/** If this is a download for an icon addon, this contains a pointer
* to the addon so that we can notify the addon that the icon is
* ready. */
Addon *m_icon_addon;
public:
Request(HttpCommands command, int priority,
bool manage_memory=true);
Request(HttpCommands command, int priority, bool manage_memory,
const std::string &url, const std::string &save);
void setAddonIconNotification(Addon *a);
void notifyAddon();
// --------------------------------------------------------------------
/** Returns the URL to download from. */
const std::string &getURL() const {return m_url;}
// --------------------------------------------------------------------
/** Returns the full save file name. */
const std::string &getSavePath() const {return m_full_path;}
// --------------------------------------------------------------------
/** Returns the command to do for this request. */
HttpCommands getCommand() const { return m_command; }
// --------------------------------------------------------------------
/** Returns the priority of this request. */
int getPriority() const { return m_priority; }
// --------------------------------------------------------------------
/** Returns the current progress. */
float getProgress() const { return m_progress.get(); }
// --------------------------------------------------------------------
/** Sets the current progress. */
void setProgress(float f) { m_progress.set(f); }
// --------------------------------------------------------------------
/** Used in sorting requests by priority. */
bool operator<(const Request &r) { return r.m_priority < m_priority;}
// --------------------------------------------------------------------
/** Returns if the memory for this object should be managed by
* by network_http (i.e. freed once the request is handled). */
bool manageMemory() const { return m_manage_memory; }
// --------------------------------------------------------------------
}; // Request
#endif

View File

@ -111,18 +111,6 @@ int MaterialManager::addEntity(Material *m)
return (int)m_materials.size()-1;
}
//-----------------------------------------------------------------------------
void MaterialManager::reInit()
{
for(std::vector<Material*>::const_iterator i=m_materials.begin();
i!=m_materials.end(); i++)
{
delete *i;
}
m_materials.clear();
loadMaterial();
} // reInit
//-----------------------------------------------------------------------------
void MaterialManager::loadMaterial()
{

View File

@ -46,7 +46,6 @@ public:
MaterialManager();
~MaterialManager();
void loadMaterial ();
void reInit ();
void setAllMaterialFlags(video::ITexture* t,
scene::IMeshBuffer *mb) const;

View File

@ -86,7 +86,7 @@ void AbstractStateManager::pushMenu(std::string name)
assert(!ModalDialog::isADialogActive()); // you need to close any dialog before calling this
if (UserConfigParams::m_verbosity >= 3)
if (UserConfigParams::logGUI())
{
std::cout << "[AbstractStateManager::pushMenu] switching to screen "
<< name.c_str() << std::endl;
@ -116,7 +116,7 @@ void AbstractStateManager::pushScreen(Screen* screen)
{
assert(!ModalDialog::isADialogActive()); // you need to close any dialog before calling this
if (UserConfigParams::m_verbosity >= 3)
if (UserConfigParams::logGUI())
{
std::cout << "[AbstractStateManager::pushScreen] switching to screen "
<< screen->getName().c_str() << std::endl;
@ -139,7 +139,7 @@ void AbstractStateManager::replaceTopMostScreen(Screen* screen)
if (!screen->isLoaded()) screen->loadFromFile();
std::string name = screen->getName();
if (UserConfigParams::m_verbosity >= 3)
if (UserConfigParams::logGUI())
{
std::cout << "[AbstractStateManager::replaceTopmostScreen] switching to screen "
<< name.c_str() << std::endl;
@ -199,7 +199,7 @@ void AbstractStateManager::popMenu()
return;
}
if (UserConfigParams::m_verbosity >= 3)
if (UserConfigParams::logGUI())
{
std::cout << "[AbstractStateManager::popMenu] switching to screen "
<< m_menu_stack[m_menu_stack.size()-1].c_str() << std::endl;
@ -231,7 +231,7 @@ void AbstractStateManager::resetAndGoToScreen(Screen* screen)
std::string name = screen->getName();
if (UserConfigParams::m_verbosity >= 3)
if (UserConfigParams::logGUI())
{
std::cout << "[AbstractStateManager::resetAndGoToScreen] switching to screen "
<< name.c_str() << std::endl;

View File

@ -1146,6 +1146,14 @@
RelativePath="..\..\addons\network_http.cpp"
>
</File>
<File
RelativePath="..\..\addons\news_manager.cpp"
>
</File>
<File
RelativePath="..\..\addons\request.cpp"
>
</File>
<File
RelativePath="..\..\addons\zip.cpp"
>
@ -2164,6 +2172,14 @@
RelativePath="..\..\addons\network_http.hpp"
>
</File>
<File
RelativePath="..\..\addons\news_manager.hpp"
>
</File>
<File
RelativePath="..\..\addons\request.hpp"
>
</File>
<File
RelativePath="..\..\addons\zip.hpp"
>

View File

@ -45,6 +45,7 @@
#include "main_loop.hpp"
#include "addons/addons_manager.hpp"
#include "addons/network_http.hpp"
#include "addons/news_manager.hpp"
#include "audio/music_manager.hpp"
#include "audio/sfx_manager.hpp"
#include "challenges/unlock_manager.hpp"
@ -794,6 +795,7 @@ void initRest()
// This only initialises the non-network part of the addons manager. The
// online section of the addons manager will be initialised from a
// separate thread running in network http.
news_manager = new NewsManager();
addons_manager = new AddonsManager();
network_http = new NetworkHttp();
music_manager = new MusicManager();
@ -848,6 +850,7 @@ void cleanTuxKart()
//see InitTuxkart()
if(race_manager) delete race_manager;
if(network_http) delete network_http;
if(news_manager) delete news_manager;
if(network_manager) delete network_manager;
if(grand_prix_manager) delete grand_prix_manager;
if(highscore_manager) delete highscore_manager;

View File

@ -24,6 +24,7 @@
#include "addons/addons_manager.hpp"
#include "addons/network_http.hpp"
#include "addons/request.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/widgets.hpp"
#include "input/input_manager.hpp"
@ -41,15 +42,16 @@ AddonsLoading::AddonsLoading(const float w, const float h,
: ModalDialog(w, h)
{
loadFromFile("addons_view_dialog.stkgui");
m_addon = *(addons_manager->getAddon(id));
m_icon_shown = false;
m_addon = *(addons_manager->getAddon(id));
m_icon_shown = false;
m_download_request = NULL;
/*Init the icon here to be able to load a single image*/
m_icon = getWidget<IconButtonWidget> ("icon" );
m_progress = getWidget<ProgressBarWidget>("progress");
m_install_button = getWidget<ButtonWidget> ("install" );
m_back_button = getWidget<ButtonWidget> ("cancel" );
m_state = getWidget<LabelWidget> ("state" );
m_icon = getWidget<IconButtonWidget> ("icon" );
m_progress = getWidget<ProgressBarWidget>("progress");
m_install_button = getWidget<ButtonWidget> ("install" );
m_back_button = getWidget<ButtonWidget> ("cancel" );
m_state = getWidget<LabelWidget> ("state" );
if(m_progress)
m_progress->setVisible(false);
@ -132,7 +134,7 @@ void AddonsLoading::onUpdate(float delta)
{
if(m_progress->isVisible())
{
float progress = network_http->getProgress();
float progress = m_download_request->getProgress();
m_progress->setValue((int)(progress*100.0f));
if(progress<0)
{
@ -168,10 +170,12 @@ void AddonsLoading::onUpdate(float delta)
**/
void AddonsLoading::startDownload()
{
std::string file = m_addon.getZipFileName();
std::string save = "tmp/"
+ StringUtils::getBasename(m_addon.getZipFileName());
network_http->downloadFileAsynchron(file, save);
std::string file = m_addon.getZipFileName();
std::string save = "tmp/"
+ StringUtils::getBasename(m_addon.getZipFileName());
m_download_request = network_http->downloadFileAsynchron(file, save,
/*priority*/5,
/*manage memory*/false);
} // startDownload
// ----------------------------------------------------------------------------

View File

@ -29,6 +29,8 @@
#include "guiengine/modaldialog.hpp"
#include "utils/synchronised.hpp"
class Request;
/**
* \ingroup states_screens
*/
@ -51,6 +53,10 @@ private:
/** True if the icon is being displayed. */
bool m_icon_shown;
/** A pointer to the download request, which gives access
* to the progress of a download. */
Request *m_download_request;
public:
/**
* Creates a modal dialog with given percentage of screen width and height

View File

@ -47,8 +47,7 @@
#include "states_screens/grand_prix_win.hpp"
#endif
#include "addons/network_http.hpp"
#include "addons/news_manager.hpp"
#include "tracks/track_manager.hpp"
#include "tracks/track.hpp"
#include "utils/string_utils.hpp"
@ -112,7 +111,7 @@ void MainMenuScreen::init()
#endif
LabelWidget* w = this->getWidget<LabelWidget>("info_addons");
const core::stringw &news_text = network_http->getNextNewsMessage();
const core::stringw &news_text = news_manager->getNextNewsMessage();
w->setText(news_text, true);
w->update(0.01f);
}
@ -136,7 +135,7 @@ void MainMenuScreen::onUpdate(float delta, irr::video::IVideoDriver* driver)
w->update(delta);
if(w->scrolledOff())
{
const core::stringw &news_text = network_http->getNextNewsMessage();
const core::stringw &news_text = news_manager->getNextNewsMessage();
w->setText(news_text, true);
}

View File

@ -95,6 +95,11 @@ public:
/** Unlocks the mutex.
*/
void unlock() {pthread_mutex_unlock(&m_mutex); }
// ------------------------------------------------------------------------
/** Gives access to the mutex, which can then be used in other pthread
* calls (e.g. pthread_cond_wait).
*/
pthread_mutex_t* getMutex() { return &m_mutex; }
private:
// Make sure that no actual copying is taking place
// ------------------------------------------------------------------------