Updater stuff.

This commit is contained in:
Martin Rotter 2014-04-10 13:06:35 +02:00
parent a876726177
commit 2643feaaa9
20 changed files with 812 additions and 189 deletions

@ -261,12 +261,13 @@ elseif(WIN32 AND MSVC)
set(APP_SOURCES ${APP_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/resources/executable_properties/rssguard_win.rc) set(APP_SOURCES ${APP_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/resources/executable_properties/rssguard_win.rc)
endif(MINGW AND WIN32) endif(MINGW AND WIN32)
# Add source files. # APP source files.
set(APP_SOURCES set(APP_SOURCES
${APP_SOURCES} ${APP_SOURCES}
# QTSINGLEAPPLICATION sources. # QTSINGLEAPPLICATION sources.
src/qtsingleapplication/qtlocalpeer.cpp src/qtsingleapplication/qtlocalpeer.cpp
src/qtsingleapplication/qtsinglecoreapplication.cpp
src/qtsingleapplication/qtsingleapplication.cpp src/qtsingleapplication/qtsingleapplication.cpp
# GUI sources. # GUI sources.
@ -340,12 +341,13 @@ set(APP_SOURCES
src/main.cpp src/main.cpp
) )
# Add headers. # APP headers.
set(APP_HEADERS set(APP_HEADERS
${APP_HEADERS} ${APP_HEADERS}
# QTSINGLEAPPLICATION headers. # QTSINGLEAPPLICATION headers.
src/qtsingleapplication/qtlocalpeer.h src/qtsingleapplication/qtlocalpeer.h
src/qtsingleapplication/qtsinglecoreapplication.h
src/qtsingleapplication/qtsingleapplication.h src/qtsingleapplication/qtsingleapplication.h
# GUI headers. # GUI headers.
@ -410,7 +412,7 @@ set(APP_HEADERS
src/application.h src/application.h
) )
# Add form files. # APP form files.
set(APP_FORMS set(APP_FORMS
src/gui/formupdate.ui src/gui/formupdate.ui
src/gui/formmain.ui src/gui/formmain.ui
@ -421,7 +423,7 @@ set(APP_FORMS
src/gui/toolbareditor.ui src/gui/toolbareditor.ui
) )
# Add translations. # APP translations.
set(APP_TRANSLATIONS set(APP_TRANSLATIONS
localization/rssguard-cs_CZ.ts localization/rssguard-cs_CZ.ts
localization/rssguard-de_DE.ts localization/rssguard-de_DE.ts
@ -480,10 +482,57 @@ include_directories (
${CMAKE_CURRENT_SOURCE_DIR}/src/gui ${CMAKE_CURRENT_SOURCE_DIR}/src/gui
${CMAKE_CURRENT_SOURCE_DIR}/src/network-web ${CMAKE_CURRENT_SOURCE_DIR}/src/network-web
${CMAKE_CURRENT_SOURCE_DIR}/src/dynamic-shortcuts ${CMAKE_CURRENT_SOURCE_DIR}/src/dynamic-shortcuts
${CMAKE_CURRENT_SOURCE_DIR}/src/updater
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}/src ${CMAKE_CURRENT_BINARY_DIR}/src
) )
if(WIN32 OR OS2)
project(updater)
set(UPDATER_SOURCES
# QTSINGLEAPPLICATION sources.
src/qtsingleapplication/qtlocalpeer.cpp
src/qtsingleapplication/qtsinglecoreapplication.cpp
src/updater/detector.cpp
src/updater/main.cpp
)
set(UPDATER_HEADERS
# QTSINGLEAPPLICATION headers.
src/qtsingleapplication/qtlocalpeer.h
src/qtsingleapplication/qtsinglecoreapplication.h
src/updater/detector.h
)
if(${USE_QT_5})
add_executable("updater"
${UPDATER_SOURCES}
)
# Use modules from Qt.
qt5_use_modules(updater
Core
Network
)
else(${USE_QT_5})
qt4_wrap_cpp(UPDATER_MOC ${UPDATER_HEADERS})
add_executable("updater"
${UPDATER_SOURCES}
${UPDATER_MOC}
)
# Link modules from Qt.
target_link_libraries("updater"
${QT_QTCORE_LIBRARY}
${QT_QTNETWORK_LIBRARY}
)
endif(${USE_QT_5})
endif(WIN32 OR OS2)
# Setup compilation for Qt 5. # Setup compilation for Qt 5.
if(${USE_QT_5}) if(${USE_QT_5})
add_executable(${EXE_NAME} WIN32 MACOSX_BUNDLE add_executable(${EXE_NAME} WIN32 MACOSX_BUNDLE

@ -145,7 +145,7 @@ QPair<FeedsModelFeed*, QNetworkReply::NetworkError> FeedsModelFeed::guessFeed(co
} }
QByteArray feed_contents; QByteArray feed_contents;
if ((result.second = NetworkFactory::downloadFeedFile(url, if ((result.second = NetworkFactory::downloadFile(url,
Settings::instance()->value(APP_CFG_FEEDS, "feed_update_timeout", DOWNLOAD_TIMEOUT).toInt(), Settings::instance()->value(APP_CFG_FEEDS, "feed_update_timeout", DOWNLOAD_TIMEOUT).toInt(),
feed_contents, feed_contents,
!username.isEmpty(), !username.isEmpty(),
@ -349,7 +349,7 @@ QVariant FeedsModelFeed::data(int column, int role) const {
void FeedsModelFeed::update() { void FeedsModelFeed::update() {
QByteArray feed_contents; QByteArray feed_contents;
int download_timeout = Settings::instance()->value(APP_CFG_FEEDS, "feed_update_timeout", DOWNLOAD_TIMEOUT).toInt(); int download_timeout = Settings::instance()->value(APP_CFG_FEEDS, "feed_update_timeout", DOWNLOAD_TIMEOUT).toInt();
QNetworkReply::NetworkError download_result = NetworkFactory::downloadFeedFile(url(), QNetworkReply::NetworkError download_result = NetworkFactory::downloadFile(url(),
download_timeout, download_timeout,
feed_contents, feed_contents,
passwordProtected(), passwordProtected(),

@ -103,6 +103,7 @@
#define APP_REVISION "@APP_REVISION@" #define APP_REVISION "@APP_REVISION@"
#define APP_QUIT_INSTANCE "app_quit"
#define APP_IS_RUNNING "app_is_running" #define APP_IS_RUNNING "app_is_running"
#define APP_SKIN_DEFAULT "base/vergilius.xml" #define APP_SKIN_DEFAULT "base/vergilius.xml"
#define APP_THEME_DEFAULT "mini-kfaenza" #define APP_THEME_DEFAULT "mini-kfaenza"

@ -172,6 +172,9 @@ void FormMain::processExecutionMessage(const QString &message) {
display(); display();
} }
else if (message == APP_QUIT_INSTANCE) {
qApp->quit();
}
} }
void FormMain::quit() { void FormMain::quit() {

@ -27,6 +27,7 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QDesktopServices> #include <QDesktopServices>
#include <QProcess>
FormUpdate::FormUpdate(QWidget *parent) FormUpdate::FormUpdate(QWidget *parent)
@ -78,7 +79,7 @@ void FormUpdate::checkForUpdates() {
m_ui->m_lblAvailableRelease->setText(update.first.m_availableVersion); m_ui->m_lblAvailableRelease->setText(update.first.m_availableVersion);
m_ui->m_txtChanges->setText(update.first.m_changes); m_ui->m_txtChanges->setText(update.first.m_changes);
if (update.first.m_availableVersion > APP_VERSION) { if (update.first.m_availableVersion >= APP_VERSION) {
m_ui->m_lblStatus->setStatus(WidgetWithStatus::Ok, m_ui->m_lblStatus->setStatus(WidgetWithStatus::Ok,
tr("New release available."), tr("New release available."),
tr("This is new version which can be\ndownloaded and installed.")); tr("This is new version which can be\ndownloaded and installed."));
@ -108,6 +109,44 @@ void FormUpdate::startUpdate() {
url_file = APP_URL; url_file = APP_URL;
} }
#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
// On Windows/OS2 we can update the application right away.
// Download the files.
QByteArray output;
//NetworkFactory::downloadFile(url_file, DOWNLOAD_TIMEOUT, output);
#if QT_VERSION >= 0x050000
QString temp_directory = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
#else
QString temp_directory = QDesktopServices::storageLocation(QDesktopServices::TempLocation);
#endif
if (!temp_directory.isEmpty()) {
QString output_file_name = url_file.mid(url_file.lastIndexOf('/') + 1);
QFile output_file(temp_directory + QDir::separator() + output_file_name);
if (output_file.exists()) {
output_file.remove();
}
if (output_file.open(QIODevice::WriteOnly)) {
output_file.write(output);
output_file.flush();
output_file.close();
// TODO: spustit updater
// pouzit qprocess, nebo neco multiplatformniho
}
else {
// TODO: chyba - nelze zapisovat do souboru
}
}
else {
// TODO: chyba - nelze ulozit soubor.
}
#else
if (!WebFactory::instance()->openUrlInExternalBrowser(url_file)) { if (!WebFactory::instance()->openUrlInExternalBrowser(url_file)) {
if (SystemTrayIcon::isSystemTrayActivated()) { if (SystemTrayIcon::isSystemTrayActivated()) {
SystemTrayIcon::instance()->showMessage(tr("Cannot update application"), SystemTrayIcon::instance()->showMessage(tr("Cannot update application"),
@ -123,4 +162,5 @@ void FormUpdate::startUpdate() {
"manually on project website.")); "manually on project website."));
} }
} }
#endif
} }

@ -66,7 +66,7 @@ int main(int argc, char *argv[]) {
#endif #endif
// Instantiate base application object. // Instantiate base application object.
QtSingleApplication application(argc, argv); QtSingleApplication application(APP_LOW_NAME, argc, argv);
qDebug("Instantiated QtSingleApplication class."); qDebug("Instantiated QtSingleApplication class.");
// Check if another instance is running. // Check if another instance is running.

@ -170,7 +170,7 @@ QPair<UpdateInfo, QNetworkReply::NetworkError> SystemFactory::checkForUpdates()
QPair<UpdateInfo, QNetworkReply::NetworkError> result; QPair<UpdateInfo, QNetworkReply::NetworkError> result;
QByteArray releases_xml; QByteArray releases_xml;
result.second = NetworkFactory::downloadFeedFile(RELEASES_LIST, result.second = NetworkFactory::downloadFile(RELEASES_LIST,
5000, 5000,
releases_xml); releases_xml);

@ -105,9 +105,9 @@ QNetworkReply::NetworkError NetworkFactory::downloadIcon(const QString &url,
#endif #endif
QByteArray icon_data; QByteArray icon_data;
QNetworkReply::NetworkError network_result = downloadFeedFile(google_s2_with_url, QNetworkReply::NetworkError network_result = downloadFile(google_s2_with_url,
timeout, timeout,
icon_data); icon_data);
if (network_result == QNetworkReply::NoError) { if (network_result == QNetworkReply::NoError) {
QPixmap icon_pixmap; QPixmap icon_pixmap;
@ -118,12 +118,12 @@ QNetworkReply::NetworkError NetworkFactory::downloadIcon(const QString &url,
return network_result; return network_result;
} }
QNetworkReply::NetworkError NetworkFactory::downloadFeedFile(const QString &url, QNetworkReply::NetworkError NetworkFactory::downloadFile(const QString &url,
int timeout, int timeout,
QByteArray &output, QByteArray &output,
bool protected_contents, bool protected_contents,
const QString &username, const QString &username,
const QString &password) { const QString &password) {
// Original asynchronous behavior of QNetworkAccessManager // Original asynchronous behavior of QNetworkAccessManager
// is replaced by synchronous behavior in order to make // is replaced by synchronous behavior in order to make
// process of downloading of a file easier to understand. // process of downloading of a file easier to understand.

@ -41,7 +41,7 @@ class NetworkFactory {
// Performs SYNCHRONOUS download of file with given URL // Performs SYNCHRONOUS download of file with given URL
// and given timeout. // and given timeout.
static QNetworkReply::NetworkError downloadFeedFile(const QString &url, static QNetworkReply::NetworkError downloadFile(const QString &url,
int timeout, int timeout,
QByteArray &output, QByteArray &output,
bool protected_contents = false, bool protected_contents = false,

@ -49,7 +49,7 @@
typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
static PProcessIdToSessionId pProcessIdToSessionId = 0; static PProcessIdToSessionId pProcessIdToSessionId = 0;
#endif #endif
#if defined(Q_OS_UNIX) || defined(Q_OS_OS2) #if defined(Q_OS_UNIX)
#include <sys/types.h> #include <sys/types.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
@ -67,129 +67,137 @@ namespace QtLP_Private {
const char* QtLocalPeer::ack = "ack"; const char* QtLocalPeer::ack = "ack";
QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
: QObject(parent), id(appId) { : QObject(parent), id(appId)
QString prefix = id; {
if (id.isEmpty()) { QString prefix = id;
id = QCoreApplication::applicationFilePath(); if (id.isEmpty()) {
id = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
id = id.toLower(); id = id.toLower();
#endif #endif
prefix = id.section(QLatin1Char('/'), -1); prefix = id.section(QLatin1Char('/'), -1);
} }
prefix.remove(QRegExp("[^a-zA-Z]")); prefix.remove(QRegExp("[^a-zA-Z]"));
prefix.truncate(6); prefix.truncate(6);
QByteArray idc = id.toUtf8(); QByteArray idc = id.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size()); quint16 idNum = qChecksum(idc.constData(), idc.size());
socketName = QLatin1String("qtsingleapp-") + prefix socketName = QLatin1String("qtsingleapp-") + prefix
+ QLatin1Char('-') + QString::number(idNum, 16); + QLatin1Char('-') + QString::number(idNum, 16);
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
if (!pProcessIdToSessionId) { if (!pProcessIdToSessionId) {
QLibrary lib("kernel32"); QLibrary lib("kernel32");
pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
} }
if (pProcessIdToSessionId) { if (pProcessIdToSessionId) {
DWORD sessionId = 0; DWORD sessionId = 0;
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
socketName += QLatin1Char('-') + QString::number(sessionId, 16); socketName += QLatin1Char('-') + QString::number(sessionId, 16);
} }
#else #else
socketName += QLatin1Char('-') + QString::number(::getuid(), 16); socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
#endif #endif
server = new QLocalServer(this); server = new QLocalServer(this);
QString lockName = QDir(QDir::tempPath()).absolutePath() QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName + QLatin1Char('/') + socketName
+ QLatin1String("-lockfile"); + QLatin1String("-lockfile");
lockFile.setFileName(lockName); lockFile.setFileName(lockName);
lockFile.open(QIODevice::ReadWrite); lockFile.open(QIODevice::ReadWrite);
} }
bool QtLocalPeer::isClient() {
if (lockFile.isLocked())
return false;
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
return true;
bool res = server->listen(socketName); bool QtLocalPeer::isClient()
{
if (lockFile.isLocked())
return false;
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
return true;
bool res = server->listen(socketName);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) #if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
// ### Workaround // ### Workaround
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
res = server->listen(socketName); res = server->listen(socketName);
} }
#endif #endif
if (!res) if (!res)
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
return false; return false;
} }
bool QtLocalPeer::sendMessage(const QString &message, int timeout) {
if (!isClient())
return false;
QLocalSocket socket; bool QtLocalPeer::sendMessage(const QString &message, int timeout)
bool connOk = false; {
for(int i = 0; i < 2; i++) { if (!isClient())
// Try twice, in case the other instance is just starting up return false;
socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2); QLocalSocket socket;
if (connOk || i) bool connOk = false;
break; for(int i = 0; i < 2; i++) {
int ms = 250; // Try twice, in case the other instance is just starting up
socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2);
if (connOk || i)
break;
int ms = 250;
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
Sleep(DWORD(ms)); Sleep(DWORD(ms));
#else #else
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
#endif #endif
} }
if (!connOk) if (!connOk)
return false; return false;
QByteArray uMsg(message.toUtf8()); QByteArray uMsg(message.toUtf8());
QDataStream ds(&socket); QDataStream ds(&socket);
ds.writeBytes(uMsg.constData(), uMsg.size()); ds.writeBytes(uMsg.constData(), uMsg.size());
bool res = socket.waitForBytesWritten(timeout); bool res = socket.waitForBytesWritten(timeout);
if (res) { if (res) {
res &= socket.waitForReadyRead(timeout); // wait for ack res &= socket.waitForReadyRead(timeout); // wait for ack
if (res) if (res)
res &= (socket.read(qstrlen(ack)) == ack); res &= (socket.read(qstrlen(ack)) == ack);
} }
return res; return res;
} }
void QtLocalPeer::receiveConnection() {
QLocalSocket* socket = server->nextPendingConnection();
if (!socket)
return;
while (socket->bytesAvailable() < (int)sizeof(quint32)) void QtLocalPeer::receiveConnection()
socket->waitForReadyRead(); {
QDataStream ds(socket); QLocalSocket* socket = server->nextPendingConnection();
QByteArray uMsg; if (!socket)
quint32 remaining; return;
ds >> remaining;
uMsg.resize(remaining); while (socket->bytesAvailable() < (int)sizeof(quint32))
int got = 0; socket->waitForReadyRead();
char* uMsgBuf = uMsg.data(); QDataStream ds(socket);
do { QByteArray uMsg;
got = ds.readRawData(uMsgBuf, remaining); quint32 remaining;
remaining -= got; ds >> remaining;
uMsgBuf += got; uMsg.resize(remaining);
} while (remaining && got >= 0 && socket->waitForReadyRead(2000)); int got = 0;
if (got < 0) { char* uMsgBuf = uMsg.data();
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); do {
got = ds.readRawData(uMsgBuf, remaining);
remaining -= got;
uMsgBuf += got;
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
if (got < 0) {
qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData());
delete socket;
return;
}
QString message(QString::fromUtf8(uMsg));
socket->write(ack, qstrlen(ack));
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
delete socket; delete socket;
return; emit messageReceived(message); //### (might take a long time to return)
}
QString message(QString::fromUtf8(uMsg));
socket->write(ack, qstrlen(ack));
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
delete socket;
emit messageReceived(message); //### (might take a long time to return)
} }

@ -47,23 +47,21 @@
#include "qtlockedfile.h" #include "qtlockedfile.h"
class QtLocalPeer : public QObject { class QtLocalPeer : public QObject
{
Q_OBJECT Q_OBJECT
public: public:
explicit QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
bool isClient(); bool isClient();
bool sendMessage(const QString &message, int timeout); bool sendMessage(const QString &message, int timeout);
QString applicationId() const
{ return id; }
QString applicationId() const { Q_SIGNALS:
return id;
}
Q_SIGNALS:
void messageReceived(const QString &message); void messageReceived(const QString &message);
protected Q_SLOTS: protected Q_SLOTS:
void receiveConnection(); void receiveConnection();
protected: protected:

@ -158,8 +158,8 @@ QtLockedFile::LockMode QtLockedFile::lockMode() const
can be locked. can be locked.
If \a block is true, this function will block until the lock is If \a block is true, this function will block until the lock is
acquired. If \a block is false, this function returns \e false aquired. If \a block is false, this function returns \e false
immediately if the lock cannot be acquired. immediately if the lock cannot be aquired.
If this object already has a lock of type \a mode, this function If this object already has a lock of type \a mode, this function
returns \e true immediately. If this object has a lock of a returns \e true immediately. If this object has a lock of a

@ -69,8 +69,8 @@ class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
public: public:
enum LockMode { NoLock = 0, ReadLock, WriteLock }; enum LockMode { NoLock = 0, ReadLock, WriteLock };
explicit QtLockedFile(); QtLockedFile();
explicit QtLockedFile(const QString &name); QtLockedFile(const QString &name);
~QtLockedFile(); ~QtLockedFile();
bool open(OpenMode mode); bool open(OpenMode mode);

@ -38,79 +38,310 @@
** **
****************************************************************************/ ****************************************************************************/
#include "qtsingleapplication.h" #include "qtsingleapplication.h"
#include "qtlocalpeer.h" #include "qtlocalpeer.h"
#include <QWidget> #include <QWidget>
void QtSingleApplication::sysInit(const QString &appId) { /*!
actWin = 0; \class QtSingleApplication qtsingleapplication.h
peer = new QtLocalPeer(this, appId); \brief The QtSingleApplication class provides an API to detect and
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); communicate with running instances of an application.
This class allows you to create applications where only one
instance should be running at a time. I.e., if the user tries to
launch another instance, the already running instance will be
activated instead. Another usecase is a client-server system,
where the first started instance will assume the role of server,
and the later instances will act as clients of that server.
By default, the full path of the executable file is used to
determine whether two processes are instances of the same
application. You can also provide an explicit identifier string
that will be compared instead.
The application should create the QtSingleApplication object early
in the startup phase, and call isRunning() to find out if another
instance of this application is already running. If isRunning()
returns false, it means that no other instance is running, and
this instance has assumed the role as the running instance. In
this case, the application should continue with the initialization
of the application user interface before entering the event loop
with exec(), as normal.
The messageReceived() signal will be emitted when the running
application receives messages from another instance of the same
application. When a message is received it might be helpful to the
user to raise the application so that it becomes visible. To
facilitate this, QtSingleApplication provides the
setActivationWindow() function and the activateWindow() slot.
If isRunning() returns true, another instance is already
running. It may be alerted to the fact that another instance has
started by using the sendMessage() function. Also data such as
startup parameters (e.g. the name of the file the user wanted this
new instance to open) can be passed to the running instance with
this function. Then, the application should terminate (or enter
client mode).
If isRunning() returns true, but sendMessage() fails, that is an
indication that the running instance is frozen.
Here's an example that shows how to convert an existing
application to use QtSingleApplication. It is very simple and does
not make use of all QtSingleApplication's functionality (see the
examples for that).
\code
// Original
int main(int argc, char **argv)
{
QApplication app(argc, argv);
MyMainWidget mmw;
mmw.show();
return app.exec();
}
// Single instance
int main(int argc, char **argv)
{
QtSingleApplication app(argc, argv);
if (app.isRunning())
return !app.sendMessage(someDataString);
MyMainWidget mmw;
app.setActivationWindow(&mmw);
mmw.show();
return app.exec();
}
\endcode
Once this QtSingleApplication instance is destroyed (normally when
the process exits or crashes), when the user next attempts to run the
application this instance will not, of course, be encountered. The
next instance to call isRunning() or sendMessage() will assume the
role as the new running instance.
For console (non-GUI) applications, QtSingleCoreApplication may be
used instead of this class, to avoid the dependency on the QtGui
library.
\sa QtSingleCoreApplication
*/
void QtSingleApplication::sysInit(const QString &appId)
{
actWin = 0;
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
} }
/*!
Creates a QtSingleApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc, \a
argv, and \a GUIenabled are passed on to the QAppliation constructor.
If you are creating a console application (i.e. setting \a
GUIenabled to false), you may consider using
QtSingleCoreApplication instead.
*/
QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
: QApplication(argc, argv, GUIenabled) { : QApplication(argc, argv, GUIenabled)
sysInit(); {
sysInit();
} }
/*!
Creates a QtSingleApplication object with the application
identifier \a appId. \a argc and \a argv are passed on to the
QAppliation constructor.
*/
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
: QApplication(argc, argv) { : QApplication(argc, argv)
sysInit(appId); {
sysInit(appId);
} }
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
/*!
Creates a QtSingleApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc, \a
argv, and \a type are passed on to the QAppliation constructor.
*/
QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
: QApplication(argc, argv, type) { : QApplication(argc, argv, type)
sysInit(); {
sysInit();
} }
#if defined(Q_WS_X11)
# if defined(Q_WS_X11)
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
and \a cmap are passed on to the QApplication constructor.
*/
QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, visual, cmap) { : QApplication(dpy, visual, cmap)
sysInit(); {
sysInit();
} }
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
argv, \a visual, and \a cmap are passed on to the QApplication
constructor.
*/
QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, argc, argv, visual, cmap) { : QApplication(dpy, argc, argv, visual, cmap)
sysInit(); {
sysInit();
} }
/*!
Special constructor for X11, ref. the documentation of
QApplication's corresponding constructor. The application identifier
will be \a appId. \a dpy, \a argc, \a
argv, \a visual, and \a cmap are passed on to the QApplication
constructor.
*/
QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
: QApplication(dpy, argc, argv, visual, cmap) { : QApplication(dpy, argc, argv, visual, cmap)
sysInit(appId); {
sysInit(appId);
} }
# endif // Q_WS_X11 # endif // Q_WS_X11
#endif // QT_VERSION < 0x050000 #endif // QT_VERSION < 0x050000
bool QtSingleApplication::isRunning() {
return peer->isClient(); /*!
Returns true if another instance of this application is running;
otherwise false.
This function does not find instances of this application that are
being run by a different user (on Windows: that are running in
another session).
\sa sendMessage()
*/
bool QtSingleApplication::isRunning()
{
return peer->isClient();
} }
bool QtSingleApplication::sendMessage(const QString &message, int timeout) {
return peer->sendMessage(message, timeout); /*!
Tries to send the text \a message to the currently running
instance. The QtSingleApplication object in the running instance
will emit the messageReceived() signal when it receives the
message.
This function returns true if the message has been sent to, and
processed by, the current instance. If there is no instance
currently running, or if the running instance fails to process the
message within \a timeout milliseconds, this function return false.
\sa isRunning(), messageReceived()
*/
bool QtSingleApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
} }
QString QtSingleApplication::id() const {
return peer->applicationId(); /*!
Returns the application identifier. Two processes with the same
identifier will be regarded as instances of the same application.
*/
QString QtSingleApplication::id() const
{
return peer->applicationId();
} }
void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) {
actWin = aw; /*!
if (activateOnMessage) Sets the activation window of this application to \a aw. The
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); activation window is the widget that will be activated by
else activateWindow(). This is typically the application's main window.
disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
If \a activateOnMessage is true (the default), the window will be
activated automatically every time a message is received, just prior
to the messageReceived() signal being emitted.
\sa activateWindow(), messageReceived()
*/
void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
{
actWin = aw;
if (activateOnMessage)
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
else
disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
} }
QWidget* QtSingleApplication::activationWindow() const {
return actWin; /*!
Returns the applications activation window if one has been set by
calling setActivationWindow(), otherwise returns 0.
\sa setActivationWindow()
*/
QWidget* QtSingleApplication::activationWindow() const
{
return actWin;
} }
void QtSingleApplication::activateWindow() {
if (actWin) { /*!
actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); De-minimizes, raises, and activates this application's activation window.
actWin->raise(); This function does nothing if no activation window has been set.
actWin->activateWindow();
} This is a convenience function to show the user that this
application instance has been activated when he has tried to start
another instance.
This function should typically be called in response to the
messageReceived() signal. By default, that will happen
automatically, if an activation window has been set.
\sa setActivationWindow(), messageReceived(), initialize()
*/
void QtSingleApplication::activateWindow()
{
if (actWin) {
actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
actWin->raise();
actWin->activateWindow();
}
} }
/*!
\fn void QtSingleApplication::messageReceived(const QString& message)
This signal is emitted when the current instance receives a \a
message from another instance of this application.
\sa sendMessage(), setActivationWindow(), activateWindow()
*/
/*!
\fn void QtSingleApplication::initialize(bool dummy = true)
\obsolete
*/

@ -61,18 +61,19 @@ class QtLocalPeer;
# define QT_QTSINGLEAPPLICATION_EXPORT # define QT_QTSINGLEAPPLICATION_EXPORT
#endif #endif
class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication { class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
{
Q_OBJECT Q_OBJECT
public: public:
explicit QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
explicit QtSingleApplication(const QString &id, int &argc, char **argv); QtSingleApplication(const QString &id, int &argc, char **argv);
#if QT_VERSION < 0x050000 #if QT_VERSION < 0x050000
explicit QtSingleApplication(int &argc, char **argv, Type type); QtSingleApplication(int &argc, char **argv, Type type);
# if defined(Q_WS_X11) # if defined(Q_WS_X11)
explicit QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
explicit QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
explicit QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
# endif // Q_WS_X11 # endif // Q_WS_X11
#endif // QT_VERSION < 0x050000 #endif // QT_VERSION < 0x050000
@ -83,23 +84,19 @@ class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication {
QWidget* activationWindow() const; QWidget* activationWindow() const;
// Obsolete: // Obsolete:
void initialize(bool dummy = true) { void initialize(bool dummy = true)
isRunning(); { isRunning(); Q_UNUSED(dummy) }
Q_UNUSED(dummy)
}
static QtSingleApplication *instance() { public Q_SLOTS:
return static_cast<QtSingleApplication*>(qApp);
}
public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000); bool sendMessage(const QString &message, int timeout = 5000);
void activateWindow(); void activateWindow();
Q_SIGNALS:
Q_SIGNALS:
void messageReceived(const QString &message); void messageReceived(const QString &message);
private:
private:
void sysInit(const QString &appId = QString()); void sysInit(const QString &appId = QString());
QtLocalPeer *peer; QtLocalPeer *peer;
QWidget *actWin; QWidget *actWin;

@ -0,0 +1,149 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtsinglecoreapplication.h"
#include "qtlocalpeer.h"
/*!
\class QtSingleCoreApplication qtsinglecoreapplication.h
\brief A variant of the QtSingleApplication class for non-GUI applications.
This class is a variant of QtSingleApplication suited for use in
console (non-GUI) applications. It is an extension of
QCoreApplication (instead of QApplication). It does not require
the QtGui library.
The API and usage is identical to QtSingleApplication, except that
functions relating to the "activation window" are not present, for
obvious reasons. Please refer to the QtSingleApplication
documentation for explanation of the usage.
A QtSingleCoreApplication instance can communicate to a
QtSingleApplication instance if they share the same application
id. Hence, this class can be used to create a light-weight
command-line tool that sends commands to a GUI application.
\sa QtSingleApplication
*/
/*!
Creates a QtSingleCoreApplication object. The application identifier
will be QCoreApplication::applicationFilePath(). \a argc and \a
argv are passed on to the QCoreAppliation constructor.
*/
QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Creates a QtSingleCoreApplication object with the application
identifier \a appId. \a argc and \a argv are passed on to the
QCoreAppliation constructor.
*/
QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
/*!
Returns true if another instance of this application is running;
otherwise false.
This function does not find instances of this application that are
being run by a different user (on Windows: that are running in
another session).
\sa sendMessage()
*/
bool QtSingleCoreApplication::isRunning()
{
return peer->isClient();
}
/*!
Tries to send the text \a message to the currently running
instance. The QtSingleCoreApplication object in the running instance
will emit the messageReceived() signal when it receives the
message.
This function returns true if the message has been sent to, and
processed by, the current instance. If there is no instance
currently running, or if the running instance fails to process the
message within \a timeout milliseconds, this function return false.
\sa isRunning(), messageReceived()
*/
bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
}
/*!
Returns the application identifier. Two processes with the same
identifier will be regarded as instances of the same application.
*/
QString QtSingleCoreApplication::id() const
{
return peer->applicationId();
}
/*!
\fn void QtSingleCoreApplication::messageReceived(const QString& message)
This signal is emitted when the current instance receives a \a
message from another instance of this application.
\sa sendMessage()
*/

@ -0,0 +1,71 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTSINGLECOREAPPLICATION_H
#define QTSINGLECOREAPPLICATION_H
#include <QCoreApplication>
class QtLocalPeer;
class QtSingleCoreApplication : public QCoreApplication
{
Q_OBJECT
public:
QtSingleCoreApplication(int &argc, char **argv);
QtSingleCoreApplication(const QString &id, int &argc, char **argv);
bool isRunning();
QString id() const;
public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000);
Q_SIGNALS:
void messageReceived(const QString &message);
private:
QtLocalPeer* peer;
};
#endif // QTSINGLECOREAPPLICATION_H

11
src/updater/detector.cpp Normal file

@ -0,0 +1,11 @@
#include "updater/detector.h"
Detector::Detector(QObject *parent) : QObject(parent) {
}
void Detector::handleMessage(const QString &message) {
if (message == "app_is_running") {
qDebug("Detected another instance of RSS Guard/Updater was starting...");
}
}

17
src/updater/detector.h Normal file

@ -0,0 +1,17 @@
#ifndef DETECTOR_H
#define DETECTOR_H
#include <QObject>
class Detector : public QObject {
Q_OBJECT
public:
explicit Detector(QObject *parent = 0);
public slots:
void handleMessage(const QString& message);
};
#endif // DETECTOR_H

48
src/updater/main.cpp Normal file

@ -0,0 +1,48 @@
// 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 "qtsingleapplication/qtsinglecoreapplication.h"
#include "updater/detector.h"
#include <QTranslator>
#include <QDebug>
#include <QThread>
int main(int argc, char *argv[]) {
// Instantiate base application object.
QtSingleCoreApplication application("rssguard", argc, argv);
qDebug("Instantiated QtSingleApplication class.");
// Check if main RSS Guard instance is running.
if (application.sendMessage("app_quit")) {
qDebug("RSS Guard application is running. Quitting it...");
}
Detector detector;
qDebug().nospace() << "Running updater in thread: \'" <<
QThread::currentThreadId() << "\'.";
// Setup single-instance behavior.
QObject::connect(&application, SIGNAL(messageReceived(QString)),
&detector, SLOT(handleMessage(QString)));
// Enter global event loop.
return QtSingleCoreApplication::exec();
}