moved ipc server to separated thread
This commit is contained in:
parent
b8a536c404
commit
bcff394bfa
21 changed files with 1214 additions and 702 deletions
|
@ -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
|
||||
)
|
||||
|
|
|
@ -52,7 +52,6 @@ void MultiInstancesDevModel::update()
|
|||
|
||||
void MultiInstancesDevModel::ping()
|
||||
{
|
||||
multiInstancesProvider()->ping();
|
||||
}
|
||||
|
||||
const QVariantList& MultiInstancesDevModel::instances() const
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
90
src/multiinstances/internal/ipc/ipc.cpp
Normal file
90
src/multiinstances/internal/ipc/ipc.cpp
Normal 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";
|
||||
}
|
76
src/multiinstances/internal/ipc/ipc.h
Normal file
76
src/multiinstances/internal/ipc/ipc.h
Normal 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
|
105
src/multiinstances/internal/ipc/ipcchannel.cpp
Normal file
105
src/multiinstances/internal/ipc/ipcchannel.cpp
Normal 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();
|
||||
}
|
64
src/multiinstances/internal/ipc/ipcchannel.h
Normal file
64
src/multiinstances/internal/ipc/ipcchannel.h
Normal 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
|
46
src/multiinstances/internal/ipc/ipclock.cpp
Normal file
46
src/multiinstances/internal/ipc/ipclock.cpp
Normal 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();
|
||||
}
|
43
src/multiinstances/internal/ipc/ipclock.h
Normal file
43
src/multiinstances/internal/ipc/ipclock.h
Normal 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
|
37
src/multiinstances/internal/ipc/ipclog.h
Normal file
37
src/multiinstances/internal/ipc/ipclog.h
Normal 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
|
303
src/multiinstances/internal/ipc/ipcserver.cpp
Normal file
303
src/multiinstances/internal/ipc/ipcserver.cpp
Normal 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;
|
||||
}
|
74
src/multiinstances/internal/ipc/ipcserver.h
Normal file
74
src/multiinstances/internal/ipc/ipcserver.h
Normal 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
|
200
src/multiinstances/internal/ipc/ipcsocket.cpp
Normal file
200
src/multiinstances/internal/ipc/ipcsocket.cpp
Normal 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;
|
||||
}
|
69
src/multiinstances/internal/ipc/ipcsocket.h
Normal file
69
src/multiinstances/internal/ipc/ipcsocket.h
Normal 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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue