First implementation of Update Notification.

--HG--
branch : feature
This commit is contained in:
Roman Telezhynskyi 2016-07-15 12:00:00 +03:00
parent e4d6df06b7
commit a94daf6834
17 changed files with 1241 additions and 9 deletions

View file

@ -28,6 +28,8 @@
#include "mainwindow.h"
#include "core/vapplication.h"
#include "../fervor/fvupdater.h"
#include <QMessageBox> // For QT_REQUIRE_VERSION
#include <QTimer>
@ -55,6 +57,12 @@ int main(int argc, char *argv[])
app.InitOptions();
// Set feed URL before doing anything else
FvUpdater::sharedUpdater()->SetFeedURL("http://localhost/updateapp/Appcast.xml");
// Check for updates automatically
FvUpdater::sharedUpdater()->CheckForUpdatesSilent();
MainWindow w;
#if !defined(Q_OS_MAC)
app.setWindowIcon(QIcon(":/icon/64x64/icon64x64.png"));

View file

@ -536,6 +536,15 @@ DEPENDPATH += $$PWD/../../libs/vgeometry
win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vgeometry/$${DESTDIR}/vgeometry.lib
else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/vgeometry/$${DESTDIR}/libvgeometry.a
# Fervor static library (depend on VMisc, IFC)
unix|win32: LIBS += -L$$OUT_PWD/../../libs/fervor/$${DESTDIR}/ -lfervor
INCLUDEPATH += $$PWD/../../libs/fervor
DEPENDPATH += $$PWD/../../libs/fervor
win32:!win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/fervor/$${DESTDIR}/fervor.lib
else:unix|win32-g++: PRE_TARGETDEPS += $$OUT_PWD/../../libs/fervor/$${DESTDIR}/libfervor.a
# IFC static library (depend on QMuParser, VMisc)
unix|win32: LIBS += -L$$OUT_PWD/../../libs/ifc/$${DESTDIR}/ -lifc

7
src/libs/fervor/LICENSE Normal file
View file

@ -0,0 +1,7 @@
Copyright (c) 2012 Linas Valiukas and others.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,15 @@
# ADD TO EACH PATH $$PWD VARIABLE!!!!!!
# This need for corect working file translations.pro
SOURCES += \
$$PWD/fvupdatewindow.cpp \
$$PWD/fvupdater.cpp \
$$PWD/fvavailableupdate.cpp
HEADERS += \
$$PWD/fvupdatewindow.h \
$$PWD/fvupdater.h \
$$PWD/fvavailableupdate.h
FORMS += \
$$PWD/fvupdatewindow.ui

107
src/libs/fervor/fervor.pro Normal file
View file

@ -0,0 +1,107 @@
#-------------------------------------------------
#
# Project created by QtCreator 2016-07-14T11:55:03
#
#-------------------------------------------------
# File with common stuff for whole project
include(../../../common.pri)
QT += network widgets
# Name of library
TARGET = fervor
# We want to create a library
TEMPLATE = lib
CONFIG += \
staticlib \# Making static library
c++11 # We use C++11 standard
# Use out-of-source builds (shadow builds)
CONFIG -= debug_and_release debug_and_release_target
# Since Qt 5.4.0 the source code location is recorded only in debug builds.
# We need this information also in release builds. For this need define QT_MESSAGELOGCONTEXT.
DEFINES += QT_MESSAGELOGCONTEXT
include(fervor.pri)
# This is static library so no need in "make install"
# directory for executable file
DESTDIR = bin
# files created moc
MOC_DIR = moc
# objecs files
OBJECTS_DIR = obj
# Directory for files created uic
UI_DIR = uic
# Set using ccache. Function enable_ccache() defined in common.pri.
$$enable_ccache()
CONFIG(debug, debug|release){
# Debug mode
unix {
#Turn on compilers warnings.
*-g++{
QMAKE_CXXFLAGS += \
# Key -isystem disable checking errors in system headers.
-isystem "$${OUT_PWD}/$${MOC_DIR}" \
-isystem "$${OUT_PWD}/$${UI_DIR}" \
$$GCC_DEBUG_CXXFLAGS # See common.pri for more details.
noAddressSanitizer{ # For enable run qmake with CONFIG+=noAddressSanitizer
# do nothing
} else {
#gccs 4.8.0 Address Sanitizer
#http://blog.qt.digia.com/blog/2013/04/17/using-gccs-4-8-0-address-sanitizer-with-qt/
QMAKE_CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer
QMAKE_CFLAGS += -fsanitize=address -fno-omit-frame-pointer
QMAKE_LFLAGS += -fsanitize=address
}
}
clang*{
QMAKE_CXXFLAGS += \
# Key -isystem disable checking errors in system headers.
-isystem "$${OUT_PWD}/$${MOC_DIR}" \
-isystem "$${OUT_PWD}/$${UI_DIR}" \
$$CLANG_DEBUG_CXXFLAGS # See common.pri for more details.
}
*-icc-*{
QMAKE_CXXFLAGS += \
-isystem "$${OUT_PWD}/$${MOC_DIR}" \
-isystem "$${OUT_PWD}/$${UI_DIR}" \
$$ICC_DEBUG_CXXFLAGS
}
} else {
*-g++{
QMAKE_CXXFLAGS += $$GCC_DEBUG_CXXFLAGS # See common.pri for more details.
}
}
}else{
# Release mode
!win32-msvc*:CONFIG += silent
DEFINES += V_NO_ASSERT
!unix:*-g++{
QMAKE_CXXFLAGS += -fno-omit-frame-pointer # Need for exchndl.dll
}
noDebugSymbols{ # For enable run qmake with CONFIG+=noDebugSymbols
# do nothing
} else {
!macx:!win32-msvc*{
# Turn on debug symbols in release mode on Unix systems.
# On Mac OS X temporarily disabled. TODO: find way how to strip binary file.
QMAKE_CXXFLAGS_RELEASE += -g -gdwarf-3
QMAKE_CFLAGS_RELEASE += -g -gdwarf-3
QMAKE_LFLAGS_RELEASE =
}
}
}

