UpdateProcess.cpp
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011 #include "UpdateProcess.h"
00012 #include "Vidalia.h"
00013
00014 #include "stringutil.h"
00015
00016 #include <QDir>
00017 #include <QDomDocument>
00018 #include <QDomElement>
00019 #include <QDomNodeList>
00020
00021
00022 UpdateProcess::UpdateProcess(QObject *parent)
00023 : QProcess(parent)
00024 {
00025 _currentCommand = NoCommand;
00026 _socksPort = 0;
00027
00028 connect(this, SIGNAL(readyReadStandardError()),
00029 this, SLOT(readStandardError()));
00030 connect(this, SIGNAL(readyReadStandardOutput()),
00031 this, SLOT(readStandardOutput()));
00032 connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
00033 this, SLOT(onFinished(int, QProcess::ExitStatus)));
00034
00035 setEnvironment(systemEnvironment());
00036 }
00037
00038 void
00039 UpdateProcess::checkForUpdates(BundleInfo bi)
00040 {
00041 QStringList args;
00042
00043 args << "update" << "--force-check"
00044 << " --controller-log-format"
00045 << "--repo=" + updateRepositoryDir()
00046 << "--debug";
00047 if (_socksPort)
00048 args << "--socks-port=" + QString::number(_socksPort);
00049
00050 args << bundleInfoToString(bi);
00051
00052 vNotice("updater: launching auto-update executable: %1 %2")
00053 .arg(updateExecutable())
00054 .arg(args.join(" "));
00055 _currentBundle = bi;
00056 _currentCommand = CheckForUpdates;
00057 start(updateExecutable(), args);
00058 }
00059
00060 void
00061 UpdateProcess::installUpdates(BundleInfo bi)
00062 {
00063 QStringList args;
00064
00065 args << "update" << "--controller-log-format"
00066 << "--repo=" + updateRepositoryDir()
00067 << "--install";
00068 if (_socksPort)
00069 args << "--socks-port=" + QString::number(_socksPort);
00070
00071 args << bundleInfoToString(bi);
00072
00073 vNotice("updater: launching auto-update executable: %1 %2")
00074 .arg(updateExecutable())
00075 .arg(args.join(" "));
00076 _currentBundle = bi;
00077 _currentCommand = InstallUpdates;
00078 start(updateExecutable(), args);
00079 }
00080
00081 void
00082 UpdateProcess::setSocksPort(quint16 port)
00083 {
00084 _socksPort = port;
00085 }
00086
00087 bool
00088 UpdateProcess::isRunning() const
00089 {
00090 return (state() != QProcess::NotRunning);
00091 }
00092
00093 void
00094 UpdateProcess::cancel()
00095 {
00096 if (_currentCommand == CheckForUpdates) {
00097 #if defined(Q_OS_WIN32)
00098 kill();
00099 #else
00100 terminate();
00101 #endif
00102 }
00103 }
00104
00105 void
00106 UpdateProcess::readStandardError()
00107 {
00108 int idx;
00109 bool ok;
00110 QString line, type;
00111 QHash<QString,QString> args;
00112
00113 setReadChannel(QProcess::StandardError);
00114 while (canReadLine()) {
00115 line = readLine().trimmed();
00116 vInfo("updater (stderr): %1").arg(line);
00117
00118 idx = line.indexOf(" ");
00119 if (idx < 0 || idx == line.length()-1)
00120 continue;
00121 type = line.mid(0, idx);
00122 line = line.mid(idx + 1);
00123
00124 args = string_parse_keyvals(line, &ok);
00125 if (! ok)
00126 continue;
00127 else if (line.startsWith("thandy.InstallFailed: ", Qt::CaseInsensitive)) {
00128
00129
00130
00131
00132
00133
00134 emit installUpdatesFailed(line);
00135 continue;
00136 }
00137
00138 if (! type.compare("CAN_INSTALL", Qt::CaseInsensitive)) {
00139 QString package = args.value("PKG");
00140 if (! package.isEmpty()) {
00141 PackageInfo pkgInfo = packageInfo(package);
00142 if (pkgInfo.isValid())
00143 _packageList << pkgInfo;
00144 }
00145 } else if (_currentCommand == CheckForUpdates
00146 && ! type.compare("DEBUG")
00147 && args.value("msg").startsWith("Got ")) {
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157 QStringList parts = args.value("msg").split(" ");
00158 if (parts.size() == 5) {
00159 QStringList progress = parts.at(1).split("/");
00160 if (progress.size() == 2) {
00161 int bytesReceived = progress.at(0).toUInt();
00162 int bytesTotal = progress.at(1).toUInt();
00163 vInfo("updater: Downloaded %1 of %2 bytes of file %3").arg(bytesReceived)
00164 .arg(bytesTotal)
00165 .arg(parts.at(4));
00166 emit downloadProgress(parts.at(4), bytesReceived, bytesTotal);
00167 }
00168 }
00169 }
00170 }
00171 }
00172
00173 void
00174 UpdateProcess::readStandardOutput()
00175 {
00176 QString line;
00177
00178 setReadChannel(QProcess::StandardOutput);
00179 while (canReadLine()) {
00180 line = readLine().trimmed();
00181 vInfo("updater (stdout): %1").arg(line);
00182 }
00183 }
00184
00185 void
00186 UpdateProcess::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
00187 {
00188 vInfo("updater: update process finished with exit code %1").arg(exitCode);
00189
00190 if (_currentCommand == CheckForUpdates) {
00191 if (exitStatus == QProcess::NormalExit) {
00192 emit updatesAvailable(_currentBundle, _packageList);
00193 } else {
00194 emit checkForUpdatesFailed(tr("Vidalia was unable to check for available "
00195 "software updates because Tor's update process "
00196 "exited unexpectedly."));
00197 }
00198 } else if (_currentCommand == InstallUpdates) {
00199 if (exitStatus == QProcess::NormalExit && exitCode == 0)
00200 emit updatesInstalled(_packageList.size());
00201 }
00202 _packageList.clear();
00203 }
00204
00205 void
00206 UpdateProcess::onError(QProcess::ProcessError error)
00207 {
00208 if (error == QProcess::FailedToStart) {
00209 vWarn("updater: failed to start");
00210 emit checkForUpdatesFailed(tr("Vidalia was unable to check for available "
00211 "software updates because it could not find "
00212 "'%1'.").arg(updateExecutable()));
00213 }
00214 }
00215
00216 int
00217 UpdateProcess::checkForUpdatesInterval()
00218 {
00219
00220 return 12*60*60;
00221 }
00222
00223 QDateTime
00224 UpdateProcess::nextCheckForUpdates(const QDateTime &lastCheckedAt)
00225 {
00226 return lastCheckedAt.addSecs(checkForUpdatesInterval()).toUTC();
00227 }
00228
00229 bool
00230 UpdateProcess::shouldCheckForUpdates(const QDateTime &lastCheckedAt)
00231 {
00232 QDateTime nextCheckAt = nextCheckForUpdates(lastCheckedAt);
00233 return (QDateTime::currentDateTime().toUTC() >= nextCheckAt);
00234 }
00235
00236 QString
00237 UpdateProcess::updateExecutable()
00238 {
00239 return "thandy.exe";
00240 }
00241
00242 QString
00243 UpdateProcess::updateRepositoryDir()
00244 {
00245 return QDir::convertSeparators(Vidalia::dataDirectory() + "/updates");
00246 }
00247
00248 QString
00249 UpdateProcess::bundleInfoToString(BundleInfo bi)
00250 {
00251 switch (bi) {
00252 case TorBundleInfo:
00253 return "/bundleinfo/tor/win32/";
00254 default:
00255 return QString();
00256 };
00257 }
00258
00259 PackageInfo
00260 UpdateProcess::packageInfo(const QString &package)
00261 {
00262 QProcess proc;
00263 QStringList args;
00264
00265 args << "json2xml"
00266 << QDir::convertSeparators(updateRepositoryDir() + "/" + package);
00267
00268 vNotice("updater: launching auto-update executable: %1 %2")
00269 .arg(updateExecutable())
00270 .arg(args.join(" "));
00271
00272 proc.setEnvironment(proc.systemEnvironment());
00273 proc.start(updateExecutable(), args);
00274 if (! proc.waitForStarted())
00275 return PackageInfo();
00276 if (! proc.waitForFinished())
00277 return PackageInfo();
00278 return packageInfoFromXml(proc.readAll());
00279 }
00280
00281 PackageInfo
00282 UpdateProcess::packageInfoFromXml(const QByteArray &xml)
00283 {
00284 QDomDocument doc;
00285 QDomElement dict, elem;
00286 QDomNodeList nodeList;
00287 QString errmsg;
00288 QStringList versionParts;
00289 PackageInfo pkgInfo;
00290
00291 if (! doc.setContent(xml, false, &errmsg, 0, 0))
00292 goto err;
00293
00294
00295
00296
00297
00298 elem = doc.documentElement().firstChildElement("signed");
00299 if (elem.isNull()) {
00300 errmsg = "Signed element not found";
00301 goto err;
00302 }
00303
00304 dict = elem.firstChildElement("dict");
00305 if (dict.isNull()) {
00306 errmsg = "no Dict element as a child of Signed";
00307 goto err;
00308 }
00309
00310 elem = dict.firstChildElement("name");
00311 if (elem.isNull()) {
00312 errmsg = "Name element not found";
00313 goto err;
00314 }
00315 pkgInfo.setName(elem.text());
00316
00317 elem = dict.firstChildElement("version").firstChildElement("list");
00318 if (elem.isNull()) {
00319 errmsg = "no valid Version element found";
00320 goto err;
00321 }
00322 elem = elem.firstChildElement("item");
00323 for ( ; ! elem.isNull(); elem = elem.nextSiblingElement("item")) {
00324 versionParts << elem.text();
00325 }
00326 pkgInfo.setVersion(versionParts.join("."));
00327
00328 elem = dict.firstChildElement("shortdesc").firstChildElement("dict");
00329 if (elem.isNull()) {
00330 errmsg = "no valid Shortdesc element found";
00331 goto err;
00332 }
00333 elem = elem.firstChildElement();
00334 for ( ; ! elem.isNull(); elem = elem.nextSiblingElement()) {
00335 pkgInfo.setShortDescription(elem.tagName(), elem.text());
00336 }
00337
00338 elem = dict.firstChildElement("longdesc").firstChildElement("dict");
00339 if (elem.isNull()) {
00340 errmsg = "no valid Longdesc element found";
00341 goto err;
00342 }
00343 elem = elem.firstChildElement();
00344 for ( ; ! elem.isNull(); elem = elem.nextSiblingElement()) {
00345 pkgInfo.setLongDescription(elem.tagName(), elem.text());
00346 }
00347
00348 return pkgInfo;
00349
00350 err:
00351 vWarn("updater: invalid package info XML document: %1").arg(errmsg);
00352 return PackageInfo();
00353 }
00354