Vidalia 0.3.1
UpdateProcess.cpp
Go to the documentation of this file.
1/*
2** This file is part of Vidalia, and is subject to the license terms in the
3** LICENSE file, found in the top level directory of this distribution. If you
4** did not receive the LICENSE file with this file, you may obtain it from the
5** Vidalia source package distributed by the Vidalia Project at
6** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7** including this file, may be copied, modified, propagated, or distributed
8** except according to the terms described in the LICENSE file.
9*/
10
11#include "UpdateProcess.h"
12#include "Vidalia.h"
13
14#include "stringutil.h"
15
16#include <QDir>
17#include <QDomDocument>
18#include <QDomElement>
19#include <QDomNodeList>
20
21
23 : QProcess(parent)
24{
26 _socksPort = 0;
27
28 connect(this, SIGNAL(readyReadStandardError()),
29 this, SLOT(readStandardError()));
30 connect(this, SIGNAL(readyReadStandardOutput()),
31 this, SLOT(readStandardOutput()));
32 connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
33 this, SLOT(onFinished(int, QProcess::ExitStatus)));
34
35 setEnvironment(systemEnvironment());
36}
37
38void
40{
41 QStringList args;
42
43 args << "update" << "--force-check"
44 << " --controller-log-format"
45 << "--repo=" + updateRepositoryDir()
46 << "--debug";
47 if (_socksPort)
48 args << "--socks-port=" + QString::number(_socksPort);
49
50 args << bundleInfoToString(bi);
51
52 vNotice("updater: launching auto-update executable: %1 %2")
53 .arg(updateExecutable())
54 .arg(args.join(" "));
55 _currentBundle = bi;
57 start(updateExecutable(), args);
58}
59
60void
62{
63 QStringList args;
64
65 args << "update" << "--controller-log-format"
66 << "--repo=" + updateRepositoryDir()
67 << "--install";
68 if (_socksPort)
69 args << "--socks-port=" + QString::number(_socksPort);
70
71 args << bundleInfoToString(bi);
72
73 vNotice("updater: launching auto-update executable: %1 %2")
74 .arg(updateExecutable())
75 .arg(args.join(" "));
76 _currentBundle = bi;
78 start(updateExecutable(), args);
79}
80
81void
83{
84 _socksPort = port;
85}
86
87bool
89{
90 return (state() != QProcess::NotRunning);
91}
92
93void
95{
97#if defined(Q_OS_WIN32)
98 kill();
99#else
100 terminate();
101#endif
102 }
103}
104
105void
107{
108 int idx;
109 bool ok;
110 QString line, type;
111 QHash<QString,QString> args;
112
113 setReadChannel(QProcess::StandardError);
114 while (canReadLine()) {
115 line = readLine().trimmed();
116 vInfo("updater (stderr): %1").arg(line);
117
118 idx = line.indexOf(" ");
119 if (idx < 0 || idx == line.length()-1)
120 continue;
121 type = line.mid(0, idx);
122 line = line.mid(idx + 1);
123
124 args = string_parse_keyvals(line, &ok);
125 if (! ok)
126 continue;
127 else if (line.startsWith("thandy.InstallFailed: ", Qt::CaseInsensitive)) {
128 /** XXX: This is a fucking kludge. If installation fails, Thandy just
129 * dumps a Python traceback that (for obvious reasons) doesn't
130 * follow the expected format. There isn't a defined control
131 * message type for this yet we'd really like the error, so treat
132 * this one specially.
133 */
134 emit installUpdatesFailed(line);
135 continue;
136 }
137
138 if (! type.compare("CAN_INSTALL", Qt::CaseInsensitive)) {
139 QString package = args.value("PKG");
140 if (! package.isEmpty()) {
141 PackageInfo pkgInfo = packageInfo(package);
142 if (pkgInfo.isValid())
143 _packageList << pkgInfo;
144 }
145 } else if (_currentCommand == CheckForUpdates
146 && ! type.compare("DEBUG")
147 && args.value("msg").startsWith("Got ")) {
148 /* XXX: This is an even worse fucking kludge. Thandy only reports
149 * download progress in a not-so-parser-friendly log message,
150 * though, so we must kludge again.
151 *
152 * Here's an example of what we're parsing:
153 * "Got 1666048/1666560 bytes from http://updates.torproject.org/thandy/data/win32/tor-0.2.1.9-alpha.msi"
154 *
155 * (Note that the kludge above would even match on "Got milk?".)
156 */
157 QStringList parts = args.value("msg").split(" ");
158 if (parts.size() == 5) {
159 QStringList progress = parts.at(1).split("/");
160 if (progress.size() == 2) {
161 int bytesReceived = progress.at(0).toUInt();
162 int bytesTotal = progress.at(1).toUInt();
163 vInfo("updater: Downloaded %1 of %2 bytes of file %3").arg(bytesReceived)
164 .arg(bytesTotal)
165 .arg(parts.at(4));
166 emit downloadProgress(parts.at(4), bytesReceived, bytesTotal);
167 }
168 }
169 }
170 }
171}
172
173void
175{
176 QString line;
177
178 setReadChannel(QProcess::StandardOutput);
179 while (canReadLine()) {
180 line = readLine().trimmed();
181 vInfo("updater (stdout): %1").arg(line);
182 }
183}
184
185void
186UpdateProcess::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
187{
188 vInfo("updater: update process finished with exit code %1").arg(exitCode);
189
191 if (exitStatus == QProcess::NormalExit) {
193 } else {
194 emit checkForUpdatesFailed(tr("Vidalia was unable to check for available "
195 "software updates because Tor's update process "
196 "exited unexpectedly."));
197 }
198 } else if (_currentCommand == InstallUpdates) {
199 if (exitStatus == QProcess::NormalExit && exitCode == 0)
200 emit updatesInstalled(_packageList.size());
201 }
202 _packageList.clear();
203}
204
205void
206UpdateProcess::onError(QProcess::ProcessError error)
207{
208 if (error == QProcess::FailedToStart) {
209 vWarn("updater: failed to start");
210 emit checkForUpdatesFailed(tr("Vidalia was unable to check for available "
211 "software updates because it could not find "
212 "'%1'.").arg(updateExecutable()));
213 }
214}
215
216int
218{
219 /* XXX: Check twice a day. Why? Because arma said so. */
220 return 12*60*60;
221}
222
223QDateTime
224UpdateProcess::nextCheckForUpdates(const QDateTime &lastCheckedAt)
225{
226 return lastCheckedAt.addSecs(checkForUpdatesInterval()).toUTC();
227}
228
229bool
230UpdateProcess::shouldCheckForUpdates(const QDateTime &lastCheckedAt)
231{
232 QDateTime nextCheckAt = nextCheckForUpdates(lastCheckedAt);
233 return (QDateTime::currentDateTime().toUTC() >= nextCheckAt);
234}
235
236QString
238{
239 return "thandy.exe";
240}
241
242QString
244{
245 return QDir::convertSeparators(Vidalia::dataDirectory() + "/updates");
246}
247
248QString
250{
251 switch (bi) {
252 case TorBundleInfo:
253 return "/bundleinfo/tor/win32/";
254 default:
255 return QString();
256 };
257}
258
260UpdateProcess::packageInfo(const QString &package)
261{
262 QProcess proc;
263 QStringList args;
264
265 args << "json2xml"
266 << QDir::convertSeparators(updateRepositoryDir() + "/" + package);
267
268 vNotice("updater: launching auto-update executable: %1 %2")
269 .arg(updateExecutable())
270 .arg(args.join(" "));
271
272 proc.setEnvironment(proc.systemEnvironment());
273 proc.start(updateExecutable(), args);
274 if (! proc.waitForStarted())
275 return PackageInfo();
276 if (! proc.waitForFinished())
277 return PackageInfo();
278 return packageInfoFromXml(proc.readAll());
279}
280
283{
284 QDomDocument doc;
285 QDomElement dict, elem;
286 QDomNodeList nodeList;
287 QString errmsg;
288 QStringList versionParts;
289 PackageInfo pkgInfo;
290
291 if (! doc.setContent(xml, false, &errmsg, 0, 0))
292 goto err;
293
294 /* XXX: Qt 4.4 introduced XPath support, which would make the following
295 * parsing much easier. Whenever we drop support for Qt < 4.4, this should
296 * be updated.
297 */
298 elem = doc.documentElement().firstChildElement("signed");
299 if (elem.isNull()) {
300 errmsg = "Signed element not found";
301 goto err;
302 }
303
304 dict = elem.firstChildElement("dict");
305 if (dict.isNull()) {
306 errmsg = "no Dict element as a child of Signed";
307 goto err;
308 }
309
310 elem = dict.firstChildElement("name");
311 if (elem.isNull()) {
312 errmsg = "Name element not found";
313 goto err;
314 }
315 pkgInfo.setName(elem.text());
316
317 elem = dict.firstChildElement("version").firstChildElement("list");
318 if (elem.isNull()) {
319 errmsg = "no valid Version element found";
320 goto err;
321 }
322 elem = elem.firstChildElement("item");
323 for ( ; ! elem.isNull(); elem = elem.nextSiblingElement("item")) {
324 versionParts << elem.text();
325 }
326 pkgInfo.setVersion(versionParts.join("."));
327
328 elem = dict.firstChildElement("shortdesc").firstChildElement("dict");
329 if (elem.isNull()) {
330 errmsg = "no valid Shortdesc element found";
331 goto err;
332 }
333 elem = elem.firstChildElement();
334 for ( ; ! elem.isNull(); elem = elem.nextSiblingElement()) {
335 pkgInfo.setShortDescription(elem.tagName(), elem.text());
336 }
337
338 elem = dict.firstChildElement("longdesc").firstChildElement("dict");
339 if (elem.isNull()) {
340 errmsg = "no valid Longdesc element found";
341 goto err;
342 }
343 elem = elem.firstChildElement();
344 for ( ; ! elem.isNull(); elem = elem.nextSiblingElement()) {
345 pkgInfo.setLongDescription(elem.tagName(), elem.text());
346 }
347
348 return pkgInfo;
349
350err:
351 vWarn("updater: invalid package info XML document: %1").arg(errmsg);
352 return PackageInfo();
353}
354
stop errmsg connect(const QHostAddress &address, quint16 port)
#define vNotice(fmt)
Definition: Vidalia.h:41
#define vWarn(fmt)
Definition: Vidalia.h:42
#define vInfo(fmt)
Definition: Vidalia.h:40
void setShortDescription(const QString &lang, const QString &desc)
Definition: PackageInfo.cpp:73
void setName(const QString &name)
Definition: PackageInfo.cpp:31
void setLongDescription(const QString &lang, const QString &desc)
Definition: PackageInfo.cpp:55
void setVersion(const QString &version)
Definition: PackageInfo.cpp:43
bool isValid() const
Definition: PackageInfo.cpp:25
BundleInfo _currentBundle
void updatesAvailable(UpdateProcess::BundleInfo bi, PackageList packages)
static PackageInfo packageInfoFromXml(const QByteArray &xml)
UpdateProcess(QObject *parent=0)
quint16 _socksPort
static int checkForUpdatesInterval()
void checkForUpdates(BundleInfo bi)
UpdateCommand _currentCommand
void installUpdatesFailed(QString errmsg)
void setSocksPort(quint16 port)
static QString updateExecutable()
void onFinished(int exitCode, QProcess::ExitStatus exitStatus)
void installUpdates(BundleInfo bi)
static PackageInfo packageInfo(const QString &package)
void onError(QProcess::ProcessError error)
void readStandardError()
QString bundleInfoToString(BundleInfo bundleInfo)
void readStandardOutput()
static QString updateRepositoryDir()
void updatesInstalled(int nPackagesInstalled)
static QDateTime nextCheckForUpdates(const QDateTime &lastCheckedAt)
bool isRunning() const
void checkForUpdatesFailed(QString errmsg)
void downloadProgress(QString url, int bytesReceived, int bytesTotal)
PackageList _packageList
static bool shouldCheckForUpdates(const QDateTime &lastCheckedAt)
static QString dataDirectory()
Definition: Vidalia.cpp:355
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
bool err(QString *str, const QString &errmsg)
Definition: stringutil.cpp:37
QHash< QString, QString > string_parse_keyvals(const QString &str, bool *ok)
Definition: stringutil.cpp:244