View file

@ -0,0 +1,74 @@
/***************************************************************************************************
**
** Copyright (c) 2012 Linas Valiukas and others.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
** software and associated documentation files (the "Software"), to deal in the Software
** without restriction, including without limitation the rights to use, copy, modify,
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
******************************************************************************************************/
#include "fvavailableupdate.h"
//---------------------------------------------------------------------------------------------------------------------
FvAvailableUpdate::FvAvailableUpdate(QObject *parent)
: QObject(parent),
m_enclosureUrl(),
m_enclosureVersion(),
m_enclosurePlatform()
{
// noop
}
//---------------------------------------------------------------------------------------------------------------------
QUrl FvAvailableUpdate::GetEnclosureUrl() const
{
return m_enclosureUrl;
}
//---------------------------------------------------------------------------------------------------------------------
void FvAvailableUpdate::SetEnclosureUrl(const QUrl &enclosureUrl)
{
m_enclosureUrl = enclosureUrl;
}
//---------------------------------------------------------------------------------------------------------------------
void FvAvailableUpdate::SetEnclosureUrl(const QString &enclosureUrl)
{
SetEnclosureUrl(QUrl(enclosureUrl));
}
//---------------------------------------------------------------------------------------------------------------------
QString FvAvailableUpdate::GetEnclosureVersion() const
{
return m_enclosureVersion;
}
//---------------------------------------------------------------------------------------------------------------------
void FvAvailableUpdate::SetEnclosureVersion(const QString &enclosureVersion)
{
m_enclosureVersion = enclosureVersion;
}
//---------------------------------------------------------------------------------------------------------------------
QString FvAvailableUpdate::GetEnclosurePlatform() const
{
return m_enclosurePlatform;
}
//---------------------------------------------------------------------------------------------------------------------
void FvAvailableUpdate::SetEnclosurePlatform(const QString &enclosurePlatform)
{
m_enclosurePlatform = enclosurePlatform;
}

View file

@ -0,0 +1,52 @@
/***************************************************************************************************
**
** Copyright (c) 2012 Linas Valiukas and others.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
** software and associated documentation files (the "Software"), to deal in the Software
** without restriction, including without limitation the rights to use, copy, modify,
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
******************************************************************************************************/
#ifndef FVAVAILABLEUPDATE_H
#define FVAVAILABLEUPDATE_H
#include <QObject>
#include <QUrl>
class FvAvailableUpdate : public QObject
{
Q_OBJECT
public:
explicit FvAvailableUpdate(QObject *parent = nullptr);
QUrl GetEnclosureUrl() const;
void SetEnclosureUrl(const QUrl &enclosureUrl);
void SetEnclosureUrl(const QString &enclosureUrl);
QString GetEnclosureVersion() const;
void SetEnclosureVersion(const QString &enclosureVersion);
QString GetEnclosurePlatform() const;
void SetEnclosurePlatform(const QString &enclosurePlatform);
private:
Q_DISABLE_COPY(FvAvailableUpdate)
QUrl m_enclosureUrl;
QString m_enclosureVersion;
QString m_enclosurePlatform;
};
#endif // FVAVAILABLEUPDATE_H

