moved ipc server to separated thread

This commit is contained in:
Igor Korsukov 2021-06-11 14:28:32 +02:00 committed by pereverzev+v
parent b8a536c404
commit bcff394bfa
21 changed files with 1214 additions and 702 deletions

View file

@ -35,11 +35,21 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/internal/multiinstancesprovider.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/multiinstancesprovider.h
${CMAKE_CURRENT_LIST_DIR}/internal/ipcchannel.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/ipcchannel.h
${CMAKE_CURRENT_LIST_DIR}/internal/multiinstancesuiactions.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/multiinstancesuiactions.h
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipclog.h
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipc.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipc.h
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipcchannel.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipcchannel.h
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipcserver.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipcserver.h
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipcsocket.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipcsocket.h
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipclock.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/ipc/ipclock.h
${CMAKE_CURRENT_LIST_DIR}/dev/multiinstancesdevmodel.cpp
${CMAKE_CURRENT_LIST_DIR}/dev/multiinstancesdevmodel.h
)

View file

@ -52,7 +52,6 @@ void MultiInstancesDevModel::update()
void MultiInstancesDevModel::ping()
{
multiInstancesProvider()->ping();
}
const QVariantList& MultiInstancesDevModel::instances() const

View file

@ -42,7 +42,6 @@ public:
//! NOTE Technical
virtual const std::string& selfID() const = 0;
virtual void ping() = 0;
virtual std::vector<InstanceMeta> instances() const = 0;
virtual async::Notification instancesChanged() const = 0;
};

View file

@ -0,0 +1,90 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ipc.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QLocalSocket>
void mu::ipc::serialize(const Meta& meta, const Msg& msg, QByteArray& data)
{
QJsonObject obj;
QJsonObject metaObj;
metaObj["id"] = meta.id;
obj["meta"] = metaObj;
QJsonObject msgObj;
msgObj["destID"] = msg.destID;
msgObj["type"] = static_cast<int>(msg.type);
msgObj["method"] = msg.method;
QJsonArray argsArr;
for (const QString& arg : qAsConst(msg.args)) {
argsArr.append(arg);
}
msgObj["args"] = argsArr;
obj["msg"] = msgObj;
data = QJsonDocument(obj).toJson(QJsonDocument::Compact);
}
void mu::ipc::deserialize(const QByteArray& data, Meta& meta, Msg& msg)
{
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject obj = doc.object();
QJsonObject metaObj = obj.value("meta").toObject();
meta.id = metaObj.value("id").toString();
QJsonObject msgObj = obj.value("msg").toObject();
msg.destID = msgObj.value("destID").toString();
msg.type = static_cast<MsgType>(msgObj.value("type").toInt());
msg.method = msgObj.value("method").toString();
QJsonArray argsArr = msgObj.value("args").toArray();
for (int i = 0; i < argsArr.count(); ++i) {
msg.args << argsArr.at(i).toString();
}
}
QString mu::ipc::socketErrorToString(int err)
{
switch (err) {
case QLocalSocket::ConnectionRefusedError: return "ConnectionRefusedError";
case QLocalSocket::PeerClosedError: return "PeerClosedError";
case QLocalSocket::ServerNotFoundError: return "ServerNotFoundError";
case QLocalSocket::SocketAccessError: return "SocketAccessError";
case QLocalSocket::SocketResourceError: return "SocketResourceError";
case QLocalSocket::SocketTimeoutError: return "SocketTimeoutError";
case QLocalSocket::DatagramTooLargeError: return "DatagramTooLargeError";
case QLocalSocket::ConnectionError: return "ConnectionError";
case QLocalSocket::UnsupportedSocketOperationError: return "UnsupportedSocketOperationError";
case QLocalSocket::UnknownSocketError: return "UnknownSocketError";
case QLocalSocket::OperationError: return "OperationError";
}
return "Unknown error";
}

View file

@ -0,0 +1,76 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_IPC_IPC_H
#define MU_IPC_IPC_H
#include <QString>
#include <QByteArray>
namespace mu::ipc {
static const QString SERVER_NAME("musescore-app-ipc");
using ID = QString;
static const int TIMEOUT_MSEC(500);
static const ID BROADCAST_ID("broadcast");
static const ID DIRECT_SOCKET_ID("socket");
static const ID SERVER_ID("server");
static const QByteArray ACK("ipc_ack");
static const QString IPC_("ipc_");
static const QString IPC_INIT("ipc_init");
static const QString IPC_WHOIS("ipc_whois");
static const QString IPC_METAINFO("ipc_metainfo");
static const QString IPC_PING("ipc_ping");
enum class MsgType {
Undefined = 0,
Notify,
Request,
Response
};
struct Msg
{
QString destID;
MsgType type = MsgType::Undefined;
QString method;
QStringList args;
bool isValid() const { return type != MsgType::Undefined && !method.isEmpty(); }
};
struct Meta
{
QString id;
bool isValid() const { return !id.isEmpty(); }
};
static void serialize(const Meta& meta, const Msg& msg, QByteArray& data);
static void deserialize(const QByteArray& data, Meta& meta, Msg& msg);
static QString socketErrorToString(int err);
}
#endif // MU_IPC_IPC_H

View file

