updateinstaller.cpp
1 //------------------------------------------------------------------------------
2 // updateinstaller.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2012 "Zalewa" <zalewapl@gmail.com>
22 //------------------------------------------------------------------------------
23 #include "updateinstaller.h"
24 
25 #include "application.h"
26 #include "configuration/doomseekerconfig.h"
27 #include "datapaths.h"
28 #include "log.h"
29 #include "main.h"
30 #include "strings.hpp"
31 #include "updater/autoupdater.h"
32 #include <QDir>
33 #include <QFile>
34 #include <QFileInfo>
35 #include <QProcess>
36 #include <QTemporaryFile>
37 
38 #ifdef Q_OS_WIN32
39 const QString UPDATER_EXECUTABLE_FILENAME = "updater.exe";
40 #else
41 const QString UPDATER_EXECUTABLE_FILENAME = "updater";
42 #endif
43 
44 UpdateInstaller::UpdateInstaller(QObject *pParent)
45  : QObject(pParent)
46 {
47 }
48 
49 UpdateInstaller::~UpdateInstaller()
50 {
51 }
52 
53 QString UpdateInstaller::copyUpdaterExecutableToTemporarySpace()
54 {
55  // Windows will complain if we try to overwrite an executable
56  // of a running process. To be able to update the updater itself,
57  // we need to copy it out to somewhere else and then launch this clone.
58  QString updaterProgramPath = Strings::combinePaths(
59  QCoreApplication::applicationDirPath(),
60  UPDATER_EXECUTABLE_FILENAME);
61 
62  // Copying the file to the same directory as the packages, and prefixing
63  // its filename with the same prefix as packages, will ensure
64  // that the main program will remove the cloned updater once
65  // update is finished.
66  QString updaterCloneFilename = QString("%1-%2").arg(
67  DataPaths::UPDATE_PACKAGE_FILENAME_PREFIX).arg(
68  UPDATER_EXECUTABLE_FILENAME);
69  QString clonePath = Strings::combinePaths(
70  AutoUpdater::updateStorageDirPath(), updaterCloneFilename);
71 
72  if (QFile::copy(updaterProgramPath, clonePath))
73  {
74  bool bPermissionsSet = QFile::setPermissions(clonePath,
75  QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner
76  | QFile::ExeGroup | QFile::ReadGroup | QFile::ReadOther);
77  if (bPermissionsSet)
78  {
79  return clonePath;
80  }
81  }
82  gLog << tr("Failed to copy the updater executable to a temporary"
83  " space: \"%1\" -> \"%2\".").arg(updaterProgramPath, clonePath);
84  return QString();
85 }
86 
87 QString UpdateInstaller::errorCodeToStr(ErrorCode code)
88 {
89  switch (code)
90  {
91  case EC_Ok:
92  return tr("Ok");
93  case EC_NothingToUpdate:
94  return tr("Nothing to update.");
95  case EC_UpdatePackageMissing:
96  return tr("Update package or script are not found. Check log for details.");
97  case EC_ProcessStartFailure:
98  return tr("Failed to start updater process.");
99  default:
100  return tr("Unknown error: %1.").arg(code);
101  }
102 }
103 
104 QString UpdateInstaller::getPercentEncodedCurrentProcessArgs()
105 {
106  const QStringList &originalArgs = gApp->originalArgs();
107  QStringList argsEncoded;
108  for (int i = 1; i < originalArgs.size(); ++i)
109  {
110  const QString &arg = originalArgs[i];
111  // Skip this one because it is produced by the updater.
112  if (arg == "--update-failed")
113  {
114  i++; // also skip its value
115  continue;
116  }
117  argsEncoded << QUrl::toPercentEncoding(arg);
118  }
119  return argsEncoded.join(" ");
120 }
121 
122 QString UpdateInstaller::processErrorCodeToStr(ProcessErrorCode code)
123 {
124  switch (code)
125  {
126  case PEC_Ok:
127  return tr("Ok");
128  case PEC_UnableToReadUpdateScript:
129  return tr("Unable to read the update script.");
130  case PEC_NoInstallationDirectorySpecified:
131  return tr("No installation directory specified.");
132  case PEC_UnableToDeterminePathOfUpdater:
133  return tr("Unable to determine the path of the updater.");
134  case PEC_GeneralFailure:
135  return tr("General failure.");
136  default:
137  return tr("Unknown process error code: %1.").arg(code);
138  }
139 }
140 
142 {
143  QString scriptPath = AutoUpdater::updaterScriptPath();
144  QFile fileScript(scriptPath);
145  if (fileScript.exists())
146  {
147  gLog << tr("Installing update.");
148  QString packagesDirPath = AutoUpdater::updateStorageDirPath();
149  QDir packagesDir(packagesDirPath);
150  // Do some "does file exist" validation. This isn't 100% reliable but
151  // should work correctly in most cases.
152  bool isPackageOk = packagesDir.exists();
153  if (isPackageOk)
154  {
155  if (!startUpdaterProcess(packagesDirPath, scriptPath))
156  {
157  return EC_ProcessStartFailure;
158  }
159  }
160  else
161  {
162  gLog << tr("Package directory \"%1\" doesn't exist.").arg(packagesDirPath);
163  return EC_UpdatePackageMissing;
164  }
165  }
166  else
167  {
168  gLog << tr("Update was about to be installed but "
169  "update script \"%1\" is missing.").arg(scriptPath);
170  return EC_UpdatePackageMissing;
171  }
172  return EC_Ok;
173 }
174 
175 bool UpdateInstaller::startUpdaterProcess(const QString &packagesDir,
176  const QString &scriptFilePath)
177 {
178  QString updaterProgramPath = copyUpdaterExecutableToTemporarySpace();
179  if (updaterProgramPath.isEmpty())
180  {
181  return false;
182  }
183  qDebug() << "Updater program is located at path: " << updaterProgramPath;
184  QFile updaterProgramFile(updaterProgramPath);
185  QFileInfo programFileInfo(QCoreApplication::applicationFilePath());
186  QStringList args;
187  #ifdef Q_OS_DARWIN
188  // On Mac we're updating the bundle, but we get the location of the binary (<stuff>/Contents/MacOS/)
189  args << "--install-dir" << (QCoreApplication::applicationDirPath() + "/../..");
190  #else
191  args << "--install-dir" << QCoreApplication::applicationDirPath();
192  #endif
193  args << "--package-dir" << packagesDir;
194  args << "--script" << scriptFilePath;
195  args << "--log" << QDir::toNativeSeparators(AutoUpdater::updaterLogPath());
196  args << "--exec" << QDir::toNativeSeparators(programFileInfo.absoluteFilePath());
197  QString currentProcessArgs = getPercentEncodedCurrentProcessArgs();
198  if (!currentProcessArgs.isEmpty())
199  {
200  args << "--args" << currentProcessArgs;
201  }
202  bool bStarted = QProcess::startDetached(updaterProgramPath, args);
203  if (!bStarted)
204  {
205  gLog << tr("Failed to start updater process: %1 %2")
206  .arg(updaterProgramPath).arg(args.join(" "));
207  }
208  return bStarted;
209 }