View file

@ -0,0 +1,589 @@
/***************************************************************************************************
**
** Copyright (c) 2012 Linas Valiukas and others.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
** software and associated documentation files (the "Software"), to deal in the Software
** without restriction, including without limitation the rights to use, copy, modify,
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
******************************************************************************************************/
#include "fvupdater.h"
#include "../vmisc/projectversion.h"
#include "../vmisc/vabstractapplication.h"
#include "../ifc/xml/vabstractconverter.h"
#include "../ifc/exception/vexception.h"
#include <QtNetwork>
#include <QMessageBox>
#include <QDesktopServices>
#include <QDebug>
QPointer<FvUpdater> FvUpdater::m_Instance;
//---------------------------------------------------------------------------------------------------------------------
FvUpdater* FvUpdater::sharedUpdater()
{
static QMutex mutex;
if (m_Instance.isNull())
{
mutex.lock();
if (m_Instance.isNull())
{
m_Instance = new FvUpdater;
}
mutex.unlock();
}
return m_Instance.data();
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::drop()
{
static QMutex mutex;
mutex.lock();
delete m_Instance;
mutex.unlock();
}
//---------------------------------------------------------------------------------------------------------------------
FvUpdater::FvUpdater()
: QObject(nullptr),
m_updaterWindow(nullptr),
m_proposedUpdate(nullptr),
m_silentAsMuchAsItCouldGet(true),
m_feedURL(),
m_qnam(),
m_reply(nullptr),
m_httpRequestAborted(false),
m_xml()
{
// noop
}
//---------------------------------------------------------------------------------------------------------------------
FvUpdater::~FvUpdater()
{
hideUpdaterWindow();
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::showUpdaterWindowUpdatedWithCurrentUpdateProposal()
{
// Destroy window if already exists
hideUpdaterWindow();
// Create a new window
m_updaterWindow = new FvUpdateWindow(qApp->getMainWindow());
m_updaterWindow->UpdateWindowWithCurrentProposedUpdate();
m_updaterWindow->show();
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::hideUpdaterWindow()
{
if (m_updaterWindow)
{
m_updaterWindow->close();
}
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::SetFeedURL(const QUrl &feedURL)
{
m_feedURL = feedURL;
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::SetFeedURL(const QString &feedURL)
{
SetFeedURL(QUrl(feedURL));
}
//---------------------------------------------------------------------------------------------------------------------
QString FvUpdater::GetFeedURL() const
{
return m_feedURL.toString();
}
//---------------------------------------------------------------------------------------------------------------------
QPointer<FvAvailableUpdate> FvUpdater::GetProposedUpdate()
{
return m_proposedUpdate;
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::InstallUpdate()
{
qDebug() << "Install update";
UpdateInstallationConfirmed();
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::SkipUpdate()
{
qDebug() << "Skip update";
FvAvailableUpdate* proposedUpdate = GetProposedUpdate();
if (not proposedUpdate)
{
qWarning() << "Proposed update is NULL (shouldn't be at this point)";
return;
}
// Start ignoring this particular version
IgnoreVersion(proposedUpdate->GetEnclosureVersion());
hideUpdaterWindow();
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::RemindMeLater()
{
qDebug() << "Remind me later";
hideUpdaterWindow();
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::UpdateInstallationConfirmed()
{
qDebug() << "Confirm update installation";
QPointer<FvAvailableUpdate> proposedUpdate = GetProposedUpdate();
if (proposedUpdate.isNull())
{
qWarning() << "Proposed update is NULL (shouldn't be at this point)";
return;
}
// Open a link
if (not QDesktopServices::openUrl(proposedUpdate->GetEnclosureUrl()))
{
showErrorDialog(tr("Unable to open a browser."), true);
return;
}
hideUpdaterWindow();
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdater::CheckForUpdates(bool silentAsMuchAsItCouldGet)
{
if (m_feedURL.isEmpty())
{
qCritical() << "Please set feed URL via setFeedURL() before calling CheckForUpdates().";
return false;
}
m_silentAsMuchAsItCouldGet = silentAsMuchAsItCouldGet;
// Check if application's organization name and domain are set, fail otherwise
// (nowhere to store QSettings to)
if (QApplication::organizationName().isEmpty())
{
qCritical() << "QApplication::organizationName is not set. Please do that.";
return false;
}
if (QApplication::organizationDomain().isEmpty())
{
qCritical() << "QApplication::organizationDomain is not set. Please do that.";
return false;
}
// Set application name / version is not set yet
if (QApplication::applicationName().isEmpty())
{
qCritical() << "QApplication::applicationName is not set. Please do that.";
return false;
}
if (QApplication::applicationVersion().isEmpty())
{
qCritical() << "QApplication::applicationVersion is not set. Please do that.";
return false;
}
cancelDownloadFeed();
m_httpRequestAborted = false;
startDownloadFeed(m_feedURL);
return true;
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdater::CheckForUpdatesSilent()
{
return CheckForUpdates(true);
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdater::CheckForUpdatesNotSilent()
{
return CheckForUpdates(false);
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::startDownloadFeed(const QUrl &url)
{
m_xml.clear();
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/xml"));
request.setHeader(QNetworkRequest::UserAgentHeader, QApplication::applicationName());
request.setUrl(url);
m_reply = m_qnam.get(request);
connect(m_reply, &QNetworkReply::readyRead, this, &FvUpdater::httpFeedReadyRead);
connect(m_reply, &QNetworkReply::downloadProgress, this, &FvUpdater::httpFeedUpdateDataReadProgress);
connect(m_reply, &QNetworkReply::finished, this, &FvUpdater::httpFeedDownloadFinished);
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::cancelDownloadFeed()
{
if (m_reply)
{
m_httpRequestAborted = true;
m_reply->abort();
}
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::httpFeedReadyRead()
{
// this slot gets called every time the QNetworkReply has new data.
// We read all of its new data and write it into the file.
// That way we use less RAM than when reading it at the finished()
// signal of the QNetworkReply
m_xml.addData(m_reply->readAll());
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::httpFeedUpdateDataReadProgress(qint64 bytesRead, qint64 totalBytes)
{
Q_UNUSED(bytesRead);
Q_UNUSED(totalBytes);
if (m_httpRequestAborted)
{
return;
}
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::httpFeedDownloadFinished()
{
if (m_httpRequestAborted)
{
m_reply->deleteLater();
return;
}
const QVariant redirectionTarget = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (m_reply->error())
{
// Error.
showErrorDialog(tr("Feed download failed: %1.").arg(m_reply->errorString()), false);
}
else if (not redirectionTarget.isNull())
{
const QUrl newUrl = m_feedURL.resolved(redirectionTarget.toUrl());
m_feedURL = newUrl;
m_reply->deleteLater();
startDownloadFeed(m_feedURL);
return;
}
else
{
// Done.
xmlParseFeed();
}
m_reply->deleteLater();
m_reply = 0;
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdater::xmlParseFeed()
{
QString xmlEnclosureUrl, xmlEnclosureVersion, xmlEnclosurePlatform;
// Parse
while (not m_xml.atEnd())
{
m_xml.readNext();
if (m_xml.isStartElement())
{
if (m_xml.name() == QLatin1Literal("item"))
{
xmlEnclosureUrl.clear();
xmlEnclosureVersion.clear();
xmlEnclosurePlatform.clear();
}
else if (m_xml.name() == QLatin1Literal("enclosure"))
{
const QXmlStreamAttributes attribs = m_xml.attributes();
const QString fervorPlatform = QStringLiteral("fervor:platform");
if (attribs.hasAttribute(fervorPlatform))
{
if (CurrentlyRunningOnPlatform(attribs.value(fervorPlatform).toString().trimmed()))
{
xmlEnclosurePlatform = attribs.value(fervorPlatform).toString().trimmed();
const QString attributeUrl = QStringLiteral("url");
if (attribs.hasAttribute(attributeUrl))
{
xmlEnclosureUrl = attribs.value(attributeUrl).toString().trimmed();
}
else
{
xmlEnclosureUrl.clear();
}
const QString fervorVersion = QStringLiteral("fervor:version");
if (attribs.hasAttribute(fervorVersion))
{
const QString candidateVersion = attribs.value(fervorVersion).toString().trimmed();
if (not candidateVersion.isEmpty())
{
xmlEnclosureVersion = candidateVersion;
}
}
}
}
}
}
else if (m_xml.isEndElement())
{
if (m_xml.name() == QLatin1Literal("item"))
{
// That's it - we have analyzed a single <item> and we'll stop
// here (because the topmost is the most recent one, and thus
// the newest version.
return searchDownloadedFeedForUpdates(xmlEnclosureUrl,
xmlEnclosureVersion,
xmlEnclosurePlatform);
}
}
if (m_xml.error() && m_xml.error() != QXmlStreamReader::PrematureEndOfDocumentError)
{
showErrorDialog(tr("Feed parsing failed: %1 %2.").arg(QString::number(m_xml.lineNumber()),
m_xml.errorString()), false);
return false;
}
}
// No updates were found if we're at this point
// (not a single <item> element found)
showInformationDialog(tr("No updates were found."), false);
return false;
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdater::searchDownloadedFeedForUpdates(const QString &xmlEnclosureUrl,
const QString &xmlEnclosureVersion,
const QString &xmlEnclosurePlatform)
{
qDebug() << "Enclosure URL:" << xmlEnclosureUrl;
qDebug() << "Enclosure version:" << xmlEnclosureVersion;
qDebug() << "Enclosure platform:" << xmlEnclosurePlatform;
// Validate
if (xmlEnclosureUrl.isEmpty() || xmlEnclosureVersion.isEmpty() || xmlEnclosurePlatform.isEmpty())
{
showErrorDialog(tr("Feed error: invalid \"enclosure\" with the download link"), false);
return false;
}
// Relevant version?
if (VersionIsIgnored(xmlEnclosureVersion))
{
qDebug() << "Version '" << xmlEnclosureVersion << "' is ignored, too old or something like that.";
showInformationDialog(tr("No updates were found."), false);
return true; // Things have succeeded when you think of it.
}
//
// Success! At this point, we have found an update that can be proposed
// to the user.
//
if (m_proposedUpdate)
{
delete m_proposedUpdate;
}
m_proposedUpdate = new FvAvailableUpdate(this);
m_proposedUpdate->SetEnclosureUrl(xmlEnclosureUrl);
m_proposedUpdate->SetEnclosureVersion(xmlEnclosureVersion);
m_proposedUpdate->SetEnclosurePlatform(xmlEnclosurePlatform);
// Show "look, there's an update" window
showUpdaterWindowUpdatedWithCurrentUpdateProposal();
return true;
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdater::VersionIsIgnored(const QString &version)
{
// We assume that variable 'version' contains either:
// 1) The current version of the application (ignore)
// 2) The version that was skipped before and thus stored in QSettings (ignore)
// 3) A newer version (don't ignore)
// 'version' is not likely to contain an older version in any case.
int decVersion = 0x0;
try
{
decVersion = VAbstractConverter::GetVersion(version);
}
catch (const VException &e)
{
Q_UNUSED(e)
return true; // Ignore invalid version
}
if (decVersion == APP_VERSION)
{
return true;
}
const int lastSkippedVersion = qApp->Settings()->GetLatestSkippedVersion();
if (lastSkippedVersion != 0x0)
{
if (decVersion == lastSkippedVersion)
{
// Implicitly skipped version - skip
return true;
}
}
if (decVersion > APP_VERSION)
{
// Newer version - do not skip
return false;
}
// Fallback - skip
return true;
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::IgnoreVersion(const QString &version)
{
int decVersion = 0x0;
try
{
decVersion = VAbstractConverter::GetVersion(version);
}
catch (const VException &e)
{
Q_UNUSED(e)
return ; // Ignore invalid version
}
if (decVersion == APP_VERSION)
{
// Don't ignore the current version
return;
}
qApp->Settings()->SetLatestSkippedVersion(decVersion);
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdater::CurrentlyRunningOnPlatform(const QString &platform)
{
const QStringList platforms = QStringList() << "Q_OS_LINUX"
<< "Q_OS_MAC"
<< "Q_OS_WIN32";
switch (platforms.indexOf(platform.toUpper().trimmed()))
{
case 0: // Q_OS_LINUX
#ifdef Q_OS_LINUX // Defined on Linux.
return true;
#endif
break;
case 1: // Q_OS_MAC
#ifdef Q_OS_MAC // Defined on MAC OS (synonym for Darwin).
return true;
#endif
break;
case 2: // Q_OS_WIN32
#ifdef Q_OS_WIN32 // Defined on all supported versions of Windows.
return true;
#endif
break;
default:
break;
}
// Fallback
return false;
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::showErrorDialog(const QString &message, bool showEvenInSilentMode)
{
if (m_silentAsMuchAsItCouldGet)
{
if (not showEvenInSilentMode)
{
// Don't show errors in the silent mode
return;
}
}
QMessageBox dlFailedMsgBox;
dlFailedMsgBox.setIcon(QMessageBox::Critical);
dlFailedMsgBox.setText(tr("Error"));
dlFailedMsgBox.setInformativeText(message);
dlFailedMsgBox.exec();
}
//---------------------------------------------------------------------------------------------------------------------
void FvUpdater::showInformationDialog(const QString &message, bool showEvenInSilentMode)
{
if (m_silentAsMuchAsItCouldGet)
{
if (not showEvenInSilentMode)
{
// Don't show information dialogs in the silent mode
return;
}
}
QMessageBox dlInformationMsgBox;
dlInformationMsgBox.setIcon(QMessageBox::Information);
dlInformationMsgBox.setText(tr("Information"));
dlInformationMsgBox.setInformativeText(message);
dlInformationMsgBox.exec();
}

128
src/libs/fervor/fvupdater.h Normal file
View file

@ -0,0 +1,128 @@
/***************************************************************************************************
**
** Copyright (c) 2012 Linas Valiukas and others.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
** software and associated documentation files (the "Software"), to deal in the Software
** without restriction, including without limitation the rights to use, copy, modify,
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
******************************************************************************************************/
#ifndef FVUPDATER_H
#define FVUPDATER_H
#include <QObject>
#include <QMutex>
#include <QNetworkAccessManager>
#include <QUrl>
#include <QXmlStreamReader>
#include <QPointer>
#include "fvupdatewindow.h"
#include "fvavailableupdate.h"
class FvUpdater : public QObject
{
Q_OBJECT
public:
// Singleton
static FvUpdater* sharedUpdater();
static void drop();
// Set / get feed URL
void SetFeedURL(const QUrl &feedURL);
void SetFeedURL(const QString &feedURL);
QString GetFeedURL() const;
public slots:
// Check for updates
bool CheckForUpdates(bool silentAsMuchAsItCouldGet = true);
// Aliases
bool CheckForUpdatesSilent();
bool CheckForUpdatesNotSilent();
protected:
friend class FvUpdateWindow; // Uses GetProposedUpdate() and others
QPointer<FvAvailableUpdate> GetProposedUpdate();
protected slots:
// Update window button slots
void InstallUpdate();
void SkipUpdate();
void RemindMeLater();
// Update confirmation dialog button slots
void UpdateInstallationConfirmed();
private slots:
void httpFeedReadyRead();
void httpFeedUpdateDataReadProgress(qint64 bytesRead, qint64 totalBytes);
void httpFeedDownloadFinished();
private:
//
// Singleton business
//
Q_DISABLE_COPY(FvUpdater)
FvUpdater(); // Hide main constructor
virtual ~FvUpdater(); // Hide main destructor
static QPointer<FvUpdater> m_Instance; // Singleton instance
QPointer<FvUpdateWindow> m_updaterWindow; // Updater window (NULL if not shown)
// Available update (NULL if not fetched)
QPointer<FvAvailableUpdate> m_proposedUpdate;
// If true, don't show the error dialogs and the "no updates." dialog
// (silentAsMuchAsItCouldGet from CheckForUpdates() goes here)
// Useful for automatic update checking upon application startup.
bool m_silentAsMuchAsItCouldGet;
//
// HTTP feed fetcher infrastructure
//
QUrl m_feedURL; // Feed URL that will be fetched
QNetworkAccessManager m_qnam;
QNetworkReply* m_reply;
bool m_httpRequestAborted;
QXmlStreamReader m_xml; // XML data collector and parser
void showUpdaterWindowUpdatedWithCurrentUpdateProposal(); // Show updater window
void hideUpdaterWindow(); // Hide + destroy m_updaterWindow
void startDownloadFeed(const QUrl &url); // Start downloading feed
void cancelDownloadFeed(); // Stop downloading the current feed
// Dialogs (notifications)
// Show an error message
void showErrorDialog(const QString &message, bool showEvenInSilentMode = false);
// Show an informational message
void showInformationDialog(const QString &message, bool showEvenInSilentMode = false);
bool xmlParseFeed(); // Parse feed in m_xml
bool searchDownloadedFeedForUpdates(const QString &xmlEnclosureUrl,
const QString &xmlEnclosureVersion,
const QString &xmlEnclosurePlatform);
static bool VersionIsIgnored(const QString &version);
static void IgnoreVersion(const QString &version);
static bool CurrentlyRunningOnPlatform(const QString &platform);
};
#endif // FVUPDATER_H

View file

@ -0,0 +1,72 @@
/***************************************************************************************************
**
** Copyright (c) 2012 Linas Valiukas and others.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
** software and associated documentation files (the "Software"), to deal in the Software
** without restriction, including without limitation the rights to use, copy, modify,
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
******************************************************************************************************/
#include "fvupdatewindow.h"
#include "ui_fvupdatewindow.h"
#include "fvupdater.h"
#include "fvavailableupdate.h"
#include <QApplication>
#include <QCloseEvent>
#include <QDebug>
//---------------------------------------------------------------------------------------------------------------------
FvUpdateWindow::FvUpdateWindow(QWidget *parent)
: QDialog(parent),
m_ui(new Ui::FvUpdateWindow),
m_appIconScene(nullptr)
{
m_ui->setupUi(this);
// Delete on close
setAttribute(Qt::WA_DeleteOnClose, true);
// Set the "new version is available" string
const QString newVersString = m_ui->newVersionIsAvailableLabel->text().arg(qApp->applicationDisplayName());
m_ui->newVersionIsAvailableLabel->setText(newVersString);
// Connect buttons
connect(m_ui->installUpdateButton, &QPushButton::clicked, FvUpdater::sharedUpdater(), &FvUpdater::InstallUpdate);
connect(m_ui->skipThisVersionButton, &QPushButton::clicked, FvUpdater::sharedUpdater(), &FvUpdater::SkipUpdate);
connect(m_ui->remindMeLaterButton, &QPushButton::clicked, FvUpdater::sharedUpdater(), &FvUpdater::RemindMeLater);
}
//---------------------------------------------------------------------------------------------------------------------
FvUpdateWindow::~FvUpdateWindow()
{
delete m_ui;
}
//---------------------------------------------------------------------------------------------------------------------
bool FvUpdateWindow::UpdateWindowWithCurrentProposedUpdate()
{
QPointer<FvAvailableUpdate> proposedUpdate = FvUpdater::sharedUpdater()->GetProposedUpdate();
if (proposedUpdate.isNull())
{
return false;
}
const QString downloadString = m_ui->wouldYouLikeToDownloadLabel->text()
.arg(qApp->applicationDisplayName(), proposedUpdate->GetEnclosureVersion(), qApp->applicationVersion());
m_ui->wouldYouLikeToDownloadLabel->setText(downloadString);
return true;
}

View file

@ -0,0 +1,53 @@
/***************************************************************************************************
**
** Copyright (c) 2012 Linas Valiukas and others.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
** software and associated documentation files (the "Software"), to deal in the Software
** without restriction, including without limitation the rights to use, copy, modify,
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
******************************************************************************************************/
#ifndef FVUPDATEWINDOW_H
#define FVUPDATEWINDOW_H
#include <QDialog>
class QGraphicsScene;
namespace Ui
{
class FvUpdateWindow;
}
class FvUpdateWindow : public QDialog
{
Q_OBJECT
public:
explicit FvUpdateWindow(QWidget *parent = nullptr);
virtual ~FvUpdateWindow();
// Update the current update proposal from FvUpdater
bool UpdateWindowWithCurrentProposedUpdate();
private:
Q_DISABLE_COPY(FvUpdateWindow)
Ui::FvUpdateWindow* m_ui;
QGraphicsScene* m_appIconScene;
};
#endif // FVUPDATEWINDOW_H

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FvUpdateWindow</class>
<widget class="QDialog" name="FvUpdateWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>95</height>
</rect>
</property>
<property name="windowTitle">
<string>Software Update</string>
</property>
<property name="windowIcon">
<iconset resource="../vmisc/share/resources/icon.qrc">
<normaloff>:/icon/64x64/icon64x64.png</normaloff>:/icon/64x64/icon64x64.png</iconset>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="newVersionIsAvailableLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>A new version of %1 is available!</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="wouldYouLikeToDownloadLabel">
<property name="text">
<string>%1 %2 is now available - you have %3. Would you like to download it now?</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="skipThisVersionButton">
<property name="text">
<string>Skip This Version</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="remindMeLaterButton">
<property name="text">
<string>Remind Me Later</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="installUpdateButton">
<property name="toolTip">
<string notr="true"/>
</property>
<property name="text">
<string>Get Update</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../vmisc/share/resources/icon.qrc"/>
</resources>
<connections/>
</ui>

View file

@ -33,6 +33,7 @@
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QRegularExpression>
//---------------------------------------------------------------------------------------------------------------------
VAbstractConverter::VAbstractConverter(const QString &fileName)
@ -107,7 +108,7 @@ QString VAbstractConverter::GetVersionStr() const
}
//---------------------------------------------------------------------------------------------------------------------
int VAbstractConverter::GetVersion(const QString &version) const
int VAbstractConverter::GetVersion(const QString &version)
{
ValidateVersion(version);
@ -138,11 +139,11 @@ int VAbstractConverter::GetVersion(const QString &version) const
}
//---------------------------------------------------------------------------------------------------------------------
void VAbstractConverter::ValidateVersion(const QString &version) const
void VAbstractConverter::ValidateVersion(const QString &version)
{
const QRegExp rx(QStringLiteral("^(0|([1-9][0-9]*)).(0|([1-9][0-9]*)).(0|([1-9][0-9]*))$"));
const QRegularExpression rx(QStringLiteral("^(0|([1-9][0-9]*)).(0|([1-9][0-9]*)).(0|([1-9][0-9]*))$"));
if (rx.exactMatch(version) == false)
if (rx.match(version).hasMatch() == false)
{
const QString errorMsg(tr("Version \"%1\" invalid.").arg(version));
throw VException(errorMsg);

View file

@ -43,12 +43,13 @@ public:
void Convert();
virtual bool SaveDocument(const QString &fileName, QString &error) const Q_DECL_OVERRIDE;
static int GetVersion(const QString &version);
protected:
int ver;
QString fileName;
void ValidateInputFile(const QString &currentSchema) const;
int GetVersion(const QString &version) const;
Q_NORETURN void InvalidVersion(int ver) const;
void Save() const;
void SetVersion(const QString &version);
@ -72,7 +73,7 @@ private:
QString GetVersionStr() const;
void ValidateVersion(const QString &version) const;
static void ValidateVersion(const QString &version);
void ReserveFile() const;
};

View file

@ -11,4 +11,5 @@ SUBDIRS = \
vmisc \
vwidgets \
vtools \
vformat
vformat \
fervor

View file

@ -65,6 +65,7 @@ const QString VCommonSettings::SettingGeneralGeometry = QString
const QString VCommonSettings::SettingGeneralWindowState = QStringLiteral("windowState");
const QString VCommonSettings::SettingGeneralToolbarsState = QStringLiteral("toolbarsState");
const QString VCommonSettings::SettingPreferenceDialogSize = QStringLiteral("preferenceDialogSize");
const QString VCommonSettings::SettingLatestSkippedVersion = QStringLiteral("lastestSkippedVersion");
static const QString commonIniFilename = QStringLiteral("common");
@ -429,3 +430,15 @@ void VCommonSettings::SetPreferenceDialogSize(const QSize& sz)
{
setValue(SettingPreferenceDialogSize, sz);
}
//---------------------------------------------------------------------------------------------------------------------
int VCommonSettings::GetLatestSkippedVersion() const
{
return value(SettingLatestSkippedVersion, 0x0).toInt();
}
//---------------------------------------------------------------------------------------------------------------------
void VCommonSettings::SetLatestSkippedVersion(int value)
{
setValue(SettingLatestSkippedVersion, value);
}

View file

@ -99,8 +99,11 @@ public:
QByteArray GetToolbarsState() const;
void SetToolbarsState(const QByteArray &value);
QSize GetPreferenceDialogSize() const;
void SetPreferenceDialogSize(const QSize& sz);
QSize GetPreferenceDialogSize() const;
void SetPreferenceDialogSize(const QSize& sz);
int GetLatestSkippedVersion() const;
void SetLatestSkippedVersion(int value);
private:
Q_DISABLE_COPY(VCommonSettings)
@ -128,6 +131,7 @@ private:
static const QString SettingGeneralWindowState;
static const QString SettingGeneralToolbarsState;
static const QString SettingPreferenceDialogSize;
static const QString SettingLatestSkippedVersion;
};
#endif // VCOMMONSETTINGS_H