@ -0,0 +1,105 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ipcchannel.h"
#include <QTimer>
#include "ipcsocket.h"
#include "ipcserver.h"
#include "ipclock.h"
using namespace mu::ipc;
IpcChannel::IpcChannel()
{
m_selfSocket = new IpcSocket();
m_selfSocket->disconected().onNotify(this, [this]() { onDisconected(); });
}
IpcChannel::~IpcChannel()
{
delete m_selfSocket;
delete m_server;
}
const ID& IpcChannel::selfID() const
{
return m_selfSocket->selfID();
}
void IpcChannel::connect()
{
setupConnection();
}
bool IpcChannel::send(const Msg& msg)
{
setupConnection();
return m_selfSocket->send(msg);
}
mu::async::Channel<Msg> IpcChannel::msgReceived() const
{
return m_selfSocket->msgReceived();
}
void IpcChannel::setupConnection()
{
if (!m_selfSocket->connect(SERVER_NAME)) {
IpcLock lock(SERVER_NAME);
lock.lock();
//! NOTE Check again
if (!m_selfSocket->connect(SERVER_NAME)) {
//! NOTE If it was not possible to connect to the server, then it no there or was, but it was closed.
//! In this case, we will become a server
m_server = new IpcServer();
m_server->listen(SERVER_NAME);
}
lock.unlock();
//! NOTE Connect to self server
m_selfSocket->connect(SERVER_NAME);
}
}
void IpcChannel::onDisconected()
{
//! NOTE If the server is down, then we will try to connect to another or create a server ourselves
uint64_t min = 1;
uint64_t max = 100;
//! NOTE All sockets receive a disconnect notify at the same time, add some delay to reduce the likelihood of simultaneous server creation
int interval = static_cast<int>(reinterpret_cast<uint64_t>(this) % (max - min + 1) + min);
QTimer::singleShot(interval, [this]() {
setupConnection();
});
}
QList<Meta> IpcChannel::instances() const
{
return m_selfSocket->instances();
}
mu::async::Notification IpcChannel::instancesChanged() const
{
return m_selfSocket->instancesChanged();
}

View file

@ -0,0 +1,64 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_IPC_IPCCHANNEL_H
#define MU_IPC_IPCCHANNEL_H
#include <QString>
#include <QList>
#include "ipc.h"
#include "async/asyncable.h"
#include "async/channel.h"
#include "async/notification.h"
namespace mu::ipc {
//! NOTE Inter-Process Communication Channel
class IpcSocket;
class IpcServer;
class IpcChannel : public async::Asyncable
{
public:
IpcChannel();
~IpcChannel();
const ID& selfID() const;
void connect();
bool send(const Msg& msg);
async::Channel<Msg> msgReceived() const;
QList<Meta> instances() const;
async::Notification instancesChanged() const;
private:
void setupConnection();
void onDisconected();
IpcSocket* m_selfSocket = nullptr;
IpcServer* m_server = nullptr;
};
}
#endif // MU_IPC_IPCCHANNEL_H

View file

@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ipclock.h"
#include <QSystemSemaphore>
using namespace mu::ipc;
IpcLock::IpcLock(const QString& name)
{
m_locker = new QSystemSemaphore(name, 1 /*allowed lock count*/, QSystemSemaphore::Open);
}
IpcLock::~IpcLock()
{
delete m_locker;
}
bool IpcLock::lock()
{
return m_locker->acquire();
}
bool IpcLock::unlock()
{
return m_locker->release();
}

View file

@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_IPC_IPCLOCK_H
#define MU_IPC_IPCLOCK_H
#include <QString>
class QSystemSemaphore;
namespace mu::ipc {
class IpcLock
{
public:
IpcLock(const QString& name);
~IpcLock();
bool lock();
bool unlock();
private:
QSystemSemaphore* m_locker = nullptr;
};
}
#endif // MU_IPC_IPCLOCK_H

View file

@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_IPC_IPCLOG_H
#define MU_IPC_IPCLOG_H
#include "log.h"
//#define IPC_LOGGING_ENABLED
#undef IPCLOG
#ifdef IPC_LOGGING_ENABLED
#define IPCLOG() LOGI()
#else
#define IPCLOG() LOGN()
#endif
#endif // MU_IPC_IPCLOG_H

View file

