Skip to content
This repository has been archived by the owner on May 3, 2019. It is now read-only.

Commit

Permalink
SingleInstance application decorator
Browse files Browse the repository at this point in the history
See #161
  • Loading branch information
ColinDuquesnoy committed Dec 2, 2017
1 parent 8c85019 commit b05e7f9
Show file tree
Hide file tree
Showing 21 changed files with 857 additions and 75 deletions.
59 changes: 59 additions & 0 deletions 3rdparty/boost-di-extensions/Factory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Copyright (c) 2012-2017 Kris Jusiak (kris at jusiak dot net)
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// taken from https://raw.githubusercontent.com/boost-experimental/di/cpp14/extension/injections/factory.cpp and
// adapted to fit our coding conventions
#pragma once

#include <cassert>
#include <memory>
#include <boost/di.hpp>

namespace di = boost::di;


template <class T, class... TArgs>
struct IFactory {
virtual ~IFactory() noexcept = default;
virtual std::unique_ptr<T> create(TArgs&&...) = 0;
};

template <class, class, class>
struct factory_impl;

template <class TInjector, class T, class I, class... TArgs>
struct factory_impl<TInjector, T, IFactory<I, TArgs...>> : IFactory<I, TArgs...> {
explicit factory_impl(const TInjector& injector) : injector_((TInjector&)injector) {}

std::unique_ptr<I> create(TArgs&&... args) override {
// clang-format off
auto injector = di::make_injector(
std::move(injector_)
#if (__clang_major__ == 3) && (__clang_minor__ > 4) || defined(__GCC___) || defined(__MSVC__)
, di::bind<TArgs>().to(std::forward<TArgs>(args))[di::override]...
#else // wknd for clang 3.4
, di::core::dependency<di::scopes::instance, TArgs, TArgs, di::no_name, di::core::override>(std::forward<TArgs>(args))...
#endif
);
// clang-format on

auto object = injector.template create<std::unique_ptr<T>>();
injector_ = std::move(injector);
return std::move(object);
}

private:
TInjector& injector_;
};

template <class T>
struct Factory {
template <class TInjector, class TDependency>
auto operator()(const TInjector& injector, const TDependency&) const {
static auto sp = std::make_shared<factory_impl<TInjector, T, typename TDependency::expected>>(injector);
return sp;
}
};
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@ if (BUILD_TESTS)
else()
message(" [ ] LCOV Code Coverage Report")
endif()
endif()
endif()
53 changes: 20 additions & 33 deletions docs/developers/PlantUml/Application.puml
Original file line number Diff line number Diff line change
Expand Up @@ -54,41 +54,28 @@ namespace MellowPlayer.Infrastructure {

}
ApplicationDecorator <|-- WithLogging

interface IQtApplication #PaleGreen {
setApplicationName(QString value)
setApplicationDisplayName(QString value)
setApplicationVersion(QString value)
setOrganizationDomain(QString value)
setOrganizationName(QString value)
setWindowIcon(const QIcon& icon)
exec()
exit(int returnCode)
installTranslator(QTranslator* translator)
setFont(const QFont& font)
--signals--
aboutToQuit
commitDataRequest
}
class QtApplication #PaleGreen {
}
IQtApplication <|- QtApplication
}

namespace MellowPlayer.Presentation {
interface IQtApplication #PaleGreen {
setApplicationName(QString value)
setApplicationDisplayName(QString value)
setApplicationVersion(QString value)
setOrganizationDomain(QString value)
setOrganizationName(QString value)
setWindowIcon(const QIcon& icon)
exec()
exit(int returnCode)
installTranslator(QTranslator* translator)
setFont(const QFont& font)
--signals--
aboutToQuit
commitDataRequest
}
class QtApplication #PaleGreen {
+ setApplicationName(QString value)
+ setApplicationDisplayName(QString value)
+ setApplicationVersion(QString value)
+ setOrganizationDomain(QString value)
+ setOrganizationName(QString value)
+ setWindowIcon(const QIcon& icon)
+ exec()
+ exit(int returnCode)
+ installTranslator(QTranslator* translator)
+ setFont(const QFont& font)
--signals--
+ aboutToQuit
+ commitDataRequest

}
IQtApplication <|-- QtApplication
class Application #PaleGreen {
+ quitRequested: signal<void>
+ requestQuit()
Expand All @@ -103,7 +90,7 @@ namespace MellowPlayer.Presentation {
}
MellowPlayer.Infrastructure.IApplication <|.. Application
ContextProperty <|- Application
Application -down-> IQtApplication
Application -> MellowPlayer.Infrastructure.IQtApplication

