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