@ -0,0 +1,303 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ipcserver.h"
#include <QLocalServer>
#include <QLocalSocket>
#include <QThread>
#ifdef Q_OS_UNIX
#include <QFile>
#include <QDir>
#endif
#include "async/async.h"
#include "ipclock.h"
#include "ipclog.h"
using namespace mu::ipc;
IpcServer::~IpcServer()
{
if (m_thread) {
m_thread->quit();
m_thread->wait();
delete m_thread;
}
delete m_server;
}
bool IpcServer::listen(const QString& serverName)
{
if (!m_server) {
m_lock = new IpcLock(serverName);
m_server = new QLocalServer();
}
bool ok = m_server->listen(serverName);
#ifdef Q_OS_UNIX
// Workaround
if (!ok && m_server->serverError() == QAbstractSocket::AddressInUseError) {
QFile::remove(QDir::cleanPath(QDir::tempPath()) + QLatin1Char('/') + serverName);
ok = m_server->listen(serverName);
}
#endif
if (!ok) {
LOGE() << "failed listen: " << m_server->errorString();
return false;
}
QObject::connect(m_server, &QLocalServer::newConnection, [this]() {
QLocalSocket* socket = m_server->nextPendingConnection();
IPCLOG() << "newConnection socket: " << socket;
if (!socket) {
return;
}
QObject::connect(socket, &QLocalSocket::errorOccurred, [](QLocalSocket::LocalSocketError err) {
LOGE() << "socket error: " << socketErrorToString(err);
});
QObject::connect(socket, &QLocalSocket::disconnected, [socket, this]() {
IPCLOG() << "disconnected socket: " << socket;
onDisconnected(socket);
});
socket->waitForReadyRead(TIMEOUT_MSEC);
QByteArray id = socket->readAll();
IncomingSocket inc;
inc.socket = socket;
inc.meta.id = QString::fromUtf8(id);
m_incomingSockets.append(inc);
LOGI() << "id: " << id;
QObject::connect(socket, &QLocalSocket::readyRead, [socket, this]() {
onIncomingReadyRead(socket);
});
// askWhoIs(socket);
sendMetaInfoToAllIncoming();
});
if (!m_thread) {
m_thread = new QThread();
}
m_server->moveToThread(m_thread);
m_thread->start();
return true;
}
void IpcServer::onIncomingReadyRead(QLocalSocket* socket)
{
QByteArray data = socket->readAll();
IPCLOG() << data;
// async::Async::call(this, [this, socket, data]() {
Meta meta;
Msg msg;
deserialize(data, meta, msg);
IPCLOG() << "incoming [" << meta.id << "] data: " << data;
if (msg.method == IPC_INIT) {
onIncomingInit(socket, meta, msg);
}
if (msg.method == IPC_WHOIS) {
onIncomingWhoIs(socket, meta, msg);
}
if (msg.method == IPC_PING) {
onIncomingPing(socket, meta, msg);
}
//! NOTE Resend to others (broadcast)
if (msg.destID == BROADCAST_ID) {
for (IncomingSocket& s : m_incomingSockets) {
//! NOTE We do not resend to incoming socket
if (socket != s.socket) {
IPCLOG() << "resend to " << s.meta.id;
doSendToSocket(s.socket, data);
}
}
}
// });
}
void IpcServer::onIncomingInit(QLocalSocket* socket, const Meta& meta, const Msg& msg)
{
UNUSED(msg);
IPCLOG() << "init from: " << meta.id;
IncomingSocket& s = incomingSocket(socket);
if (!s.socket) {
LOGE() << "not found incoming socket";
return;
}
s.meta = meta;
sendMetaInfoToAllIncoming();
}
void IpcServer::onIncomingWhoIs(QLocalSocket* socket, const Meta& meta, const Msg& msg)
{
UNUSED(msg);
IPCLOG() << "who is answer: " << meta.id;
IncomingSocket& s = incomingSocket(socket);
if (!s.socket) {
LOGE() << "not found incoming socket";
return;
}
s.meta = meta;
sendMetaInfoToAllIncoming();
}
void IpcServer::onIncomingPing(QLocalSocket* socket, const Meta& meta, const Msg& msg)
{
UNUSED(msg);
IPCLOG() << "ping from: " << meta.id;
IncomingSocket& s = incomingSocket(socket);
if (!s.socket) {
LOGE() << "not found incoming socket";
return;
}
s.meta = meta;
sendMetaInfoToAllIncoming();
}
bool IpcServer::doSendToSocket(QLocalSocket* socket, const QByteArray& data)
{
IPCLOG() << data;
m_lock->lock();
socket->write(data);
bool ok = socket->waitForBytesWritten(TIMEOUT_MSEC);
if (!ok) {
LOGE() << "failed write data to socket";
return false;
}
m_lock->unlock();
return true;
}
void IpcServer::sendToSocket(QLocalSocket* socket, const Msg& msg)
{
Meta meta;
meta.id = SERVER_ID;
QByteArray data;
serialize(meta, msg, data);
doSendToSocket(socket, data);
}
void IpcServer::askWhoIs(QLocalSocket* socket)
{
Msg askMsg;
askMsg.destID = DIRECT_SOCKET_ID;
askMsg.method = IPC_WHOIS;
sendToSocket(socket, askMsg);
}
void IpcServer::sendMetaInfoToAllIncoming()
{
Meta meta;
meta.id = SERVER_ID;
Msg msg;
msg.destID = DIRECT_SOCKET_ID;
msg.method = IPC_METAINFO;
msg.args << QString::number(m_incomingSockets.count());
for (const IncomingSocket& s : qAsConst(m_incomingSockets)) {
msg.args << s.meta.id;
}
QByteArray data;
serialize(meta, msg, data);
for (IncomingSocket& s : m_incomingSockets) {
doSendToSocket(s.socket, data);
}
}
void IpcServer::onDisconnected(QLocalSocket* socket)
{
int index = -1;
for (int i = 0; i < m_incomingSockets.count(); ++i) {
if (m_incomingSockets.at(i).socket == socket) {
index = i;
break;
}
}
if (index < 0) {
LOGW() << "not found socket";
return;
}
m_incomingSockets.removeAt(index);
sendMetaInfoToAllIncoming();
}
IpcServer::IncomingSocket& IpcServer::incomingSocket(QLocalSocket* socket)
{
for (IncomingSocket& s : m_incomingSockets) {
if (s.socket == socket) {
return s;
}
}
static IncomingSocket null;
return null;
}
const IpcServer::IncomingSocket& IpcServer::incomingSocket(QLocalSocket* socket) const
{
for (const IncomingSocket& s : m_incomingSockets) {
if (s.socket == socket) {
return s;
}
}
static IncomingSocket null;
return null;
}

View file

@ -0,0 +1,74 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_IPC_IPCSERVER_H
#define MU_IPC_IPCSERVER_H
#include <QList>
#include "ipc.h"
#include "async/asyncable.h"
class QLocalServer;
class QLocalSocket;
class QThread;
namespace mu::ipc {
class IpcLock;
class IpcServer
{
public:
IpcServer() = default;
~IpcServer();
bool listen(const QString& serverName);
private:
struct IncomingSocket
{
Meta meta;
QLocalSocket* socket = nullptr;
};
void onIncomingReadyRead(QLocalSocket* socket);
void onIncomingInit(QLocalSocket* socket, const Meta& meta, const Msg& msg);
void onIncomingWhoIs(QLocalSocket* socket, const Meta& meta, const Msg& msg);
void onIncomingPing(QLocalSocket* socket, const Meta& meta, const Msg& msg);
bool doSendToSocket(QLocalSocket* socket, const QByteArray& data);
void sendToSocket(QLocalSocket* socket, const Msg& msg);
void askWhoIs(QLocalSocket* socket);
void sendMetaInfoToAllIncoming();
void onDisconnected(QLocalSocket* socket);
IncomingSocket& incomingSocket(QLocalSocket* socket);
const IncomingSocket& incomingSocket(QLocalSocket* socket) const;
IpcLock* m_lock = nullptr;
QLocalServer* m_server = nullptr;
QList<IncomingSocket> m_incomingSockets;
QThread* m_thread = nullptr;
};
}
#endif // MU_IPC_IPCSERVER_H

