This repository has been archived by the owner on May 3, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SingleInstance application decorator
See #161
- Loading branch information
1 parent
8c85019
commit b05e7f9
Showing
21 changed files
with
857 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,4 +83,4 @@ if (BUILD_TESTS) | |
else() | ||
message(" [ ] LCOV Code Coverage Report") | ||
endif() | ||
endif() | ||
endif() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
lib/MellowPlayer/Infrastructure/Application/SingleInstance.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
62
lib/MellowPlayer/Infrastructure/Application/SingleInstance.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.