Rewrite updater logic for better robustness.

This commit is contained in:
Martin Rotter 2014-08-21 08:24:39 +02:00
parent d59d0cee3e
commit b3e1b5bbe9
25 changed files with 206 additions and 87 deletions

View File

@ -73,6 +73,7 @@ if(WIN32 OR OS2)
# Setup "rssguard_updater" project.
project(rssguard_updater)
set(UPDATER_SUBFOLDER "updater")
set(UPDATER_EXE_NAME "rssguard_updater")
endif(WIN32 OR OS2)
@ -362,6 +363,7 @@ set(APP_SOURCES
src/miscellaneous/databasefactory.cpp
src/miscellaneous/skinfactory.cpp
src/miscellaneous/iconfactory.cpp
src/miscellaneous/iofactory.cpp
# CORE sources.
src/core/messagesmodel.cpp
@ -505,6 +507,31 @@ set(APP_TEXT
resources/text/COPYING_BSD
)
if(WIN32)
set(APP_DLLS_QT4_MSVC2010
resources/binaries/windows/deployment/qt4-msvc2010/libeay32.dll
resources/binaries/windows/deployment/qt4-msvc2010/msvcp100.dll
resources/binaries/windows/deployment/qt4-msvc2010/msvcr100.dll
resources/binaries/windows/deployment/qt4-msvc2010/QtCore4.dll
resources/binaries/windows/deployment/qt4-msvc2010/QtGui4.dll
resources/binaries/windows/deployment/qt4-msvc2010/QtNetwork4.dll
resources/binaries/windows/deployment/qt4-msvc2010/QtSql4.dll
resources/binaries/windows/deployment/qt4-msvc2010/QtWebKit4.dll
resources/binaries/windows/deployment/qt4-msvc2010/QtXml4.dll
resources/binaries/windows/deployment/qt4-msvc2010/ssleay32.dll
)
set(APP_DLLS_QT4_MSVC2010_IMAGEFORMATS
resources/binaries/windows/deployment/qt4-msvc2010/imageformats/qgif4.dll
resources/binaries/windows/deployment/qt4-msvc2010/imageformats/qico4.dll
resources/binaries/windows/deployment/qt4-msvc2010/imageformats/qjpeg4.dll
resources/binaries/windows/deployment/qt4-msvc2010/imageformats/qmng4.dll
resources/binaries/windows/deployment/qt4-msvc2010/imageformats/qsvg4.dll
resources/binaries/windows/deployment/qt4-msvc2010/imageformats/qtga4.dll
resources/binaries/windows/deployment/qt4-msvc2010/imageformats/qtiff4.dll
)
endif(WIN32)
# Setup source & header files for "rssguard_updater".
if(WIN32 OR OS2)
set(UPDATER_SOURCES
@ -513,6 +540,7 @@ if(WIN32 OR OS2)
src/qtsingleapplication/qtsingleapplication.cpp
# MAIN sources.
src/miscellaneous/iofactory.cpp
src/updater/formupdater.cpp
src/updater/main.cpp
)
@ -652,7 +680,25 @@ if(WIN32 OR OS2)
install(TARGETS ${EXE_NAME}
RUNTIME DESTINATION ./)
install(TARGETS ${UPDATER_EXE_NAME}
RUNTIME DESTINATION ./)
RUNTIME DESTINATION ./${UPDATER_SUBFOLDER})
# Copy DLLs and other binary files for main installation and updater.
if(WIN32)
# Copy "7za" utility on Windows, OS/2 uses built-in "7za".
install(FILES resources/binaries/windows/7za/7za.exe
resources/binaries/windows/7za/7za_license.txt
DESTINATION ./${UPDATER_SUBFOLDER})
if(NOT ${USE_QT_5})
install(FILES ${APP_DLLS_QT4_MSVC2010}
DESTINATION ./)
install(FILES ${APP_DLLS_QT4_MSVC2010_IMAGEFORMATS}
DESTINATION ./imageformats)
install(FILES ${APP_DLLS_QT4_MSVC2010}
DESTINATION ./${UPDATER_SUBFOLDER})
endif(NOT ${USE_QT_5})
endif(WIN32)
if(BUNDLE_ICON_THEMES)
install(DIRECTORY resources/graphics/icons/mini-kfaenza
@ -675,12 +721,6 @@ if(WIN32 OR OS2)
DESTINATION ./l10n)
install(FILES ${APP_TEXT}
DESTINATION ./)
if(WIN32)
# Copy "7za" utility on Windows, OS/2 uses built-in "7za".
install(FILES resources/7za/7za.exe resources/7za/7za_license.txt
DESTINATION ./)
endif(WIN32)
elseif(APPLE)
message(STATUS "[${APP_LOW_NAME}] You will probably install on Mac OS X.")

View File

@ -35,6 +35,7 @@
#define APP_VERSION "@APP_VERSION@"
#define APP_USERAGENT QString("@APP_NAME@/@APP_VERSION@ (@APP_URL@) on @CMAKE_SYSTEM@")
#define APP_UPDATER_SUBFOLDER "updater"
#define APP_UPDATER_EXECUTABLE "rssguard_updater.exe"
#define APP_7ZA_EXECUTABLE "7za.exe"