View file

@ -0,0 +1,200 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ipcsocket.h"
#include <QLocalSocket>
#include <QUuid>
#include "async/async.h"
#include "ipclock.h"
#include "ipclog.h"
using namespace mu::ipc;
IpcSocket::~IpcSocket()
{
delete m_socket;
delete m_lock;
}
const ID& IpcSocket::selfID() const
{
if (m_selfID.isEmpty()) {
m_selfID = QUuid::createUuid().toString(QUuid::Id128);
}
return m_selfID;
}
bool IpcSocket::connect(const QString& serverName)
{
if (!m_socket) {
m_lock = new IpcLock(serverName);
m_socket = new QLocalSocket();
QObject::connect(m_socket, &QLocalSocket::errorOccurred, [this](QLocalSocket::LocalSocketError err) {
//! NOTE If the server is down, then we will try to connect to another or create a server ourselves
if (err == QLocalSocket::PeerClosedError) {
m_disconected.notify();
}
});
QObject::connect(m_socket, &QLocalSocket::readyRead, [this]() {
onReadyRead();
});
}
if (m_socket->state() == QLocalSocket::ConnectedState) {
return true;
}
m_socket->connectToServer(serverName);
bool ok = m_socket->waitForConnected(TIMEOUT_MSEC);
if (!ok) {
LOGW() << "failed connect to server";
return false;
}
m_socket->write(selfID().toUtf8());
ok = m_socket->waitForBytesWritten(TIMEOUT_MSEC);
if (!ok) {
LOGE() << "failed init socket";
}
LOGI() << "success connected to ipc server";
return true;
}
mu::async::Notification IpcSocket::disconected()
{
return m_disconected;
}
bool IpcSocket::send(const Msg& msg)
{
IF_ASSERT_FAILED(m_socket) {
return false;
}
Meta meta;
meta.id = selfID();
QByteArray data;
serialize(meta, msg, data);
IPCLOG() << data;
m_lock->lock();
m_socket->write(data);
bool ok = m_socket->waitForBytesWritten(TIMEOUT_MSEC);
m_lock->unlock();
if (!ok) {
LOGE() << "failed send to socket";
}
return ok;
}
void IpcSocket::onReadyRead()
{
QByteArray data = m_socket->readAll();
IPCLOG() << data;
async::Async::call(this, [this, data]() {
onDataReceived(data);
});
}
void IpcSocket::onDataReceived(const QByteArray& data)
{
IPCLOG() << "received: " << data;
Meta receivedMeta;
Msg receivedMsg;
deserialize(data, receivedMeta, receivedMsg);
if (receivedMsg.method.startsWith(IPC_)) {
onIpcMsg(receivedMeta, receivedMsg);
return;
}
if (receivedMeta.id == selfID()) {
return;
}
m_msgReceived.send(receivedMsg);
}
void IpcSocket::onIpcMsg(const Meta& receivedMeta, const Msg& receivedMsg)
{
IPCLOG() << "received ipc msg: " << receivedMsg.method;
// answer on who is
if (receivedMsg.method == IPC_WHOIS) {
Msg answerMsg;
answerMsg.destID = SERVER_ID;
answerMsg.method = IPC_WHOIS;
answerMsg.args << selfID();
send(answerMsg);
return;
}
// receive meta info
if (receivedMsg.method == IPC_METAINFO) {
IPCLOG() << "received meta info from: " << receivedMeta.id << ", args: " << receivedMsg.args;
IF_ASSERT_FAILED(!receivedMsg.args.isEmpty()) {
return;
}
int count = receivedMsg.args.at(0).toInt();
IF_ASSERT_FAILED(count > 0) {
return;
}
m_instances.clear();
count += 1;
for (int i = 1; i < count; ++i) {
Meta meta;
meta.id = receivedMsg.args.at(i);
m_instances.append(meta);
}
m_instancesChanged.notify();
}
}
mu::async::Channel<Msg> IpcSocket::msgReceived() const
{
return m_msgReceived;
}
QList<Meta> IpcSocket::instances() const
{
return m_instances;
}
mu::async::Notification IpcSocket::instancesChanged() const
{
return m_instancesChanged;
}

View file

@ -0,0 +1,69 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_IPC_IPCSOCKET_H
#define MU_IPC_IPCSOCKET_H
#include "ipc.h"
#include "async/channel.h"
#include "async/notification.h"
#include "async/asyncable.h"
class QLocalSocket;
namespace mu::ipc {
class IpcLock;
class IpcSocket : public async::Asyncable
{
public:
IpcSocket() = default;
~IpcSocket();
const ID& selfID() const;
bool connect(const QString& serverName);
async::Notification disconected();
bool send(const Msg& msg);
async::Channel<Msg> msgReceived() const;
QList<Meta> instances() const;
async::Notification instancesChanged() const;
private:
void onReadyRead();
void onDataReceived(const QByteArray& data);
void onIpcMsg(const Meta& receivedMeta, const Msg& receivedMsg);
mutable ID m_selfID = 0;
IpcLock* m_lock = nullptr;
QLocalSocket* m_socket = nullptr;
async::Notification m_disconected;
async::Channel<Msg> m_msgReceived;
QList<Meta> m_instances;
async::Notification m_instancesChanged;
};
}
#endif // MU_IPC_IPCSOCKET_H

View file

