+ implement mechanism to allow only a single application instance

This commit is contained in:
wmayer 2015-11-05 15:56:50 +01:00
parent 899199daf5
commit 3d8d6eca5e
7 changed files with 276 additions and 36 deletions

View File

@ -1285,49 +1285,57 @@ void Application::initApplication(void)
Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADInit")); Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADInit"));
} }
void Application::processCmdLineFiles(void) std::list<std::string> Application::getCmdLineFiles()
{ {
Base::Console().Log("Init: Processing command line files\n"); std::list<std::string> files;
// cycling through all the open files // cycling through all the open files
unsigned short count = 0; unsigned short count = 0;
count = atoi(mConfig["OpenFileCount"].c_str()); count = atoi(mConfig["OpenFileCount"].c_str());
std::string File; std::string File;
if (count == 0 && mConfig["RunMode"] == "Exit")
mConfig["RunMode"] = "Cmd";
for (unsigned short i=0; i<count; i++) { for (unsigned short i=0; i<count; i++) {
// getting file name // getting file name
std::ostringstream temp; std::ostringstream temp;
temp << "OpenFile" << i; temp << "OpenFile" << i;
FileInfo File(mConfig[temp.str()].c_str()); std::string file(mConfig[temp.str()]);
files.push_back(file);
}
return files;
}
void Application::processFiles(const std::list<std::string>& files)
{
Base::Console().Log("Init: Processing command line files\n");
for (std::list<std::string>::const_iterator it = files.begin(); it != files.end(); ++it) {
Base::FileInfo file(*it);
Base::Console().Log("Init: Processing file: %s\n",file.filePath().c_str());
std::string Ext = File.extension();
Base::Console().Log("Init: Processing file: %s\n",File.filePath().c_str());
try { try {
if (file.hasExtension("fcstd") || file.hasExtension("std")) {
if (File.hasExtension("fcstd") || File.hasExtension("std")) {
// try to open // try to open
Application::_pcSingleton->openDocument(File.filePath().c_str()); Application::_pcSingleton->openDocument(file.filePath().c_str());
} }
else if (File.hasExtension("fcscript")||File.hasExtension("fcmacro")) { else if (file.hasExtension("fcscript") || file.hasExtension("fcmacro")) {
Base::Interpreter().runFile(File.filePath().c_str(), true); Base::Interpreter().runFile(file.filePath().c_str(), true);
} }
else if (File.hasExtension("py")) { else if (file.hasExtension("py")) {
try { try {
Base::Interpreter().loadModule(File.fileNamePure().c_str()); Base::Interpreter().loadModule(file.fileNamePure().c_str());
} }
catch(const PyException&) { catch(const PyException&) {
// if module load not work, just try run the script (run in __main__) // if module load not work, just try run the script (run in __main__)
Base::Interpreter().runFile(File.filePath().c_str(),true); Base::Interpreter().runFile(file.filePath().c_str(),true);
} }
} }
else { else {
std::vector<std::string> mods = App::GetApplication().getImportModules(Ext.c_str()); std::string ext = file.extension();
std::vector<std::string> mods = App::GetApplication().getImportModules(ext.c_str());
if (!mods.empty()) { if (!mods.empty()) {
std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str()); std::string escapedstr = Base::Tools::escapedUnicodeFromUtf8(file.filePath().c_str());
Base::Interpreter().loadModule(mods.front().c_str()); Base::Interpreter().loadModule(mods.front().c_str());
Base::Interpreter().runStringArg("import %s",mods.front().c_str()); Base::Interpreter().runStringArg("import %s",mods.front().c_str());
Base::Interpreter().runStringArg("%s.open(u\"%s\")",mods.front().c_str(), Base::Interpreter().runStringArg("%s.open(u\"%s\")",mods.front().c_str(),
@ -1335,7 +1343,7 @@ void Application::processCmdLineFiles(void)
Base::Console().Log("Command line open: %s.open(u\"%s\")\n",mods.front().c_str(),escapedstr.c_str()); Base::Console().Log("Command line open: %s.open(u\"%s\")\n",mods.front().c_str(),escapedstr.c_str());
} }
else { else {
Console().Warning("File format not supported: %s \n", File.filePath().c_str()); Console().Warning("File format not supported: %s \n", file.filePath().c_str());
} }
} }
} }
@ -1343,12 +1351,24 @@ void Application::processCmdLineFiles(void)
throw; // re-throw to main() function throw; // re-throw to main() function
} }
catch (const Base::Exception& e) { catch (const Base::Exception& e) {
Console().Error("Exception while processing file: %s [%s]\n", File.filePath().c_str(), e.what()); Console().Error("Exception while processing file: %s [%s]\n", file.filePath().c_str(), e.what());
} }
catch (...) { catch (...) {
Console().Error("Unknown exception while processing file: %s \n", File.filePath().c_str()); Console().Error("Unknown exception while processing file: %s \n", file.filePath().c_str());
} }
} }
}
void Application::processCmdLineFiles(void)
{
// process files passed to command line
std::list<std::string> files = getCmdLineFiles();
processFiles(files);
if (files.empty()) {
if (mConfig["RunMode"] == "Exit")
mConfig["RunMode"] = "Cmd";
}
const std::map<std::string,std::string>& cfg = Application::Config(); const std::map<std::string,std::string>& cfg = Application::Config();
std::map<std::string,std::string>::const_iterator it = cfg.find("SaveFile"); std::map<std::string,std::string>::const_iterator it = cfg.find("SaveFile");
@ -1586,6 +1606,7 @@ void Application::ParseOptions(int ac, char ** av)
("run-test,t", value<int>() ,"Test level") ("run-test,t", value<int>() ,"Test level")
("module-path,M", value< vector<string> >()->composing(),"Additional module paths") ("module-path,M", value< vector<string> >()->composing(),"Additional module paths")
("python-path,P", value< vector<string> >()->composing(),"Additional python paths") ("python-path,P", value< vector<string> >()->composing(),"Additional python paths")
("single-instance", "Allow to run a single instance of the application")
; ;
@ -1806,6 +1827,10 @@ void Application::ParseOptions(int ac, char ** av)
}; };
} }
if (vm.count("single-instance")) {
mConfig["SingleInstance"] = "1";
}
if (vm.count("dump-config")) { if (vm.count("dump-config")) {
std::stringstream str; std::stringstream str;
for (std::map<std::string,std::string>::iterator it=mConfig.begin(); it != mConfig.end(); ++it) { for (std::map<std::string,std::string>::iterator it=mConfig.begin(); it != mConfig.end(); ++it) {

View File

@ -216,6 +216,8 @@ public:
static void destruct(void); static void destruct(void);
static void destructObserver(void); static void destructObserver(void);
static void processCmdLineFiles(void); static void processCmdLineFiles(void);
static std::list<std::string> getCmdLineFiles();
static void processFiles(const std::list<std::string>&);
static void runApplication(void); static void runApplication(void);
friend Application &GetApplication(void); friend Application &GetApplication(void);
static std::map<std::string,std::string> &Config(void){return mConfig;} static std::map<std::string,std::string> &Config(void){return mConfig;}

View File

@ -1538,15 +1538,34 @@ void Application::initTypes(void)
void Application::runApplication(void) void Application::runApplication(void)
{ {
const std::map<std::string,std::string>& cfg = App::Application::Config();
std::map<std::string,std::string>::const_iterator it;
// A new QApplication // A new QApplication
Base::Console().Log("Init: Creating Gui::Application and QApplication\n"); Base::Console().Log("Init: Creating Gui::Application and QApplication\n");
// if application not yet created by the splasher // if application not yet created by the splasher
int argc = App::Application::GetARGC(); int argc = App::Application::GetARGC();
int systemExit = 1000; int systemExit = 1000;
GUIApplication mainApp(argc, App::Application::GetARGV(), systemExit); GUISingleApplication mainApp(argc, App::Application::GetARGV(), systemExit);
// check if a single or multiple instances can run
it = cfg.find("SingleInstance");
if (it != cfg.end() && mainApp.isRunning()) {
// send the file names to be opened to the server application so that this
// opens them
std::list<std::string> files = App::Application::getCmdLineFiles();
for (std::list<std::string>::iterator jt = files.begin(); jt != files.end(); ++jt) {
QByteArray msg(jt->c_str(), static_cast<int>(jt->size()));
msg.prepend("OpenFile:");
if (!mainApp.sendMessage(msg)) {
qWarning("Failed to send message to server");
break;
}
}
return;
}
// set application icon and window title // set application icon and window title
const std::map<std::string,std::string>& cfg = App::Application::Config();
std::map<std::string,std::string>::const_iterator it;
it = cfg.find("Application"); it = cfg.find("Application");
if (it != cfg.end()) { if (it != cfg.end()) {
mainApp.setApplicationName(QString::fromUtf8(it->second.c_str())); mainApp.setApplicationName(QString::fromUtf8(it->second.c_str()));
@ -1615,6 +1634,8 @@ void Application::runApplication(void)
Application app(true); Application app(true);
MainWindow mw; MainWindow mw;
mw.setWindowTitle(mainApp.applicationName()); mw.setWindowTitle(mainApp.applicationName());
QObject::connect(&mainApp, SIGNAL(messageReceived(const QList<QByteArray> &)),
&mw, SLOT(processMessages(const QList<QByteArray> &)));
ParameterGrp::handle hDocGrp = WindowParameter::getDefaultParameter()->GetGroup("Document"); ParameterGrp::handle hDocGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
int timeout = hDocGrp->GetInt("AutoSaveTimeout", 15); // 15 min int timeout = hDocGrp->GetInt("AutoSaveTimeout", 15); // 15 min
@ -1741,19 +1762,8 @@ void Application::runApplication(void)
Instance->d->startingUp = false; Instance->d->startingUp = false;
#if 0
// processing all command line files
App::Application::processCmdLineFiles();
// Create new document?
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
if (hGrp->GetBool("CreateNewDoc", false)) {
App::GetApplication().newDocument();
}
#else
// gets called once we start the event loop // gets called once we start the event loop
QTimer::singleShot(0, &mw, SLOT(delayedStartup())); QTimer::singleShot(0, &mw, SLOT(delayedStartup()));
#endif
// run the Application event loop // run the Application event loop
Base::Console().Log("Init: Entering event loop\n"); Base::Console().Log("Init: Entering event loop\n");

View File

@ -26,7 +26,24 @@
#ifndef _PreComp_ #ifndef _PreComp_
# include <sstream> # include <sstream>
# include <stdexcept> # include <stdexcept>
# include <QByteArray>
# include <QDataStream>
# include <QDebug>
# include <QFileInfo>
# include <QSessionManager> # include <QSessionManager>
# include <QTimer>
#endif
#include <QLocalServer>
#include <QLocalSocket>
#if defined(Q_OS_WIN)
# include <Windows.h>
#endif
#if defined(Q_OS_UNIX)
# include <sys/types.h>
# include <time.h>
# include <unistd.h>
#endif #endif
#include "GuiApplication.h" #include "GuiApplication.h"
@ -46,6 +63,10 @@ GUIApplication::GUIApplication(int & argc, char ** argv, int exitcode)
{ {
} }
GUIApplication::~GUIApplication()
{
}
bool GUIApplication::notify (QObject * receiver, QEvent * event) bool GUIApplication::notify (QObject * receiver, QEvent * event)
{ {
if (!receiver && event) { if (!receiver && event) {
@ -123,4 +144,140 @@ void GUIApplication::commitData(QSessionManager &manager)
} }
} }
// ----------------------------------------------------------------------------
class GUISingleApplication::Private {
public:
Private(GUISingleApplication *q_ptr)
: q_ptr(q_ptr)
, timer(new QTimer(q_ptr))
, server(0)
, running(false)
{
timer->setSingleShot(true);
std::string exeName = App::GetApplication().getExecutableName();
serverName = QString::fromStdString(exeName);
}
~Private()
{
if (server)
server->close();
delete server;
}
void setupConnection()
{
QLocalSocket socket;
socket.connectToServer(serverName);
if (socket.waitForConnected(1000)) {
this->running = true;
}
else {
startServer();
}
}
void startServer()
{
// Start a QLocalServer to listen for connections
server = new QLocalServer();
QObject::connect(server, SIGNAL(newConnection()),
q_ptr, SLOT(receiveConnection()));
// first attempt
if (!server->listen(serverName)) {
if (server->serverError() == QAbstractSocket::AddressInUseError) {
// second attempt
server->removeServer(serverName);
server->listen(serverName);
}
}
if (server->isListening()) {
Base::Console().Log("Local server '%s' started\n", qPrintable(serverName));
}
else {
Base::Console().Log("Local server '%s' failed to start\n", qPrintable(serverName));
}
}
GUISingleApplication *q_ptr;
QTimer *timer;
QLocalServer *server;
QString serverName;
QList<QByteArray> messages;
bool running;
};
GUISingleApplication::GUISingleApplication(int & argc, char ** argv, int exitcode)
: GUIApplication(argc, argv, exitcode),
d_ptr(new Private(this))
{
d_ptr->setupConnection();
connect(d_ptr->timer, SIGNAL(timeout()), this, SLOT(processMessages()));
}
GUISingleApplication::~GUISingleApplication()
{
}
bool GUISingleApplication::isRunning() const
{
return d_ptr->running;
}
bool GUISingleApplication::sendMessage(const QByteArray &message, int timeout)
{
QLocalSocket socket;
bool connected = false;
for(int i = 0; i < 2; i++) {
socket.connectToServer(d_ptr->serverName);
connected = socket.waitForConnected(timeout/2);
if (connected || i > 0)
break;
int ms = 250;
#if defined(Q_OS_WIN)
Sleep(DWORD(ms));
#else
usleep(ms*1000);
#endif
}
if (!connected)
return false;
QDataStream ds(&socket);
ds << message;
socket.waitForBytesWritten(timeout);
return true;
}
void GUISingleApplication::receiveConnection()
{
QLocalSocket *socket = d_ptr->server->nextPendingConnection();
if (!socket)
return;
connect(socket, SIGNAL(disconnected()),
socket, SLOT(deleteLater()));
if (socket->waitForReadyRead()) {
QDataStream in(socket);
if (!in.atEnd()) {
d_ptr->timer->stop();
QByteArray message;
in >> message;
Base::Console().Log("Received message: %s\n", message.constData());
d_ptr->messages.push_back(message);
d_ptr->timer->start(1000);
}
}
socket->disconnectFromServer();
}
void GUISingleApplication::processMessages()
{
QList<QByteArray> msg = d_ptr->messages;
d_ptr->messages.clear();
Q_EMIT messageReceived(msg);
}
#include "moc_GuiApplication.cpp" #include "moc_GuiApplication.cpp"

View File

@ -25,6 +25,7 @@
#define GUI_APPLICATION_H #define GUI_APPLICATION_H
#include "GuiApplicationNativeEventAware.h" #include "GuiApplicationNativeEventAware.h"
#include <QList>
class QSessionManager; class QSessionManager;
@ -38,7 +39,8 @@ class GUIApplication : public GUIApplicationNativeEventAware
Q_OBJECT Q_OBJECT
public: public:
GUIApplication(int & argc, char ** argv, int exitcode); explicit GUIApplication(int & argc, char ** argv, int exitcode);
virtual ~GUIApplication();
/** /**
* Make forwarding events exception-safe and get more detailed information * Make forwarding events exception-safe and get more detailed information
@ -51,6 +53,29 @@ private:
int systemExit; int systemExit;
}; };
class GUISingleApplication : public GUIApplication
{
Q_OBJECT
public:
explicit GUISingleApplication(int & argc, char ** argv, int exitcode);
virtual ~GUISingleApplication();
bool isRunning() const;
bool sendMessage(const QByteArray &message, int timeout = 5000);
private Q_SLOTS:
void receiveConnection();
void processMessages();
Q_SIGNALS:
void messageReceived(const QList<QByteArray> &);
private:
class Private;
QScopedPointer<Private> d_ptr;
};
} }
#endif // GUI_APPLICATION_H #endif // GUI_APPLICATION_H

View File

@ -999,6 +999,23 @@ void MainWindow::showMainWindow()
#endif #endif
} }
void MainWindow::processMessages(const QList<QByteArray> & msg)
{
// handle all the messages to open files
try {
WaitCursor wc;
std::list<std::string> files;
QByteArray action("OpenFile:");
for (QList<QByteArray>::const_iterator it = msg.begin(); it != msg.end(); ++it) {
if (it->startsWith(action))
files.push_back(std::string(it->mid(action.size()).constData()));
}
App::Application::processFiles(files);
}
catch (const Base::SystemExitException&) {
}
}
void MainWindow::delayedStartup() void MainWindow::delayedStartup()
{ {
// processing all command line files // processing all command line files

View File

@ -262,6 +262,10 @@ private Q_SLOTS:
* \internal * \internal
*/ */
void delayedStartup(); void delayedStartup();
/**
* \internal
*/
void processMessages(const QList<QByteArray> &);
Q_SIGNALS: Q_SIGNALS:
void timeEvent(); void timeEvent();