488 lines
12 KiB
C++
488 lines
12 KiB
C++
/**
|
|
* updater.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Auto updater logic and downloader for Qt-4 clients.
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2006 Null Team
|
|
*
|
|
* 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 2 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "updater.h"
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
|
|
#include <QUrl>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QProcess>
|
|
|
|
#define MIN_SIZE 1024
|
|
#define MAX_SIZE (16*1024*1024)
|
|
|
|
#define TMP_EXT ".tmp"
|
|
#ifdef _WINDOWS
|
|
#define EXE_EXT ".exe"
|
|
#else
|
|
#define EXE_EXT ".bin"
|
|
#endif
|
|
|
|
using namespace TelEngine;
|
|
namespace { // anonymous
|
|
|
|
/**
|
|
* UI logic interaction
|
|
*/
|
|
class UpdateLogic : public ClientLogic
|
|
{
|
|
public:
|
|
enum Policy {
|
|
Invalid,
|
|
Never,
|
|
Check,
|
|
Download,
|
|
Install,
|
|
};
|
|
inline UpdateLogic(const char* name)
|
|
: ClientLogic(name,100),
|
|
m_policy(Invalid), m_checking(false),
|
|
m_checked(false), m_install(false),
|
|
m_http(0), m_file(0), m_httpSlots(0), m_canUpdate(true)
|
|
{ }
|
|
virtual ~UpdateLogic();
|
|
inline Policy policy() const
|
|
{ return static_cast<Policy>(m_policy); }
|
|
virtual bool initializedClient();
|
|
virtual void exitingClient();
|
|
virtual bool action(Window* wnd, const String& name, NamedList* params);
|
|
virtual bool toggle(Window* wnd, const String& name, bool active);
|
|
void gotPercentage(int percent);
|
|
void endHttp(bool error);
|
|
protected:
|
|
void setPolicy(int policy, bool save);
|
|
void startChecking(bool start = true);
|
|
void finishedChecking();
|
|
void startDownloading(bool start = true);
|
|
void finishedDownloading();
|
|
void startInstalling();
|
|
private:
|
|
QString filePath(bool temp);
|
|
bool startHttp(const char* url, const QString& saveAs);
|
|
void stopHttp();
|
|
void stopFile();
|
|
int m_policy;
|
|
bool m_checking;
|
|
bool m_checked;
|
|
bool m_install;
|
|
String m_url;
|
|
QHttp* m_http;
|
|
QFile* m_file;
|
|
QtUpdateHttp* m_httpSlots;
|
|
bool m_canUpdate;
|
|
};
|
|
/**
|
|
* Plugin registration
|
|
*/
|
|
class Updater : public Plugin
|
|
{
|
|
public:
|
|
Updater();
|
|
virtual ~Updater();
|
|
virtual void initialize();
|
|
private:
|
|
UpdateLogic* m_logic;
|
|
};
|
|
|
|
static Updater s_plugin;
|
|
|
|
static const TokenDict s_policies[] = {
|
|
{ "never", UpdateLogic::Never },
|
|
{ "check", UpdateLogic::Check },
|
|
{ "download", UpdateLogic::Download },
|
|
{ "install", UpdateLogic::Install },
|
|
{ 0, UpdateLogic::Invalid }
|
|
};
|
|
|
|
UpdateLogic::~UpdateLogic()
|
|
{
|
|
}
|
|
|
|
bool UpdateLogic::initializedClient()
|
|
{
|
|
// Check if the current user can write to install dir
|
|
// Disable and uncheck all updater UI controls on failure
|
|
Configuration cfg(Engine::configFile("updater"));
|
|
m_canUpdate = !File::exists(cfg) || cfg.save();
|
|
if (!m_canUpdate) {
|
|
Debug(toString(),DebugInfo,"Disabling updates: the current user can't write to '%s'",
|
|
Engine::configPath().c_str());
|
|
NamedList p("");
|
|
p.addParam("check:upd_automatic","false");
|
|
p.addParam("active:upd_automatic","false");
|
|
p.addParam("active:upd_install","false");
|
|
p.addParam("active:upd_check","false");
|
|
p.addParam("active:upd_download","false");
|
|
for (int i = 0; s_policies[i].token; i++)
|
|
p.addParam("active:upd_policy_" + String(s_policies[i].token),"false");
|
|
if (Client::self())
|
|
Client::self()->setParams(&p);
|
|
return false;
|
|
}
|
|
|
|
int policy = Engine::config().getIntValue("client",toString(),s_policies,Never);
|
|
policy = Client::s_settings.getIntValue(toString(),"policy",s_policies,policy);
|
|
setPolicy(policy,false);
|
|
if (QFile::exists(filePath(false))) {
|
|
m_install = Client::s_settings.getBoolValue(toString(),"install");
|
|
if ((m_policy >= Install) && !m_install) {
|
|
Debug(toString(),DebugNote,"Deleting old updater file");
|
|
QFile::remove(filePath(false));
|
|
}
|
|
}
|
|
Client::self()->setActive("upd_install",m_install);
|
|
if (m_install && (m_policy >= Install))
|
|
startInstalling();
|
|
else if (m_policy >= Check)
|
|
startChecking();
|
|
return false;
|
|
}
|
|
|
|
void UpdateLogic::exitingClient()
|
|
{
|
|
startDownloading(false);
|
|
startChecking(false);
|
|
stopHttp();
|
|
delete m_httpSlots;
|
|
m_httpSlots = 0;
|
|
}
|
|
|
|
bool UpdateLogic::action(Window* wnd, const String& name, NamedList* params)
|
|
{
|
|
if (!m_canUpdate)
|
|
return false;
|
|
if (name == "upd_install")
|
|
startInstalling();
|
|
else if (name == "upd_check")
|
|
startChecking();
|
|
else if (name == "upd_download")
|
|
startDownloading();
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool UpdateLogic::toggle(Window* wnd, const String& name, bool active)
|
|
{
|
|
if (!m_canUpdate)
|
|
return false;
|
|
if (!name.startsWith("upd_"))
|
|
return false;
|
|
if (name == "upd_check")
|
|
startChecking(active);
|
|
else if (name == "upd_download")
|
|
startDownloading(active);
|
|
else if (name == "upd_automatic")
|
|
setPolicy(active ? Install : Never,true);
|
|
else if (active) {
|
|
String tmp = name;
|
|
if (tmp.startSkip("upd_policy_",false))
|
|
setPolicy(lookup(tmp,s_policies,Invalid),true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void UpdateLogic::setPolicy(int policy, bool save)
|
|
{
|
|
if ((policy == Invalid) || (policy == m_policy))
|
|
return;
|
|
const char* pol = lookup(policy,s_policies);
|
|
if (!pol)
|
|
return;
|
|
m_policy = policy;
|
|
if (save) {
|
|
Client::s_settings.setValue(toString(),"policy",pol);
|
|
Client::save(Client::s_settings);
|
|
}
|
|
if (!Client::self())
|
|
return;
|
|
for (policy = Never; policy <= Install; policy++) {
|
|
String tmp = "upd_policy_";
|
|
tmp += lookup(policy,s_policies);
|
|
Client::self()->setCheck(tmp,(policy == m_policy));
|
|
}
|
|
Client::self()->setCheck("upd_automatic",(Install == m_policy));
|
|
}
|
|
|
|
void UpdateLogic::startChecking(bool start)
|
|
{
|
|
String url = Engine::config().getValue("client","updateurl");
|
|
Engine::runParams().replaceParams(url);
|
|
if (url.trimBlanks().null()) {
|
|
start = false;
|
|
if (Client::self()) {
|
|
Client::self()->setActive("upd_check",false);
|
|
Client::self()->setActive("upd_download",false);
|
|
Client::self()->setActive("upd_install",false);
|
|
}
|
|
}
|
|
if (start) {
|
|
Debug(toString(),DebugNote,"Checking new version: %s",url.c_str());
|
|
m_checked = false;
|
|
m_checking = true;
|
|
start = startHttp(url,"");
|
|
if (Client::self()) {
|
|
Client::self()->setActive("upd_download",false);
|
|
Client::self()->setSelect("upd_progress","0");
|
|
Client::self()->setText("upd_version","");
|
|
}
|
|
}
|
|
else
|
|
stopHttp();
|
|
if (Client::self())
|
|
Client::self()->setCheck("upd_check",start);
|
|
}
|
|
|
|
void UpdateLogic::startDownloading(bool start)
|
|
{
|
|
m_checking = false;
|
|
if (start && m_install) {
|
|
m_install = false;
|
|
Client::s_settings.setValue(toString(),"install",String::boolText(false));
|
|
Client::save(Client::s_settings);
|
|
}
|
|
if (start) {
|
|
Debug(toString(),DebugNote,"Downloading from: %s",m_url.c_str());
|
|
start = startHttp(m_url,filePath(true));
|
|
}
|
|
else {
|
|
stopHttp();
|
|
QFile::remove(filePath(true));
|
|
}
|
|
if (Client::self()) {
|
|
Client::self()->setActive("upd_check",!start);
|
|
Client::self()->setActive("upd_install",m_install);
|
|
Client::self()->setCheck("upd_download",start);
|
|
Client::self()->setSelect("upd_progress","0");
|
|
}
|
|
}
|
|
|
|
void UpdateLogic::startInstalling()
|
|
{
|
|
if (!QFile::exists(filePath(false)))
|
|
return;
|
|
QString cmd = Engine::config().getValue("client","updatecmd");
|
|
if (!cmd.isEmpty()) {
|
|
String tmp = cmd.toUtf8().constData();
|
|
NamedList params(Engine::runParams());
|
|
params.setParam("filename",filePath(false).toUtf8().constData());
|
|
params.replaceParams(tmp);
|
|
if (tmp.trimBlanks().null())
|
|
return;
|
|
cmd = QString::fromUtf8(tmp.c_str());
|
|
}
|
|
else
|
|
cmd = filePath(false);
|
|
if (QProcess::startDetached(cmd)) {
|
|
Debug(toString(),DebugNote,"Executing: %s",cmd.toUtf8().constData());
|
|
Client::s_settings.setValue(toString(),"install",String::boolText(false));
|
|
Client::save(Client::s_settings);
|
|
Engine::halt(0);
|
|
return;
|
|
}
|
|
Debug(toString(),DebugWarn,"Failed to execute: %s",cmd.toUtf8().constData());
|
|
}
|
|
|
|
void UpdateLogic::finishedChecking()
|
|
{
|
|
if (Client::self()) {
|
|
Client::self()->setCheck("upd_check",false);
|
|
Client::self()->setActive("upd_download",m_checked);
|
|
Client::self()->setSelect("upd_progress","0");
|
|
}
|
|
if (m_checked && (m_policy >= Download))
|
|
startDownloading();
|
|
}
|
|
|
|
void UpdateLogic::finishedDownloading()
|
|
{
|
|
if (Client::self()) {
|
|
Client::self()->setCheck("upd_download",false);
|
|
Client::self()->setActive("upd_check",true);
|
|
Client::self()->setActive("upd_install",m_install);
|
|
if (!m_install)
|
|
Client::self()->setSelect("upd_progress","0");
|
|
}
|
|
Client::s_settings.setValue(toString(),"install",String::boolText(m_install));
|
|
Client::save(Client::s_settings);
|
|
}
|
|
|
|
QString UpdateLogic::filePath(bool temp)
|
|
{
|
|
return QString::fromUtf8((Engine::configPath(true) + Engine::pathSeparator() + toString() +
|
|
(temp ? TMP_EXT : EXE_EXT)));
|
|
}
|
|
|
|
bool UpdateLogic::startHttp(const char* url, const QString& saveAs)
|
|
{
|
|
stopHttp();
|
|
QUrl qurl(QString::fromUtf8(url));
|
|
if (!qurl.isValid())
|
|
return false;
|
|
QFile* file = 0;
|
|
if (!saveAs.isEmpty()) {
|
|
QFile::remove(saveAs);
|
|
file = new QFile(saveAs);
|
|
if (!(file->open(QIODevice::WriteOnly) &&
|
|
file->setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ExeOwner))) {
|
|
file->remove();
|
|
delete file;
|
|
return false;
|
|
}
|
|
m_file = file;
|
|
}
|
|
if (!m_httpSlots)
|
|
m_httpSlots = new QtUpdateHttp(this);
|
|
m_http = m_httpSlots->http();
|
|
const char* proxy = Client::s_settings.getValue(toString(),"proxy_host");
|
|
if (proxy)
|
|
m_http->setProxy(proxy,
|
|
Client::s_settings.getIntValue(toString(),"proxy_port",8080),
|
|
Client::s_settings.getValue(toString(),"proxy_user"),
|
|
Client::s_settings.getValue(toString(),"proxy_pass"));
|
|
m_http->setHost(qurl.host(),qurl.port(80));
|
|
m_http->get(qurl.path(),file);
|
|
return true;
|
|
}
|
|
|
|
void UpdateLogic::stopHttp()
|
|
{
|
|
QHttp* http = m_http;
|
|
m_http = 0;
|
|
if (http) {
|
|
http->abort();
|
|
delete http;
|
|
}
|
|
stopFile();
|
|
}
|
|
|
|
void UpdateLogic::stopFile()
|
|
{
|
|
QFile* file = m_file;
|
|
m_file = 0;
|
|
delete file;
|
|
}
|
|
|
|
void UpdateLogic::gotPercentage(int percent)
|
|
{
|
|
if (!Client::self())
|
|
return;
|
|
Client::self()->setSelect("upd_progress",String(percent));
|
|
}
|
|
|
|
void UpdateLogic::endHttp(bool error)
|
|
{
|
|
stopFile();
|
|
if (!m_http)
|
|
return;
|
|
if (m_checking) {
|
|
if (!error) {
|
|
QByteArray data = m_http->readAll();
|
|
if (data.size() <= 1024) {
|
|
String str(data.constData());
|
|
// 1st row is the URL, everything else description
|
|
int nl = str.find('\n');
|
|
if (nl > 0) {
|
|
int len = (str.at(nl - 1) == '\r') ? (nl - 1) : nl;
|
|
URI url(str.substr(0,len));
|
|
url.trimBlanks();
|
|
if (url.getProtocol() == "http") {
|
|
m_checked = true;
|
|
m_url = url;
|
|
if (Client::self())
|
|
Client::self()->setText("upd_version",str.substr(nl+1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finishedChecking();
|
|
}
|
|
else {
|
|
if (!error) {
|
|
QFileInfo info(filePath(true));
|
|
if ((info.size() >= MIN_SIZE) && (info.size() <= MAX_SIZE)) {
|
|
QFile::remove(filePath(false));
|
|
m_install = QFile::rename(filePath(true),filePath(false));
|
|
}
|
|
}
|
|
QFile::remove(filePath(true));
|
|
finishedDownloading();
|
|
}
|
|
}
|
|
|
|
|
|
QHttp* QtUpdateHttp::http()
|
|
{
|
|
QHttp* h = new QHttp(this);
|
|
connect(h,SIGNAL(dataReadProgress(int,int)),this,SLOT(dataProgress(int,int)));
|
|
connect(h,SIGNAL(done(bool)),this,SLOT(requestDone(bool)));
|
|
return h;
|
|
}
|
|
|
|
void QtUpdateHttp::dataProgress(int done, int total)
|
|
{
|
|
if (!m_logic)
|
|
return;
|
|
int percent = 0;
|
|
if (done)
|
|
percent = (done <= total) ? (done * 100 / total) : 50;
|
|
m_logic->gotPercentage(percent);
|
|
}
|
|
|
|
void QtUpdateHttp::requestDone(bool error)
|
|
{
|
|
if (m_logic)
|
|
m_logic->endHttp(error);
|
|
}
|
|
|
|
|
|
Updater::Updater()
|
|
: Plugin("updater",true), m_logic(0)
|
|
{
|
|
Output("Loaded module Updater");
|
|
}
|
|
|
|
Updater::~Updater()
|
|
{
|
|
Output("Unloading module Updater");
|
|
TelEngine::destruct(m_logic);
|
|
}
|
|
|
|
void Updater::initialize()
|
|
{
|
|
Output("Initializing module Updater");
|
|
if (m_logic)
|
|
return;
|
|
m_logic = new UpdateLogic("updater");
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
#include "updater.moc"
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|