@ -1,541 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ipcchannel.h"
#include <QLocalServer>
#include <QLocalSocket>
#include <QSystemSemaphore>
#include <QUuid>
#include <QDataStream>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#ifdef Q_OS_UNIX
#include <QFile>
#include <QDir>
#endif
#include "log.h"
//#define IPC_LOGGING_ENABLED
#undef MYLOG
#ifdef IPC_LOGGING_ENABLED
#define MYLOG() LOGI()
#else
#define MYLOG() LOGN()
#endif
using namespace mu::mi;
static const QString SERVER_NAME("musescore-app-ipc");
static const int RW_TIMEOUT = 500; // ms
static const int CHECK_CONNECT_INTERVAL = 500; // ms
static const QString DEST_BROADCAST("broadcast");
static const QString DEST_SOCKET("socket"); // direct to socket
static const QString DEST_SERVER("server"); // direct to server
static const QString IPC_("ipc_");
static const QString IPC_INIT("ipc_init");
static const QString IPC_WHOIS("ipc_whois");
static const QString IPC_METAINFO("ipc_metainfo");
static const QString IPC_PING("ipc_ping");
static QString socketErrorToString(QLocalSocket::LocalSocketError err)
{
switch (err) {
case QLocalSocket::ConnectionRefusedError: return "ConnectionRefusedError";
case QLocalSocket::PeerClosedError: return "PeerClosedError";
case QLocalSocket::ServerNotFoundError: return "ServerNotFoundError";
case QLocalSocket::SocketAccessError: return "SocketAccessError";
case QLocalSocket::SocketResourceError: return "SocketResourceError";
case QLocalSocket::SocketTimeoutError: return "SocketTimeoutError";
case QLocalSocket::DatagramTooLargeError: return "DatagramTooLargeError";
case QLocalSocket::ConnectionError: return "ConnectionError";
case QLocalSocket::UnsupportedSocketOperationError: return "UnsupportedSocketOperationError";
case QLocalSocket::UnknownSocketError: return "UnknownSocketError";
case QLocalSocket::OperationError: return "OperationError";
}
return "Unknown error";
}
IpcChannel::~IpcChannel()
{
delete m_serverLock;
delete m_selfSocket;
delete m_server;
}
// ============================================================
// Common
// ============================================================
const QString& IpcChannel::selfID() const
{
if (!m_selfID.isEmpty()) {
return m_selfID;
}
m_selfID = QUuid::createUuid().toString(QUuid::Id128);
return m_selfID;
}
bool IpcChannel::doSendToSocket(QLocalSocket* socket, const QByteArray& data) const
{
QDataStream stream(socket);
stream.writeBytes(data.constData(), data.size());
bool ok = socket->waitForBytesWritten(RW_TIMEOUT);
if (!ok) {
LOGE() << "failed send to socket";
}
return ok;
}
void IpcChannel::doReadSocket(QLocalSocket* socket, QByteArray& data) const
{
QDataStream stream(socket);
quint32 remaining;
stream >> remaining;
data.resize(remaining);
char* ptr = data.data();
stream.readRawData(ptr, remaining);
}
void IpcChannel::serialize(const Meta& meta, const Msg& msg, QByteArray& data) const
{
QJsonObject obj;
QJsonObject metaObj;
metaObj["id"] = meta.id;
metaObj["isServer"] = meta.isServer;
obj["meta"] = metaObj;
QJsonObject msgObj;
msgObj["destID"] = msg.destID;
msgObj["requestID"] = msg.requestID;
msgObj["method"] = msg.method;
QJsonArray argsArr;
for (const QString& arg : qAsConst(msg.args)) {
argsArr.append(arg);
}
msgObj["args"] = argsArr;
obj["msg"] = msgObj;
data = QJsonDocument(obj).toJson(QJsonDocument::Compact);
}
void IpcChannel::deserialize(const QByteArray& data, Meta& meta, Msg& msg) const
{
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject obj = doc.object();
QJsonObject metaObj = obj.value("meta").toObject();
meta.id = metaObj.value("id").toString();
meta.isServer = metaObj.value("isServer").toBool();
QJsonObject msgObj = obj.value("msg").toObject();
msg.destID = msgObj.value("destID").toString();
msg.requestID = msgObj.value("requestID").toInt();
msg.method = msgObj.value("method").toString();
QJsonArray argsArr = msgObj.value("args").toArray();
for (int i = 0; i < argsArr.count(); ++i) {
msg.args << argsArr.at(i).toString();
}
}
// ============================================================
// Self socket
// ============================================================
bool IpcChannel::init()
{
Msg msg;
msg.destID = DEST_SERVER;
msg.method = IPC_INIT;
return send(msg);
}
void IpcChannel::ping()
{
Msg msg;
msg.destID = DEST_SERVER;
msg.method = IPC_PING;
send(msg);
}
bool IpcChannel::send(const Msg& msg)
{
if (!checkConnectToServer()) {
if (!m_serverLock) {
m_serverLock = new QSystemSemaphore(SERVER_NAME, 1, QSystemSemaphore::Open);
}
m_serverLock->acquire();
//! NOTE Check again
if (!checkConnectToServer()) {
//! NOTE If it was not possible to connect to the server, then it no there or was, but it was closed.
//! In this case, we will become a server
makeServer();
}
m_serverLock->release();
//! NOTE Connect to self server
checkConnectToServer();
}
return sendToSocket(m_selfSocket, msg);
}
bool IpcChannel::sendToSocket(QLocalSocket* socket, const Msg& msg) const
{
Meta meta;
meta.id = selfID();
meta.isServer = selfIsServer();
QByteArray data;
serialize(meta, msg, data);
return doSendToSocket(socket, data);
}
void IpcChannel::onDataReceived(const QByteArray& data)
{
MYLOG() << "received: " << data;
Meta receivedMeta;
Msg receivedMsg;
deserialize(data, receivedMeta, receivedMsg);
if (receivedMsg.method.startsWith(IPC_)) {
onIpcMsg(receivedMeta, receivedMsg);
return;
}
if (receivedMeta.id == selfID()) {
return;
}
m_listenCh.send(receivedMsg);
}
void IpcChannel::onIpcMsg(const Meta& receivedMeta, const Msg& receivedMsg)
{
MYLOG() << "received ipc msg: " << receivedMsg.method;
// answer on who is
if (receivedMsg.method == IPC_WHOIS) {
Msg answerMsg;
answerMsg.destID = DEST_SERVER;
answerMsg.method = IPC_WHOIS;
answerMsg.args << selfID();
sendToSocket(m_selfSocket, answerMsg);
return;
}
// receive meta info
if (receivedMsg.method == IPC_METAINFO) {
MYLOG() << "received meta info from: " << receivedMeta.id << ", args: " << receivedMsg.args;
IF_ASSERT_FAILED(!receivedMsg.args.isEmpty()) {
return;
}
int count = receivedMsg.args.at(0).toInt();
IF_ASSERT_FAILED(count > 0) {
return;
}
m_instances.clear();
count += 1;
for (int i = 1; i < count; ++i) {
Meta meta;
meta.id = receivedMsg.args.at(i);
meta.isServer = receivedMeta.id == meta.id; // must be received only from server
m_instances.append(meta);
}
m_instancesChanged.notify();
}
}
mu::async::Channel<IpcChannel::Msg> IpcChannel::listen()
{
return m_listenCh;
}
bool IpcChannel::checkConnectToServer()
{
if (!m_selfSocket) {
m_selfSocket = new QLocalSocket();
QObject::connect(m_selfSocket, &QLocalSocket::errorOccurred, [this](QLocalSocket::LocalSocketError err) {
MYLOG() << "socket error: " << socketErrorToString(err);
//! NOTE If the server is down, then we will try to connect to another or create a server ourselves
if (err == QLocalSocket::PeerClosedError) {
uint64_t min = 1;
uint64_t max = 100;
int interval = static_cast<int>(reinterpret_cast<uint64_t>(this) % (max - min + 1) + min);
QTimer::singleShot(interval, [this]() {
LOGI() << "try send ping";
ping();
});
}
});
QObject::connect(m_selfSocket, &QLocalSocket::readyRead, [this]() {
QByteArray data;
doReadSocket(m_selfSocket, data);
onDataReceived(data);
});
}
if (m_selfSocket->state() == QLocalSocket::ConnectedState) {
return true;
}
m_selfSocket->connectToServer(SERVER_NAME);
bool ok = m_selfSocket->waitForConnected(500);
if (!ok) {
LOGW() << "failed connect to server, err: " << socketErrorToString(m_selfSocket->error());
return false;
}
LOGI() << "success connected to ipc server";
return true;
}
QList<IpcChannel::Meta> IpcChannel::instances() const
{
return m_instances;
}
mu::async::Notification IpcChannel::instancesChanged() const
{
return m_instancesChanged;
}
// ============================================================
// Me as server
// ============================================================
bool IpcChannel::selfIsServer() const
{
return m_server != nullptr;
}
bool IpcChannel::makeServer()
{
LOGI() << "make new ipc server, selfID: " << selfID();
m_server = new QLocalServer();
bool ok = m_server->listen(SERVER_NAME);
#ifdef Q_OS_UNIX
// Workaround
if (!ok && m_server->serverError() == QAbstractSocket::AddressInUseError) {
QFile::remove(QDir::cleanPath(QDir::tempPath()) + QLatin1Char('/') + SERVER_NAME);
ok = m_server->listen(SERVER_NAME);
}
#endif
if (!ok) {
LOGE() << "failed listen: " << m_server->errorString();
return false;
}
QObject::connect(m_server, &QLocalServer::newConnection, [this]() {
QLocalSocket* socket = m_server->nextPendingConnection();
MYLOG() << "newConnection socket: " << socket;
if (!socket) {
return;
}
QObject::connect(socket, &QLocalSocket::errorOccurred, [](QLocalSocket::LocalSocketError err) {
LOGE() << "socket error: " << socketErrorToString(err);
});
QObject::connect(socket, &QLocalSocket::disconnected, [socket, this]() {
MYLOG() << "disconnected socket: " << socket;
onDisconnected(socket);
});
QObject::connect(socket, &QLocalSocket::readyRead, [socket, this]() {
onIncomingReadyRead(socket);
});
IncomingSocket inc;
inc.socket = socket;
m_incomingSockets.append(inc);
askWhoIs(socket);
});
return true;
}
void IpcChannel::askWhoIs(QLocalSocket* socket) const
{
Msg askMsg;
askMsg.destID = DEST_SOCKET;
askMsg.method = IPC_WHOIS;
sendToSocket(socket, askMsg);
}
void IpcChannel::onIncomingReadyRead(QLocalSocket* socket)
{
QByteArray data;
doReadSocket(socket, data);
Meta meta;
Msg msg;
deserialize(data, meta, msg);
MYLOG() << "incoming [" << meta.id << "] data: " << data;
if (msg.method == IPC_INIT) {
onIncomingInit(socket, meta, msg);
}
if (msg.method == IPC_WHOIS) {
onIncomingWhoIs(socket, meta, msg);
}
if (msg.method == IPC_PING) {
onIncomingPing(socket, meta, msg);
}
//! NOTE Resend to others (broadcast)
if (msg.destID == DEST_BROADCAST) {
for (IncomingSocket& s : m_incomingSockets) {
//! NOTE We do not resend to incoming socket
//! In addition to the metainfo message, we send it to ourselves socket too
if (socket != s.socket || msg.method == IPC_METAINFO) {
MYLOG() << "resend to " << s.meta.id;
doSendToSocket(s.socket, data);
}
}
}
}
void IpcChannel::onIncomingInit(QLocalSocket* socket, const Meta& meta, const Msg& msg)
{
UNUSED(msg);
MYLOG() << "init from: " << meta.id;
IncomingSocket& s = incomingSocket(socket);
if (!s.socket) {
LOGE() << "not found incoming socket";
return;
}
s.meta = meta;
sendMetaInfoToAllIncoming();
}
void IpcChannel::onIncomingWhoIs(QLocalSocket* socket, const Meta& meta, const Msg& msg)
{
UNUSED(msg);
MYLOG() << "who is answer: " << meta.id;
IncomingSocket& s = incomingSocket(socket);
if (!s.socket) {
LOGE() << "not found incoming socket";
return;
}
s.meta = meta;
sendMetaInfoToAllIncoming();
}
void IpcChannel::onIncomingPing(QLocalSocket* socket, const Meta& meta, const Msg& msg)
{
UNUSED(msg);
MYLOG() << "ping from: " << meta.id;
IncomingSocket& s = incomingSocket(socket);
if (!s.socket) {
LOGE() << "not found incoming socket";
return;
}
s.meta = meta;
sendMetaInfoToAllIncoming();
}
void IpcChannel::onDisconnected(QLocalSocket* socket)
{
int index = -1;
for (int i = 0; i < m_incomingSockets.count(); ++i) {
if (m_incomingSockets.at(i).socket == socket) {
index = i;
break;
}
}
if (index < 0) {
LOGW() << "not found socket";
return;
}
m_incomingSockets.removeAt(index);
sendMetaInfoToAllIncoming();
}
IpcChannel::IncomingSocket& IpcChannel::incomingSocket(QLocalSocket* socket)
{
for (IncomingSocket& s : m_incomingSockets) {
if (s.socket == socket) {
return s;
}
}
static IncomingSocket null;
return null;
}
void IpcChannel::sendMetaInfoToAllIncoming()
{
Msg msg;
msg.destID = DEST_BROADCAST;
msg.method = IPC_METAINFO;
msg.args << QString::number(m_incomingSockets.count());
for (const IncomingSocket& s : qAsConst(m_incomingSockets)) {
msg.args << s.meta.id;
}
sendToSocket(m_selfSocket, msg);
}