View File

@ -20,6 +20,7 @@
#include "definitions/definitions.h"
#include "miscellaneous/systemfactory.h"
#include "miscellaneous/iconfactory.h"
#include "miscellaneous/iofactory.h"
#include "network-web/networkfactory.h"
#include "network-web/webfactory.h"
#include "network-web/downloader.h"
@ -198,9 +199,29 @@ void FormUpdate::startUpdate() {
// via self-update feature.
close();
qDebug("Preparing to launch external updater '%s'.", APP_UPDATER_EXECUTABLE);
// Now we need to copy updater to temporary path and launch it
// with correct arguments from there.
#if QT_VERSION >= 0x050000
QString temp_directory = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
#else
QString temp_directory = QDesktopServices::storageLocation(QDesktopServices::TempLocation);
#endif
if (!QProcess::startDetached(APP_UPDATER_EXECUTABLE,
QString source_updater_directory = QDir::toNativeSeparators(qApp->applicationDirPath() + QDir::separator() +
APP_UPDATER_SUBFOLDER);
QString target_updater_directory = QDir::toNativeSeparators(temp_directory + QDir::separator() +
APP_UPDATER_SUBFOLDER);
if (QDir(temp_directory).exists(APP_UPDATER_SUBFOLDER)) {
IOFactory::removeDirectory(target_updater_directory);
}
IOFactory::copyDirectory(source_updater_directory, target_updater_directory);
qDebug("Preparing to launch external updater '%s'.",
qPrintable(target_updater_directory + QDir::separator() + APP_UPDATER_EXECUTABLE));
if (!QProcess::startDetached(target_updater_directory + QDir::separator() + APP_UPDATER_EXECUTABLE,
QStringList() <<
APP_VERSION <<
m_updateInfo.m_availableVersion <<

85
src/miscellaneous/iofactory.cpp Executable file
View File

@ -0,0 +1,85 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2014 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#include "miscellaneous/iofactory.h"
#include <QDir>
#include <QFileInfo>
#include <QFile>
IOFactory::IOFactory() {
}
bool IOFactory::removeDirectory(const QString& directory_name,
const QStringList& exception_file_list,
const QStringList& exception_folder_list) {
bool result = true;
QDir dir(directory_name);
if (dir.exists(directory_name)) {
foreach (QFileInfo info,
dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
if (info.isDir()) {
if (!exception_folder_list.contains(info.fileName())) {
result &= removeDirectory(info.absoluteFilePath(), exception_file_list);
}
}
else if (!exception_file_list.contains(info.fileName())) {
result &= QFile::remove(info.absoluteFilePath());
}
}
result &= dir.rmdir(directory_name);
}
return result;
}
bool IOFactory::copyDirectory(QString source, QString destination) {
QDir dir(source);
if (!dir.exists()) {
return false;
}
foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QString dst_path = destination + QDir::separator() + d;
dir.mkpath(dst_path);
copyDirectory(source + QDir::separator() + d, dst_path);
}
foreach (QString f, dir.entryList(QDir::Files)) {
QString original_file = source + QDir::separator() + f;
QString destination_file = destination + QDir::separator() + f;
if (!QFile::exists(destination_file) || QFile::remove(destination_file)) {
if (QFile::copy(original_file, destination_file)) {
qDebug("Copied file \'%s\'.", qPrintable(f));
}
else {
qDebug("Failed to copy file \'%s\'.", qPrintable(QDir::toNativeSeparators(original_file)));
}
}
else {
qDebug("Failed to remove file \'%s\'.", qPrintable(QDir::toNativeSeparators(original_file)));
}
}
return true;
}

36
src/miscellaneous/iofactory.h Executable file
View File

@ -0,0 +1,36 @@
// This file is part of RSS Guard.
//
// Copyright (C) 2011-2014 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// RSS Guard is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with RSS Guard. If not, see <http://www.gnu.org/licenses/>.
#ifndef IOFACTORY_H
#define IOFACTORY_H
#include <QStringList>
class IOFactory {
private:
IOFactory();
public:
static bool copyDirectory(QString source, QString destination);
static bool removeDirectory(const QString &directory_name,
const QStringList &exception_file_list = QStringList(),
const QStringList &exception_folder_list = QStringList());
};
#endif // IOFACTORY_H

View File

@ -18,6 +18,7 @@
#include "updater/formupdater.h"
#include "definitions/definitions.h"
#include "miscellaneous/iofactory.h"
#include "qtsingleapplication/qtsingleapplication.h"
#include <QDesktopWidget>
@ -133,7 +134,7 @@ void FormUpdater::triggerDebugMessageConsumption(QtMsgType type, const QString &
void FormUpdater::consumeDebugMessage(QtMsgType type, const QString &message) {
switch (type) {
case QtDebugMsg:
printText(QString("DEBUG: %1").arg(message));
printText(message);
break;
case QtWarningMsg:
@ -241,7 +242,7 @@ bool FormUpdater::doPreparationCleanup() {
// Remove old folders.
if (QDir(m_parsedArguments["output_temp_path"]).exists()) {
if (!removeDirectory(m_parsedArguments["output_temp_path"])) {
if (!IOFactory::removeDirectory(m_parsedArguments["output_temp_path"])) {
printText("Cleanup of old temporary files failed.");
return false;
}
@ -250,20 +251,14 @@ bool FormUpdater::doPreparationCleanup() {
}
}
if (!removeDirectory(m_parsedArguments["rssguard_path"],
QStringList() << APP_7ZA_EXECUTABLE,
if (!IOFactory::removeDirectory(m_parsedArguments["rssguard_path"],
QStringList(),
// Keep the folder with settings.
QStringList() << "data")) {
printText("Full cleanup of actual RSS Guard installation failed.");
printText("Some files from old installation may persist.");
}
// TODO: Přidat obecně rekurzivní funkci renameDirectory
// která ke všem souborům a složkám (mimo vyjimky) přidá dohodnutý suffix.
// Tydle soubory budou pak smazaný hlavní aplikací při startu.
if (!QFile::rename(m_parsedArguments["updater_path"], m_parsedArguments["updater_path"] + ".old")) {
printText("Updater executable was not renamed and it will not be updated.");
}
return true;
}
@ -280,14 +275,14 @@ bool FormUpdater::doExtractionAndCopying() {
QString("-o%1").arg(m_parsedArguments["output_temp_path"]) <<
m_parsedArguments["update_file_path"];
printText(QString("Calling extractor %1 with these arguments:").arg(APP_7ZA_EXECUTABLE));
printText(QString("Calling extractor \'%1\' with these arguments:").arg(APP_7ZA_EXECUTABLE));
foreach(const QString &argument, extractor_arguments) {
printText(QString(" -> '%1'").arg(argument));
}
process_extractor.setEnvironment(QProcessEnvironment::systemEnvironment().toStringList());
process_extractor.setWorkingDirectory(m_parsedArguments["rssguard_path"]);
process_extractor.setWorkingDirectory(qApp->applicationDirPath());
process_extractor.start(APP_7ZA_EXECUTABLE, extractor_arguments);
@ -296,7 +291,7 @@ bool FormUpdater::doExtractionAndCopying() {
}
printText(process_extractor.readAll());
printText(QString("Extractor finished with exit code %1.").arg(process_extractor.exitCode()));
printText(QString("Extractor finished with exit code \'%1\'.").arg(process_extractor.exitCode()));
if (process_extractor.exitCode() != 0 || process_extractor.exitStatus() != QProcess::NormalExit) {
printText("Extraction failed due errors. Update cannot continue.");
@ -317,7 +312,7 @@ bool FormUpdater::doExtractionAndCopying() {
QString rssguard_single_temp_root = rssguard_temp_root.at(0).absoluteFilePath();
if (!copyDirectory(rssguard_single_temp_root, m_parsedArguments["rssguard_path"])) {
if (!IOFactory::copyDirectory(rssguard_single_temp_root, m_parsedArguments["rssguard_path"])) {
printText("Critical error appeared during copying of application files.");
return false;
}
@ -334,7 +329,7 @@ bool FormUpdater::doFinalCleanup() {
printNewline();
printHeading("Final cleanup");
result_path = removeDirectory(m_parsedArguments["output_temp_path"]);
result_path = IOFactory::removeDirectory(m_parsedArguments["output_temp_path"]);
result_file = QFile::remove(m_parsedArguments["update_file_path"]);
printText(QString("Removing temporary files\n -> %1 -> %2\n -> %3 -> %4").arg(
@ -385,62 +380,3 @@ void FormUpdater::moveToCenterAndResize() {
resize(600, 400);
move(qApp->desktop()->screenGeometry().center() - rect().center());
}
bool FormUpdater::removeDirectory(const QString& directory_name,
const QStringList& exception_file_list,
const QStringList& exception_folder_list) {
bool result = true;
QDir dir(directory_name);
if (dir.exists(directory_name)) {
foreach (QFileInfo info,
dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
if (info.isDir()) {
if (!exception_folder_list.contains(info.fileName())) {
result &= removeDirectory(info.absoluteFilePath(), exception_file_list);
}
}
else if (!exception_file_list.contains(info.fileName())) {
result &= QFile::remove(info.absoluteFilePath());
}
}
result &= dir.rmdir(directory_name);
}
return result;
}
bool FormUpdater::copyDirectory(QString source, QString destination) {
QDir dir(source);
if (! dir.exists()) {
return false;
}
foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QString dst_path = destination + QDir::separator() + d;
dir.mkpath(dst_path);
copyDirectory(source + QDir::separator() + d, dst_path);
}
foreach (QString f, dir.entryList(QDir::Files)) {
QString original_file = source + QDir::separator() + f;
QString destination_file = destination + QDir::separator() + f;
if (!QFile::exists(destination_file) || QFile::remove(destination_file)) {
if (QFile::copy(original_file, destination_file)) {
printText(QString("Copied file %1").arg(f));
}
else {
printText(QString("Failed to copy file %1").arg(original_file));
}
}
else {
printText(QString("Failed to remove file %1").arg(original_file));
}
}
return true;
}