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. # Setup "rssguard_updater" project.
project(rssguard_updater) project(rssguard_updater)
set(UPDATER_SUBFOLDER "updater")
set(UPDATER_EXE_NAME "rssguard_updater") set(UPDATER_EXE_NAME "rssguard_updater")
endif(WIN32 OR OS2) endif(WIN32 OR OS2)
@ -362,6 +363,7 @@ set(APP_SOURCES
src/miscellaneous/databasefactory.cpp src/miscellaneous/databasefactory.cpp
src/miscellaneous/skinfactory.cpp src/miscellaneous/skinfactory.cpp
src/miscellaneous/iconfactory.cpp src/miscellaneous/iconfactory.cpp
src/miscellaneous/iofactory.cpp
# CORE sources. # CORE sources.
src/core/messagesmodel.cpp src/core/messagesmodel.cpp
@ -505,6 +507,31 @@ set(APP_TEXT
resources/text/COPYING_BSD 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". # Setup source & header files for "rssguard_updater".
if(WIN32 OR OS2) if(WIN32 OR OS2)
set(UPDATER_SOURCES set(UPDATER_SOURCES
@ -513,6 +540,7 @@ if(WIN32 OR OS2)
src/qtsingleapplication/qtsingleapplication.cpp src/qtsingleapplication/qtsingleapplication.cpp
# MAIN sources. # MAIN sources.
src/miscellaneous/iofactory.cpp
src/updater/formupdater.cpp src/updater/formupdater.cpp
src/updater/main.cpp src/updater/main.cpp
) )
@ -652,7 +680,25 @@ if(WIN32 OR OS2)
install(TARGETS ${EXE_NAME} install(TARGETS ${EXE_NAME}
RUNTIME DESTINATION ./) RUNTIME DESTINATION ./)
install(TARGETS ${UPDATER_EXE_NAME} 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) if(BUNDLE_ICON_THEMES)
install(DIRECTORY resources/graphics/icons/mini-kfaenza install(DIRECTORY resources/graphics/icons/mini-kfaenza
@ -675,12 +721,6 @@ if(WIN32 OR OS2)
DESTINATION ./l10n) DESTINATION ./l10n)
install(FILES ${APP_TEXT} install(FILES ${APP_TEXT}
DESTINATION ./) 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) elseif(APPLE)
message(STATUS "[${APP_LOW_NAME}] You will probably install on Mac OS X.") message(STATUS "[${APP_LOW_NAME}] You will probably install on Mac OS X.")

View File

@ -35,8 +35,9 @@
#define APP_VERSION "@APP_VERSION@" #define APP_VERSION "@APP_VERSION@"
#define APP_USERAGENT QString("@APP_NAME@/@APP_VERSION@ (@APP_URL@) on @CMAKE_SYSTEM@") #define APP_USERAGENT QString("@APP_NAME@/@APP_VERSION@ (@APP_URL@) on @CMAKE_SYSTEM@")
#define APP_UPDATER_EXECUTABLE "rssguard_updater.exe" #define APP_UPDATER_SUBFOLDER "updater"
#define APP_7ZA_EXECUTABLE "7za.exe" #define APP_UPDATER_EXECUTABLE "rssguard_updater.exe"
#define APP_7ZA_EXECUTABLE "7za.exe"
#define RELEASES_LIST "https://bitbucket.org/skunkos/rssguard/raw/master/resources/text/UPDATES?at=master" #define RELEASES_LIST "https://bitbucket.org/skunkos/rssguard/raw/master/resources/text/UPDATES?at=master"
#define DEFAULT_LOCALE "en_GB" #define DEFAULT_LOCALE "en_GB"

View File

@ -20,6 +20,7 @@
#include "definitions/definitions.h" #include "definitions/definitions.h"
#include "miscellaneous/systemfactory.h" #include "miscellaneous/systemfactory.h"
#include "miscellaneous/iconfactory.h" #include "miscellaneous/iconfactory.h"
#include "miscellaneous/iofactory.h"
#include "network-web/networkfactory.h" #include "network-web/networkfactory.h"
#include "network-web/webfactory.h" #include "network-web/webfactory.h"
#include "network-web/downloader.h" #include "network-web/downloader.h"
@ -198,9 +199,29 @@ void FormUpdate::startUpdate() {
// via self-update feature. // via self-update feature.
close(); 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() << QStringList() <<
APP_VERSION << APP_VERSION <<
m_updateInfo.m_availableVersion << 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 "updater/formupdater.h"
#include "definitions/definitions.h" #include "definitions/definitions.h"
#include "miscellaneous/iofactory.h"
#include "qtsingleapplication/qtsingleapplication.h" #include "qtsingleapplication/qtsingleapplication.h"
#include <QDesktopWidget> #include <QDesktopWidget>
@ -133,7 +134,7 @@ void FormUpdater::triggerDebugMessageConsumption(QtMsgType type, const QString &
void FormUpdater::consumeDebugMessage(QtMsgType type, const QString &message) { void FormUpdater::consumeDebugMessage(QtMsgType type, const QString &message) {
switch (type) { switch (type) {
case QtDebugMsg: case QtDebugMsg:
printText(QString("DEBUG: %1").arg(message)); printText(message);
break; break;
case QtWarningMsg: case QtWarningMsg:
@ -241,7 +242,7 @@ bool FormUpdater::doPreparationCleanup() {
// Remove old folders. // Remove old folders.
if (QDir(m_parsedArguments["output_temp_path"]).exists()) { 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."); printText("Cleanup of old temporary files failed.");
return false; return false;
} }
@ -250,20 +251,14 @@ bool FormUpdater::doPreparationCleanup() {
} }
} }
if (!removeDirectory(m_parsedArguments["rssguard_path"], if (!IOFactory::removeDirectory(m_parsedArguments["rssguard_path"],
QStringList() << APP_7ZA_EXECUTABLE, QStringList(),
QStringList() << "data")) { // Keep the folder with settings.
QStringList() << "data")) {
printText("Full cleanup of actual RSS Guard installation failed."); printText("Full cleanup of actual RSS Guard installation failed.");
printText("Some files from old installation may persist."); 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; return true;
} }
@ -280,14 +275,14 @@ bool FormUpdater::doExtractionAndCopying() {
QString("-o%1").arg(m_parsedArguments["output_temp_path"]) << QString("-o%1").arg(m_parsedArguments["output_temp_path"]) <<
m_parsedArguments["update_file_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) { foreach(const QString &argument, extractor_arguments) {
printText(QString(" -> '%1'").arg(argument)); printText(QString(" -> '%1'").arg(argument));
} }
process_extractor.setEnvironment(QProcessEnvironment::systemEnvironment().toStringList()); 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); process_extractor.start(APP_7ZA_EXECUTABLE, extractor_arguments);
@ -296,7 +291,7 @@ bool FormUpdater::doExtractionAndCopying() {
} }
printText(process_extractor.readAll()); 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) { if (process_extractor.exitCode() != 0 || process_extractor.exitStatus() != QProcess::NormalExit) {
printText("Extraction failed due errors. Update cannot continue."); 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(); 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."); printText("Critical error appeared during copying of application files.");
return false; return false;
} }
@ -334,7 +329,7 @@ bool FormUpdater::doFinalCleanup() {
printNewline(); printNewline();
printHeading("Final cleanup"); 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"]); result_file = QFile::remove(m_parsedArguments["update_file_path"]);
printText(QString("Removing temporary files\n -> %1 -> %2\n -> %3 -> %4").arg( printText(QString("Removing temporary files\n -> %1 -> %2\n -> %3 -> %4").arg(
@ -385,62 +380,3 @@ void FormUpdater::moveToCenterAndResize() {
resize(600, 400); resize(600, 400);
move(qApp->desktop()->screenGeometry().center() - rect().center()); 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;
}