View file

@ -1,121 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_MI_IPCCHANNEL_H
#define MU_MI_IPCCHANNEL_H
#include <QString>
#include <QList>
#include "async/channel.h"
#include "async/notification.h"
class QLocalSocket;
class QLocalServer;
class QSystemSemaphore;
namespace mu::mi {
//! NOTE Inter-Process Communication Channel
class IpcChannel
{
public:
IpcChannel() = default;
~IpcChannel();
struct Meta
{
QString id = 0;
bool isServer = false;
bool isValid() const { return !id.isEmpty(); }
};
struct Msg
{
QString destID;
int requestID = 0;
QString method;
QStringList args;
};
const QString& selfID() const;
bool init();
void ping();
bool send(const Msg& msg);
async::Channel<Msg> listen();
QList<Meta> instances() const;
async::Notification instancesChanged() const;
private:
// common
bool sendToSocket(QLocalSocket* socket, const Msg& msg) const;
bool doSendToSocket(QLocalSocket* socket, const QByteArray& data) const;
void doReadSocket(QLocalSocket* socket, QByteArray& data) const;
void serialize(const Meta& meta, const Msg& msg, QByteArray& data) const;
void deserialize(const QByteArray& data, Meta& meta, Msg& msg) const;
// self socket
bool checkConnectToServer();
void onDataReceived(const QByteArray& data);
void onIpcMsg(const Meta& receivedMeta, const Msg& receivedMsg);
// me as server
bool selfIsServer() const;
bool makeServer();
void onIncomingReadyRead(QLocalSocket* socket);
void onDisconnected(QLocalSocket* socket);
void askWhoIs(QLocalSocket* socket) const;
void onIncomingInit(QLocalSocket* socket, const Meta& meta, const Msg& msg);
void onIncomingWhoIs(QLocalSocket* socket, const Meta& meta, const Msg& msg);
void onIncomingPing(QLocalSocket* socket, const Meta& meta, const Msg& msg);
void sendMetaInfoToAllIncoming();
struct IncomingSocket
{
Meta meta;
QLocalSocket* socket = nullptr;
};
IncomingSocket& incomingSocket(QLocalSocket* socket);
mutable QString m_selfID = 0;
QLocalSocket* m_selfSocket = nullptr;
QSystemSemaphore* m_serverLock = nullptr;
QLocalServer* m_server = nullptr;
QList<IncomingSocket> m_incomingSockets;
async::Channel<Msg> m_listenCh;
QList<Meta> m_instances;
async::Notification m_instancesChanged;
};
}
#endif // MU_MI_IPCCHANNEL_H

