text: #include <QFile>
  #include <QMessageBox>
  #include <FileSystem.h>
  #include <updater/GoUpdate.h>
  #include "UpdateController.h"
  #include <QApplication>
  #include <thread>
  #include <chrono>
  #include <LocalPeer.h>
  // from <sys/stat.h>
  #ifndef S_IRUSR
  #define __S_IREAD 0400         /* Read by owner.  */
  #define __S_IWRITE 0200        /* Write by owner.  */
  #define __S_IEXEC 0100         /* Execute by owner.  */
  #define S_IRUSR __S_IREAD      /* Read by owner.  */
  #define S_IWUSR __S_IWRITE     /* Write by owner.  */
  #define S_IXUSR __S_IEXEC      /* Execute by owner.  */
  
  #define S_IRGRP (S_IRUSR >> 3) /* Read by group.  */
  #define S_IWGRP (S_IWUSR >> 3) /* Write by group.  */
  #define S_IXGRP (S_IXUSR >> 3) /* Execute by group.  */
  
  #define S_IROTH (S_IRGRP >> 3) /* Read by others.  */
  #define S_IWOTH (S_IWGRP >> 3) /* Write by others.  */
  #define S_IXOTH (S_IXGRP >> 3) /* Execute by others.  */
  #endif
  static QFile::Permissions unixModeToPermissions(const int mode)
  {
      QFile::Permissions perms;
  
      if (mode & S_IRUSR)
      {
          perms |= QFile::ReadUser;
      }
      if (mode & S_IWUSR)
      {
          perms |= QFile::WriteUser;
      }
      if (mode & S_IXUSR)
      {
          perms |= QFile::ExeUser;
      }
  
      if (mode & S_IRGRP)
      {
          perms |= QFile::ReadGroup;
      }
      if (mode & S_IWGRP)
      {
          perms |= QFile::WriteGroup;
      }
      if (mode & S_IXGRP)
      {
          perms |= QFile::ExeGroup;
      }
  
      if (mode & S_IROTH)
      {
          perms |= QFile::ReadOther;
      }
      if (mode & S_IWOTH)
      {
          perms |= QFile::WriteOther;
      }
      if (mode & S_IXOTH)
      {
          perms |= QFile::ExeOther;
      }
      return perms;
  }
  
  static const QLatin1String liveCheckFile("live.check");
  
  UpdateController::UpdateController(QWidget * parent, const QString& root, const QString updateFilesDir, GoUpdate::OperationList operations)
  {
      m_parent = parent;
      m_root = root;
      m_updateFilesDir = updateFilesDir;
      m_operations = operations;
  }
  
  
  void UpdateController::installUpdates()
  {
      qint64 pid = -1;
      QStringList args;
      bool started = false;
  
      qDebug() << "Installing updates.";
  #ifdef Q_OS_WIN
      QString finishCmd = QApplication::applicationFilePath();
  #elif defined Q_OS_LINUX || defined Q_OS_FREEBSD
      QString finishCmd = FS::PathCombine(m_root, "BSDMC");
  #elif defined Q_OS_MAC
      QString finishCmd = QApplication::applicationFilePath();
  #else
  #error Unsupported operating system.
  #endif
  
      QString backupPath = FS::PathCombine(m_root, "update", "backup");
      QDir origin(m_root);
  
      // clean up the backup folder. it should be empty before we start
      if(!FS::deletePath(backupPath))
      {
          qWarning() << "couldn't remove previous backup folder" << backupPath;
      }
      // and it should exist.
      if(!FS::ensureFolderPathExists(backupPath))
      {
          qWarning() << "couldn't create folder" << backupPath;
          return;
      }
  
      bool useXPHack = false;
      QString exePath;
      QString exeOrigin;
      QString exeBackup;
  
      // perform the update operations
      for(auto op: m_operations)
      {
          switch(op.type)
          {
              // replace = move original out to backup, if it exists, move the new file in its place
              case GoUpdate::Operation::OP_REPLACE:
              {
  #ifdef Q_OS_WIN32
                  // hack for people renaming the .exe because ... reasons :)
                  if(op.destination == "BSDMC.exe")
                  {
                      op.destination = QFileInfo(QApplication::applicationFilePath()).fileName();
                  }
  #endif
                  QFileInfo destination (FS::PathCombine(m_root, op.destination));
  #ifdef Q_OS_WIN32
                  if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA)
                  {
                      if(destination.fileName() == "BSDMC.exe")
                      {
                          QDir rootDir(m_root);
                          exeOrigin = rootDir.relativeFilePath(op.source);
                          exePath = rootDir.relativeFilePath(op.destination);
                          exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName()));
                          useXPHack = true;
                          continue;
                      }
                  }
  #endif
                  if(destination.exists())
                  {
                      QString backupName = op.destination;
                      backupName.replace('/', '_');
                      QString backupFilePath = FS::PathCombine(backupPath, backupName);
                      if(!QFile::rename(destination.absoluteFilePath(), backupFilePath))
                      {
                          qWarning() << "Couldn't move:" << destination.absoluteFilePath() << "to" << backupFilePath;
                          m_failedOperationType = Replace;
                          m_failedFile = op.destination;
                          fail();
                          return;
                      }
                      BackupEntry be;
                      be.original = destination.absoluteFilePath();
                      be.backup = backupFilePath;
                      be.update = op.source;
                      m_replace_backups.append(be);
                  }
                  // make sure the folder we are putting this into exists
                  if(!FS::ensureFilePathExists(destination.absoluteFilePath()))
                  {
                      qWarning() << "REPLACE: Couldn't create folder:" << destination.absoluteFilePath();
                      m_failedOperationType = Replace;
                      m_failedFile = op.destination;
                      fail();
                      return;
                  }
                  // now move the new file in
                  if(!QFile::rename(op.source, destination.absoluteFilePath()))
                  {
                      qWarning() << "REPLACE: Couldn't move:" << op.source << "to" << destination.absoluteFilePath();
                      m_failedOperationType = Replace;
                      m_failedFile = op.destination;
                      fail();
                      return;
                  }
                  QFile::setPermissions(destination.absoluteFilePath(), unixModeToPermissions(op.destinationMode));
              }
              break;
              // delete = move original to backup
              case GoUpdate::Operation::OP_DELETE:
              {
                  QString destFilePath = FS::PathCombine(m_root, op.destination);
                  if(QFile::exists(destFilePath))
                  {
                      QString backupName = op.destination;
                      backupName.replace('/', '_');
                      QString trashFilePath = FS::PathCombine(backupPath, backupName);
  
                      if(!QFile::rename(destFilePath, trashFilePath))
                      {
                          qWarning() << "DELETE: Couldn't move:" << op.destination << "to" << trashFilePath;
                          m_failedFile = op.destination;
                          m_failedOperationType = Delete;
                          fail();
                          return;
                      }
                      BackupEntry be;
                      be.original = destFilePath;
                      be.backup = trashFilePath;
                      m_delete_backups.append(be);
                  }
              }
              break;
          }
      }
  
      // try to start the new binary
      args = qApp->arguments();
      args.removeFirst();
  
      // on old Windows, do insane things... no error checking here, this is just to have something.
      if(useXPHack)
      {
          QString script;
          auto nativePath = QDir::toNativeSeparators(exePath);
          auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin);
          auto nativeBackupPath = QDir::toNativeSeparators(exeBackup);
  
          // so we write this vbscript thing...
          QTextStream out(&script);
          out << "WScript.Sleep 1000\n";
          out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n";
          out << "Set shell=CreateObject(\"WScript.Shell\")\n";
          out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n";
          out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n";
          out << "shell.Run \"" << nativePath << "\"\n";
  
          QString scriptPath = FS::PathCombine(m_root, "update", "update.vbs");
  
          // we save it
          QFile scriptFile(scriptPath);
          scriptFile.open(QIODevice::WriteOnly);
          scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n"));
          scriptFile.close();
  
          // we run it
          started = QProcess::startDetached("wscript", {scriptPath}, m_root);
  
          // and we quit. conscious thought.
          qApp->quit();
          return;
      }
      bool doLiveCheck = true;
      bool startFailed = false;
  
      // remove live check file, if any
      if(QFile::exists(liveCheckFile))
      {
          if(!QFile::remove(liveCheckFile))
          {
              qWarning() << "Couldn't remove the" << liveCheckFile << "file! We will proceed without :(";
              doLiveCheck = false;
          }
      }
  
      if(doLiveCheck)
      {
          if(!args.contains("--alive"))
          {
              args.append("--alive");
          }
      }
  
      // FIXME: reparse args and construct a safe variant from scratch. This is a workaround for GH-1874:
      QStringList realargs;
      int skip = 0;
      for(auto & arg: args)
      {
          if(skip)
          {
              skip--;
              continue;
          }
          if(arg == "-l")
          {
              skip = 1;
              continue;
          }
          realargs.append(arg);
      }
  
      // start the updated application
      started = QProcess::startDetached(finishCmd, realargs, QDir::currentPath(), &pid);
      // much dumber check - just find out if the call
      if(!started || pid == -1)
      {
          qWarning() << "Couldn't start new process properly!";
          startFailed = true;
      }
      if(!startFailed && doLiveCheck)
      {
          int attempts = 0;
          while(attempts < 10)
          {
              attempts++;
              QString key;
              std::this_thread::sleep_for(std::chrono::milliseconds(250));
              if(!QFile::exists(liveCheckFile))
              {
                  qWarning() << "Couldn't find the" << liveCheckFile << "file!";
                  startFailed = true;
                  continue;
              }
              try
              {
                  key = QString::fromUtf8(FS::read(liveCheckFile));
                  auto id = ApplicationId::fromRawString(key);
                  LocalPeer peer(nullptr, id);
                  if(peer.isClient())
                  {
                      startFailed = false;
                      qDebug() << "Found process started with key " << key;
                      break;
                  }
                  else
                  {
                      startFailed = true;
                      qDebug() << "Process started with key " << key << "apparently died or is not reponding...";
                      break;
                  }
              }
              catch (const Exception &e)
              {
                  qWarning() << "Couldn't read the" << liveCheckFile << "file!";
                  startFailed = true;
                  continue;
              }
          }
      }
      if(startFailed)
      {
          m_failedOperationType = Start;
          fail();
          return;
      }
      else
      {
          origin.rmdir(m_updateFilesDir);
          qApp->quit();
          return;
      }
  }
  
  void UpdateController::fail()
  {
      qWarning() << "Update failed!";
  
      QString msg;
      bool doRollback = false;
      QString failTitle = QObject::tr("Update failed!");
      QString rollFailTitle = QObject::tr("Rollback failed!");
      switch (m_failedOperationType)
      {
          case Replace:
          {
              msg = QObject::tr("Couldn't replace file %1. Changes will be reverted.\n"
                  "See the BSDMC log file for details.").arg(m_failedFile);
              doRollback = true;
              QMessageBox::critical(m_parent, failTitle, msg);
              break;
          }
          case Delete:
          {
              msg = QObject::tr("Couldn't remove file %1. Changes will be reverted.\n"
                  "See the BSDMC log file for details.").arg(m_failedFile);
              doRollback = true;
              QMessageBox::critical(m_parent, failTitle, msg);
              break;
          }
          case Start:
          {
              msg = QObject::tr("The new version didn't start or is too old and doesn't respond to startup checks.\n"
                  "\n"
                  "Roll back to previous version?");
              auto result = QMessageBox::critical(
                  m_parent,
                  failTitle,
                  msg,
                  QMessageBox::Yes | QMessageBox::No,
                  QMessageBox::Yes
              );
              doRollback = (result == QMessageBox::Yes);
              break;
          }
          case Nothing:
          default:
              return;
      }
      if(doRollback)
      {
          auto rollbackOK = rollback();
          if(!rollbackOK)
          {
              msg = QObject::tr("The rollback failed too.\n"
                  "You will have to repair BSDMC manually.\n"
                  "Please let us know why and how this happened.").arg(m_failedFile);
              QMessageBox::critical(m_parent, rollFailTitle, msg);
              qApp->quit();
          }
      }
      else
      {
          qApp->quit();
      }
  }
  
  bool UpdateController::rollback()
  {
      bool revertOK = true;
      // if the above failed, roll back changes
      for(auto backup:m_replace_backups)
      {
          qWarning() << "restoring" << backup.original << "from" << backup.backup;
          if(!QFile::rename(backup.original, backup.update))
          {
              revertOK = false;
              qWarning() << "moving new" << backup.original << "back to" << backup.update << "failed!";
              continue;
          }
  
          if(!QFile::rename(backup.backup, backup.original))
          {
              revertOK = false;
              qWarning() << "restoring" << backup.original << "failed!";
          }
      }
      for(auto backup:m_delete_backups)
      {
          qWarning() << "restoring" << backup.original << "from" << backup.backup;
          if(!QFile::rename(backup.backup, backup.original))
          {
              revertOK = false;
              qWarning() << "restoring" << backup.original << "failed!";
          }
      }
      return revertOK;
  }
  