/////////////////////////////////////////////////////////////////////////////
// Name: checkupdates.cpp
// Purpose:
// Author: Cesar Mauri Loba (cesar at crea-si dot com)
// Copyright: (C) 2012-14 Cesar Mauri Loba - CREA Software Systems
//
// 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, see .
/////////////////////////////////////////////////////////////////////////////
#include "checkupdates.h"
#include "version.h"
#include
#include
#include
#define UPDATE_HOSTNAME "eviacam.crea-si.com"
#ifdef NDEBUG
#define MODE_DEBUG _T("&m=r")
#else
#define MODE_DEBUG _T("&m=d")
#endif
#if defined(__WXGTK__)
#define UPDATE_FILE _T("/version.php?cv=") _T(VERSION) _T("&p=linux") MODE_DEBUG
#else
#define UPDATE_FILE _T("/version.php?cv=") _T(VERSION) _T("&p=windows") MODE_DEBUG
#endif
// Compare two version strings
// Return:
// -1: s1< s2
// 0: s1== s2
// 1: s1> s2
static
int versionstrcmp(const char* s1, const char* s2)
{
int compo_s1[4], compo_s2[4];
for (int i= 0; i< 4; i++) compo_s1[i]= compo_s2[i]= 0;
sscanf (s1, "%d.%d.%d.%d", &compo_s1[0], &compo_s1[1], &compo_s1[2], &compo_s1[3]);
sscanf (s2, "%d.%d.%d.%d", &compo_s2[0], &compo_s2[1], &compo_s2[2], &compo_s2[3]);
for (int i= 0; i< 4; i++) {
if (compo_s1[i]< compo_s2[i]) return -1;
if (compo_s1[i]> compo_s2[i]) return 1;
}
return 0;
}
#define VERSION_STRING_MAX_SIZE 20
// Check if a new version of the software is available by
// checking the contents of
// http://UPDATE_HOSTNAME/UPDATE_FILE
//
// Return:
// 1: newer version available
// 0: no newer version available
// -1: local version newer than remote!
// -2: cannot resolve host name
// -3: cannot connect to host
// -4: file does not exist (on server)
// -5: generic error
static
int check_updates(std::string* new_version)
{
// Create request on the heap as recommended in the documentation
wxHTTP* request= new wxHTTP();
if (!request) return -5;
wxInputStream *httpStream = NULL;
int retval= 0;
request->SetHeader(_T("Content-type"), _T("text/html; charset=utf-8"));
request->SetTimeout(10); // 10 seconds of timeout
// Start connection request (actually only resolve hostname)
if (!request->Connect(_T(UPDATE_HOSTNAME))) {
retval= -2;
goto check_update_error;
}
// Set file to request & make connection
httpStream = request->GetInputStream(UPDATE_FILE);
if (!httpStream) {
// Due to a buggy wxHTTP implementation, many errors are reported
// this way. User only sees "connection error".
retval= -3;
goto check_update_error;
}
if (request->GetError()!= wxPROTO_NOERR) {
switch (request->GetError()) {
case wxPROTO_CONNERR: retval= -3; break;
case wxPROTO_NOFILE: retval= -4; break;
default: retval= -5;
}
goto check_update_error;
}
// Read file contents up to VERSION_STRING_MAX_SIZE bytes
char buffer[VERSION_STRING_MAX_SIZE + 1];
{
unsigned int read_count= 0;
bool is_eof= false;
while (read_count< VERSION_STRING_MAX_SIZE && !is_eof) {
httpStream->Read(&buffer[read_count], VERSION_STRING_MAX_SIZE - read_count);
read_count+= static_cast(httpStream->LastRead());
is_eof= httpStream->Eof();
}
assert (read_count<= VERSION_STRING_MAX_SIZE);
buffer[read_count]= 0; // String terminator
}
retval= versionstrcmp(buffer, VERSION);
if (new_version) (*new_version)= buffer;
check_update_error:
if (httpStream) wxDELETE(httpStream);
if (request) request->Destroy();
return retval;
}
DECLARE_EVENT_TYPE(THREAD_FINISHED_EVENT, wxCommandEvent);
DEFINE_EVENT_TYPE(THREAD_FINISHED_EVENT);
DEFINE_EVENT_TYPE(CHECKUPDATE_FINISHED_EVENT);
BEGIN_EVENT_TABLE(eviacam::CheckUpdates, wxEvtHandler)
EVT_COMMAND(wxID_ANY, THREAD_FINISHED_EVENT, eviacam::CheckUpdates::OnThreadFinished)
END_EVENT_TABLE()
namespace eviacam {
void CheckUpdates::OnThreadFinished(wxCommandEvent& event)
{
// This event handler translates the event sent from the worker thread,
// which stores version name using a SetClientData, into a new wxCommandEvent
// which uses SetString. We need to do so in order to maintain compatibility with
// wx2.x. There are chances to leak some memory if some THREAD_FINISHED_EVENT are
// discarded and thus not properly deallocated.
// TODO: when wx 3.0 takes over 2.x remove this stuff and send events directly
// from the thread using wxQueueEvent. See here for more info:
// // http://docs.wxwidgets.org/trunk/group__group__funcmacro__events.html#ga0cf60a1ad3a5f1e659f7ae591570f58d
assert(wxIsMainThread());
wxCommandEvent eventNew(CHECKUPDATE_FINISHED_EVENT);
// String
wxString* msg = static_cast(event.GetClientData());
eventNew.SetString(wxString(msg->mb_str(wxConvUTF8), wxConvUTF8)); // Take a deep copy of the string
delete msg;
// Status code
eventNew.SetInt(event.GetInt());
// Finish processing old event
event.Skip(true);
// Fire new event. Use this instead of wxQueueEvent for wx2.8 compatibility
wxPostEvent(this, eventNew);
}
CheckUpdates::CheckUpdates()
: m_threadRunning(false)
{
}
CheckUpdates::~CheckUpdates()
{
// Wait for the thread to finish
while (m_threadRunning)
wxThread::This()->Sleep(1);
}
void CheckUpdates::Start()
{
m_threadRunning = true;
new CheckUpdatesWorker(*this);
}
CheckUpdates::CheckUpdatesWorker::CheckUpdatesWorker(CheckUpdates& handler)
: wxThread(wxTHREAD_DETACHED)
, m_handler(&handler)
{
wxThreadError retval= this->Create();
if (retval== wxTHREAD_NO_ERROR) retval= this->Run();
if (retval!= wxTHREAD_NO_ERROR) {
m_handler->m_threadRunning = false;
wxCommandEvent event(THREAD_FINISHED_EVENT);
// Store information string using SetClientData instead of SetString
wxString* msg = new wxString(_("Error checking for updates. Try again later."));
event.SetClientData(msg);
event.SetInt(CheckUpdates::ERROR_CHECKING_NEW_VERSION);
// Use this instead of wxQueueEvent for wx2.8 compatibility
wxPostEvent(m_handler, event);
}
}
CheckUpdates::CheckUpdatesWorker::~CheckUpdatesWorker()
{
m_handler->m_threadRunning = false;
}
wxThread::ExitCode CheckUpdates::CheckUpdatesWorker::Entry()
{
std::string new_version;
int result= check_updates(&new_version);
wxString* msg = NULL;
wxCommandEvent event(THREAD_FINISHED_EVENT);
switch (result) {
case 1: // newer version available
msg = new wxString(new_version.c_str(), wxConvUTF8);
event.SetInt(CheckUpdates::NEW_VERSION_AVAILABLE);
break;
case 0: // no newer version available
case -1: // local version newer than remote!
msg = new wxString(new_version.c_str(), wxConvUTF8);
event.SetInt(CheckUpdates::NO_NEW_VERSION_AVAILABLE);
break;
case -2: // cannot resolve host name
msg = new wxString(_("Cannot resolve host name: "));
msg->Append(_T(UPDATE_HOSTNAME));
event.SetInt(CheckUpdates::ERROR_CHECKING_NEW_VERSION);
break;
case -3: // cannot connect to host
msg = new wxString(_("Conection failed: "));
msg->Append(_T(UPDATE_HOSTNAME));
event.SetInt(CheckUpdates::ERROR_CHECKING_NEW_VERSION);
break;
case -4: // file does not exist (on server)
msg = new wxString(_("Sorry. Version file not found. Please report us."));
event.SetInt(CheckUpdates::ERROR_CHECKING_NEW_VERSION);
break;
case -5: // generic error
msg = new wxString(_("Sorry. Something bad happened."));
event.SetInt(CheckUpdates::ERROR_CHECKING_NEW_VERSION);
break;
default:
assert (false);
}
// Store information string using SetClientData instead of SetString
event.SetClientData(msg);
// Use this instead of wxQueueEvent for wx2.8 compatibility
wxPostEvent(m_handler, event);
return ExitCode(0);
}
}