View file

@ -22,9 +22,11 @@
#include "multiinstancesprovider.h"
#include "uri.h"
#include "ipcchannel.h"
#include "log.h"
using namespace mu::mi;
using namespace mu::ipc;
static const mu::UriQuery DEV_SHOW_INFO_URI("musescore://devtools/multiinstances/info?sync=false&modal=false");
@ -44,17 +46,83 @@ void MultiInstancesProvider::init()
m_ipcChannel = new IpcChannel();
m_selfID = m_ipcChannel->selfID().toStdString();
m_ipcChannel->msgReceived().onReceive(this, [this](const Msg& msg) {
onMsg(msg);
});
m_ipcChannel->instancesChanged().onNotify(this, [this]() {
m_instancesChanged.notify();
});
m_ipcChannel->init();
m_ipcChannel->connect();
m_timeout.setSingleShot(true);
QObject::connect(&m_timeout, &QTimer::timeout, [this]() {
LOGI() << "timeout";
m_loop.quit();
});
}
void MultiInstancesProvider::onMsg(const Msg& msg)
{
LOGI() << msg.method;
if (m_onMsg) {
m_onMsg(msg);
}
if (msg.type == MsgType::Request && msg.method == "score_is_opened") {
Msg answer;
answer.destID = ipc::BROADCAST_ID;
answer.type = MsgType::Response;
answer.method = "score_is_opened";
answer.args << QString::number(0);
m_ipcChannel->send(answer);
}
}
bool MultiInstancesProvider::isScoreAlreadyOpened(const io::path& scorePath) const
{
Q_UNUSED(scorePath);
return false;
//! NOTE Temporary solution, I will think how do this better
int total = m_ipcChannel->instances().count();
if (total < 2) {
LOGD() << "only one instance";
return false;
}
Msg msg;
msg.destID = ipc::BROADCAST_ID;
msg.type = MsgType::Request;
msg.method = "score_is_opened";
msg.args << scorePath.toQString();
total -= 1;
int recived = 0;
bool ret = false;
m_onMsg = [this, total, &recived, &ret](const Msg& msg) {
if (!(msg.type == MsgType::Response && msg.method == "score_is_opened")) {
return;
}
++recived;
ret = msg.args.at(0).toInt();
if (ret || recived == total) {
m_timeout.stop();
m_loop.quit();
}
};
LOGI() << "send Request, selfID: " << m_ipcChannel->selfID();
m_ipcChannel->send(msg);
m_timeout.start(500);
m_loop.exec();
m_onMsg = nullptr;
return ret;
}
void MultiInstancesProvider::activateWindowForScore(const io::path& scorePath)
@ -67,19 +135,13 @@ const std::string& MultiInstancesProvider::selfID() const
return m_selfID;
}
void MultiInstancesProvider::ping()
{
m_ipcChannel->ping();
}
std::vector<InstanceMeta> MultiInstancesProvider::instances() const
{
std::vector<InstanceMeta> ret;
QList<IpcChannel::Meta> ints = m_ipcChannel->instances();
for (const IpcChannel::Meta& m : qAsConst(ints)) {
QList<Meta> ints = m_ipcChannel->instances();
for (const Meta& m : qAsConst(ints)) {
InstanceMeta im;
im.id = m.id.toStdString();
im.isServer = m.isServer;
ret.push_back(std::move(im));
}

View file

@ -24,14 +24,18 @@
#include "../imultiinstancesprovider.h"
#include <functional>
#include <QEventLoop>
#include <QTimer>
#include "modularity/ioc.h"
#include "actions/iactionsdispatcher.h"
#include "actions/actionable.h"
#include "iinteractive.h"
#include "async/asyncable.h"
#include "ipc/ipcchannel.h"
namespace mu::mi {
class IpcChannel;
class MultiInstancesProvider : public IMultiInstancesProvider, public actions::Actionable, public async::Asyncable
{
INJECT(mi, actions::IActionsDispatcher, dispatcher)
@ -47,14 +51,20 @@ public:
void activateWindowForScore(const io::path& scorePath) override;
const std::string& selfID() const override;
void ping() override;
std::vector<InstanceMeta> instances() const override;
async::Notification instancesChanged() const override;
private:
IpcChannel* m_ipcChannel = nullptr;
void onMsg(const ipc::Msg& msg);
ipc::IpcChannel* m_ipcChannel = nullptr;
std::string m_selfID;
mutable QEventLoop m_loop;
mutable QTimer m_timeout;
mutable std::function<void(const ipc::Msg& msg)> m_onMsg;
async::Notification m_instancesChanged;
};
}

View file

@ -22,8 +22,7 @@
#include "multiinstancesmodule.h"
#include <QQmlEngine>
#include <QTimer>
#include "internal/ipcchannel.h"
#include "internal/multiinstancesuiactions.h"
#include "internal/multiinstancesprovider.h"
@ -36,10 +35,6 @@
using namespace mu::mi;
using namespace mu::framework;
//static QTimer s_testTimer;
//static IpcChannel s_testChannel;
//static int s_testCounter = 0;
static std::shared_ptr<MultiInstancesProvider> s_multiInstancesProvider = std::make_shared<MultiInstancesProvider>();
static void multiinstances_init_qrc()
@ -87,19 +82,4 @@ void MultiInstancesModule::onInit(const IApplication::RunMode& mode)
}
s_multiInstancesProvider->init();
// s_testTimer.setInterval(2000);
// s_testTimer.setSingleShot(true);
// QObject::connect(&s_testTimer, &QTimer::timeout, []() {
// ++s_testCounter;
// IpcChannel::Msg msg;
// msg.method = "test_ping";
// msg.args << QString::number(s_testCounter);
// s_testChannel.send(msg);
// s_testTimer.start();
// });
// s_testTimer.start();
}

View file

@ -245,6 +245,11 @@ Ret FileScoreController::doOpenScore(const io::path& filePath)
{
TRACEFUNC;
if (multiInstancesProvider()->isScoreAlreadyOpened(filePath)) {
multiInstancesProvider()->activateWindowForScore(filePath);
return make_ret(Ret::Code::Ok);
}
auto notation = notationCreator()->newMasterNotation();
IF_ASSERT_FAILED(notation) {
return make_ret(Ret::Code::InternalError);

View file

@ -32,6 +32,7 @@
#include "notation/inotationcreator.h"
#include "context/iglobalcontext.h"
#include "iplatformrecentfilescontroller.h"
#include "multiinstances/imultiinstancesprovider.h"
namespace mu::userscores {
class FileScoreController : public IFileScoreController, public actions::Actionable, public async::Asyncable
@ -42,6 +43,7 @@ class FileScoreController : public IFileScoreController, public actions::Actiona
INJECT(userscores, context::IGlobalContext, globalContext)
INJECT(userscores, IUserScoresConfiguration, configuration)
INJECT(userscores, IPlatformRecentFilesController, platformRecentFilesController)
INJECT(userscores, mi::IMultiInstancesProvider, multiInstancesProvider)
public:
void init();