interface IQmlApplicationEngine #PaleGreen {
setContextProperty(QString, QObject*)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include <QtQuickControls2/QQuickStyle>

using namespace std;
using namespace MellowPlayer::Presentation;
using namespace MellowPlayer::Infrastructure;

QtApplication::QtApplication(int argc, char** argv)
: qApplication_(argc, argv)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
class QTranslator;
class QIcon;

namespace MellowPlayer::Presentation
namespace MellowPlayer::Infrastructure
{
class IQtApplication : public QObject
{
Expand Down
132 changes: 132 additions & 0 deletions lib/MellowPlayer/Infrastructure/Application/SingleInstance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#include "SingleInstance.hpp"
#include "QtApplication.hpp"
#include <MellowPlayer/Domain/Logging/LoggingManager.hpp>
#include <MellowPlayer/Domain/Logging/LoggingMacros.hpp>
#include <MellowPlayer/Domain/Logging/ILogger.hpp>
#include <MellowPlayer/Domain/Player/IPlayer.hpp>
#include <MellowPlayer/Infrastructure/CommandLineArguments/ICommandLineArguments.hpp>
#include <MellowPlayer/Infrastructure/Network/LocalServer.hpp>
#include <MellowPlayer/Infrastructure/Network/LocalSocket.hpp>

using namespace std;
using namespace MellowPlayer::Domain;
using namespace MellowPlayer::Infrastructure;

const QString SingleInstance::playPauseAction_ = "play-pause";
const QString SingleInstance::nextAction_ = "next";
const QString SingleInstance::previousAction_ = "previous";
const QString SingleInstance::restoreWindowAction_ = "restore-window";
const QString SingleInstance::toggleFavoriteAction_ = "toggle-favorite";

SingleInstance::SingleInstance(IApplication& application,
IQtApplication& qtApplication,
IPlayer& currentPlayer,
ICommandLineArguments& commandLineArguments,
IFactory<ILocalServer, QString>& localServerFactory,
IFactory<ILocalSocket>& localSocketFactory)
: ApplicationDecorator(application),
logger_(LoggingManager::logger("SingleInstance")),
qtApplication_(qtApplication),
currentPlayer_(currentPlayer),
commandLineArguments_(commandLineArguments),
localServerFactory_(localServerFactory),
localSocketFactory_(localSocketFactory),
lockFile_(QDir::tempPath() + QDir::separator() + qApp->applicationName() + ".lock"),
isPrimary_(false)
{
lockFile_.setStaleLockTime(0);
}

void SingleInstance::initialize()
{
if (lockFile_.tryLock(100)) {
LOG_DEBUG(logger_, "Initializing primary application");
isPrimary_ = true;
application_.initialize();
}
}

int SingleInstance::run()
{
return isPrimary_ ? runPrimaryApplication() : runSecondaryApplication();
}

bool SingleInstance::isPrimary() const
{
return isPrimary_;
}

int SingleInstance::runPrimaryApplication()
{
LOG_DEBUG(logger_, "Running primary application");

localServer_ = localServerFactory_.create(qApp->applicationName());
connect(localServer_.get(), &ILocalServer::newConnection, this, &SingleInstance::onSecondaryApplicationConnection);
localServer_->listen();

return application_.run();
}

void SingleInstance::onSecondaryApplicationConnection()
{
LOG_DEBUG(logger_, "Another application was started, showing this one instead");
localSocket_ = localServer_->nextPendingConnection();
connect(localSocket_.get(), &ILocalSocket::readyRead, this, &SingleInstance::onSecondaryApplicationActionRequest);
}

void SingleInstance::onSecondaryApplicationActionRequest()
{
QString action = QString(localSocket_->readAll()).split("\n")[0];
LOG_DEBUG(logger_, "Secondary application request: " << action);

if (action == playPauseAction_)
currentPlayer_.togglePlayPause();
else if (action == nextAction_)
currentPlayer_.next();
else if (action == previousAction_)
currentPlayer_.previous();
else if (action == toggleFavoriteAction_)
currentPlayer_.toggleFavoriteSong();
else
application_.restoreWindow();
}

int SingleInstance::runSecondaryApplication()
{
LOG_DEBUG(logger_, "Running secondary application");

localSocket_ = localSocketFactory_.create();
localSocket_->connectToServer(qApp->applicationName(), QIODevice::WriteOnly);
connect(localSocket_.get(), &ILocalSocket::connected, this, &SingleInstance::onConnectedToPrimaryApplication);
connect(localSocket_.get(), &ILocalSocket::error, this, &SingleInstance::onConnectionErrorWithPrimaryApplication);

return qtApplication_.exec();
}

void SingleInstance::onConnectedToPrimaryApplication()
{
LOG_INFO(logger_, "connection with the primary application succeeded, transmitting command line arguments "
"and quitting...");
QString action = requestedAcion();
localSocket_->write(action + "\n");
qtApplication_.exit(1);
}

void SingleInstance::onConnectionErrorWithPrimaryApplication()
{
LOG_INFO(logger_, "could not connect to the primary application, quitting...");
qtApplication_.exit(2);
}

QString SingleInstance::requestedAcion() const
{
if (commandLineArguments_.playPauseRequested())
return playPauseAction_;
else if (commandLineArguments_.nextRequested())
return nextAction_;
else if (commandLineArguments_.previousRequested())
return previousAction_;
else if (commandLineArguments_.toggleFavoriteRequested())
return toggleFavoriteAction_;
return restoreWindowAction_;
}
62 changes: 62 additions & 0 deletions lib/MellowPlayer/Infrastructure/Application/SingleInstance.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include "ApplicationDecorator.hpp"
#include <QtCore/QLockFile>
#include <boost-di-extensions/Factory.hpp>

namespace MellowPlayer::Domain
{
class ILogger;
class IPlayer;
}

namespace MellowPlayer::Infrastructure
{
class IQtApplication;
class ILocalServer;
class ILocalSocket;
class ICommandLineArguments;

class SingleInstance: public ApplicationDecorator
{
public:
SingleInstance(IApplication& application,
IQtApplication& qtApplication,
Domain::IPlayer& currentPlayer,
ICommandLineArguments& commandLineArguments,
IFactory<ILocalServer, QString>& localServer,
IFactory<ILocalSocket>& localSocket);

void initialize() override final;
int run() override final;

bool isPrimary() const;

private:
int runPrimaryApplication();
void onSecondaryApplicationConnection();
void onSecondaryApplicationActionRequest();

int runSecondaryApplication();
void onConnectedToPrimaryApplication();
void onConnectionErrorWithPrimaryApplication();
QString requestedAcion() const;

Domain::ILogger& logger_;
IQtApplication& qtApplication_;
Domain::IPlayer& currentPlayer_;
ICommandLineArguments& commandLineArguments_;
IFactory<ILocalServer, QString>& localServerFactory_;
IFactory<ILocalSocket>& localSocketFactory_;
std::unique_ptr<ILocalServer> localServer_;
std::unique_ptr<ILocalSocket> localSocket_;
QLockFile lockFile_;
bool isPrimary_;

static const QString playPauseAction_;
static const QString nextAction_;
static const QString previousAction_;
static const QString restoreWindowAction_;
static const QString toggleFavoriteAction_;
};
}
35 changes: 35 additions & 0 deletions lib/MellowPlayer/Infrastructure/Network/LocalServer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "LocalServer.hpp"
#include "LocalSocket.hpp"

using namespace std;
using namespace MellowPlayer::Infrastructure;

LocalServer::LocalServer(IFactory<ILocalSocket>& localSocketFactory, const QString& serverName)
: localSocketFactory_(localSocketFactory), serverName_(serverName)
{
QLocalServer::removeServer(serverName);
connect(&qLocalServer_, &QLocalServer::newConnection, this, &ILocalServer::newConnection);
}

LocalServer::~LocalServer()
{
close();
}

void LocalServer::close()
{
qLocalServer_.close();
}

bool LocalServer::listen()
{
return qLocalServer_.listen(serverName_);
}

unique_ptr<ILocalSocket> LocalServer::nextPendingConnection()
{
QLocalSocket* qLocalSocket = qLocalServer_.nextPendingConnection();
unique_ptr<ILocalSocket> localSocket = localSocketFactory_.create();
localSocket->setQLocalSocket(qLocalSocket);
return localSocket;
}
Loading

0 comments on commit b05e7f9

Please sign in to comment.