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 
38 void
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 
60 void
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 
81 void
83 {
84  _socksPort = port;
85 }
86 
87 bool
89 {
90  return (state() != QProcess::NotRunning);
91 }
92 
93 void
95 {
97 #if defined(Q_OS_WIN32)
98  kill();
99 #else
100  terminate();
101 #endif
102  }
103 }
104 
105 void
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 
173 void
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 
185 void
186 UpdateProcess::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 
205 void
206 UpdateProcess::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 
216 int
218 {
219  /* XXX: Check twice a day. Why? Because arma said so. */
220  return 12*60*60;
221 }
222 
223 QDateTime
224 UpdateProcess::nextCheckForUpdates(const QDateTime &lastCheckedAt)
225 {
226  return lastCheckedAt.addSecs(checkForUpdatesInterval()).toUTC();
227 }
228 
229 bool
230 UpdateProcess::shouldCheckForUpdates(const QDateTime &lastCheckedAt)
231 {
232  QDateTime nextCheckAt = nextCheckForUpdates(lastCheckedAt);
233  return (QDateTime::currentDateTime().toUTC() >= nextCheckAt);
234 }
235 
236 QString
238 {
239  return "thandy.exe";
240 }
241 
242 QString
244 {
245  return QDir::convertSeparators(Vidalia::dataDirectory() + "/updates");
246 }
247 
248 QString
250 {
251  switch (bi) {
252  case TorBundleInfo:
253  return "/bundleinfo/tor/win32/";
254  default:
255  return QString();
256  };
257 }
258 
260 UpdateProcess::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 
282 UpdateProcess::packageInfoFromXml(const QByteArray &xml)
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 
350 err:
351  vWarn("updater: invalid package info XML document: %1").arg(errmsg);
352  return PackageInfo();
353 }
354 
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
void updatesAvailable(UpdateProcess::BundleInfo bi, PackageList packages)
void readStandardError()
static int checkForUpdatesInterval()
bool err(QString *str, const QString &errmsg)
Definition: stringutil.cpp:37
void setSocksPort(quint16 port)
bool isRunning() const
bool isValid() const
Definition: PackageInfo.cpp:25
void installUpdatesFailed(QString errmsg)
void setLongDescription(const QString &lang, const QString &desc)
Definition: PackageInfo.cpp:55
void readStandardOutput()
QHash< QString, QString > string_parse_keyvals(const QString &str, bool *ok)
Definition: stringutil.cpp:244
static PackageInfo packageInfo(const QString &package)
#define vInfo(fmt)
Definition: Vidalia.h:40
static QString updateRepositoryDir()
void checkForUpdatesFailed(QString errmsg)
void onFinished(int exitCode, QProcess::ExitStatus exitStatus)
PackageList _packageList
void setVersion(const QString &version)
Definition: PackageInfo.cpp:43
static PackageInfo packageInfoFromXml(const QByteArray &xml)
#define vNotice(fmt)
Definition: Vidalia.h:41
quint16 _socksPort
void updatesInstalled(int nPackagesInstalled)
stop errmsg connect(const QHostAddress &address, quint16 port)
UpdateProcess(QObject *parent=0)
void installUpdates(BundleInfo bi)
static QString dataDirectory()
Definition: Vidalia.cpp:355
void onError(QProcess::ProcessError error)
#define vWarn(fmt)
Definition: Vidalia.h:42
void setName(const QString &name)
Definition: PackageInfo.cpp:31
QString bundleInfoToString(BundleInfo bundleInfo)
BundleInfo _currentBundle
static bool shouldCheckForUpdates(const QDateTime &lastCheckedAt)
void downloadProgress(QString url, int bytesReceived, int bytesTotal)
UpdateCommand _currentCommand
static QDateTime nextCheckForUpdates(const QDateTime &lastCheckedAt)
static QString updateExecutable()
void setShortDescription(const QString &lang, const QString &desc)
Definition: PackageInfo.cpp:73
void checkForUpdates(BundleInfo bi)