mirror of https://github.com/qt/qtgrpc.git
273 lines
8.1 KiB
C++
273 lines
8.1 KiB
C++
// Copyright (C) 2024 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
#include "chatengine.h"
|
|
#include "chatmessages.qpb.h"
|
|
#include "clientworker.h"
|
|
|
|
#include <QtGui/QClipboard>
|
|
#include <QtGui/QDesktopServices>
|
|
#include <QtGui/QGuiApplication>
|
|
#include <QtGui/QImage>
|
|
|
|
#include <QtNetwork/QHostAddress>
|
|
#include <QtNetwork/QNetworkInterface>
|
|
|
|
#include <QtCore/QDateTime>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QList>
|
|
#include <QtCore/QUrl>
|
|
|
|
ChatEngine::ChatEngine(QObject *parent)
|
|
: QObject(parent), m_clientWorker(new ClientWorker()), m_clipboard(QGuiApplication::clipboard())
|
|
{
|
|
initialize();
|
|
#if QT_CONFIG(ssl)
|
|
setHostUri(QUrl("https://localhost:65002"));
|
|
#else
|
|
setHostUri(QUrl("http://localhost:65003"));
|
|
#endif
|
|
//! [client-3]
|
|
m_clientWorker->moveToThread(&m_clientThread);
|
|
m_clientThread.start();
|
|
connect(&m_clientThread, &QThread::finished, m_clientWorker, &QObject::deleteLater);
|
|
connect(m_clientWorker, &ClientWorker::registerFinished, this, &ChatEngine::registerFinished);
|
|
connect(m_clientWorker, &ClientWorker::chatError, this, &ChatEngine::chatError);
|
|
//! [client-3]
|
|
connect(m_clientWorker, &ClientWorker::chatStreamFinished, this,
|
|
[this](const QGrpcStatus &status) {
|
|
initialize();
|
|
emit chatStreamFinished(status);
|
|
});
|
|
connect(m_clientWorker, &ClientWorker::chatStateChanged, this,
|
|
[this](Backend::ChatState state) {
|
|
if (state == Backend::ChatState::Connected) {
|
|
m_inactivityTimer.start();
|
|
m_userStatus = chat::UserStatus::Type::ACTIVE;
|
|
}
|
|
m_chatState = state;
|
|
emit chatStateChanged();
|
|
});
|
|
|
|
connect(m_clientWorker, &ClientWorker::chatStreamMessageReceived, this,
|
|
[this](const chat::ChatMessage &message) {
|
|
// The client worker already does some message pre-processing so that
|
|
// we just have to care about updating the models correctly.
|
|
bool newUser = m_userStatusModel->updateUserStatus(message);
|
|
if (message.hasUserStatus()
|
|
&& message.userStatus().type() == chat::UserStatus::Type::LOGIN) {
|
|
if (!newUser)
|
|
return; // don't append reconnects
|
|
if (message.username() == username())
|
|
return; // don't append our own login
|
|
}
|
|
m_chatMessageModel->appendMessage(message);
|
|
});
|
|
|
|
// Simple mechanism to detect user absence from the application
|
|
m_inactivityTimer.setInterval(120'000); // Inactive after 2min
|
|
connect(&m_inactivityTimer, &QTimer::timeout, this, [this]() {
|
|
setUserStatus(chat::UserStatus::Type::INACTIVE);
|
|
});
|
|
}
|
|
|
|
ChatEngine::~ChatEngine()
|
|
{
|
|
m_clientThread.quit();
|
|
m_clientThread.wait();
|
|
}
|
|
|
|
//! [client-4a]
|
|
void ChatEngine::registerUser(const chat::Credentials &credentials)
|
|
{
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::registerUser, credentials);
|
|
}
|
|
//! [client-4a]
|
|
|
|
void ChatEngine::login(const chat::Credentials &credentials)
|
|
{
|
|
m_userCredentials = credentials;
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::login, credentials);
|
|
}
|
|
|
|
void ChatEngine::logout()
|
|
{
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::logout);
|
|
}
|
|
|
|
//! [client-7a]
|
|
void ChatEngine::sendText(const QString &message)
|
|
{
|
|
if (message.trimmed().isEmpty())
|
|
return;
|
|
|
|
if (auto request = m_clientWorker->createMessage()) {
|
|
chat::TextMessage tmsg;
|
|
tmsg.setContent(message.toUtf8());
|
|
request->setText(std::move(tmsg));
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::sendMessage, *request);
|
|
m_chatMessageModel->appendMessage(*request);
|
|
}
|
|
}
|
|
//! [client-7a]
|
|
|
|
void ChatEngine::sendFile(const QUrl &url)
|
|
{
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::sendFile, url);
|
|
}
|
|
|
|
void ChatEngine::sendFiles(const QList<QUrl> &urls)
|
|
{
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::sendFiles, urls);
|
|
}
|
|
|
|
bool ChatEngine::sendFilesFromClipboard()
|
|
{
|
|
if (!m_clipboard)
|
|
return false;
|
|
|
|
const QMimeData *mime = m_clipboard->mimeData();
|
|
if (!mime)
|
|
return false;
|
|
|
|
if (mime->hasImage()) {
|
|
auto img = mime->imageData().value<QImage>();
|
|
if (img.isNull()) {
|
|
qDebug("Received empty clipbaord image");
|
|
return false;
|
|
}
|
|
|
|
const auto downloadDir = m_clientWorker->getValidDownloadDir();
|
|
if (!downloadDir)
|
|
return false;
|
|
const auto path = downloadDir->path() + QDir::separator() + "clipboard_"
|
|
+ QUuid::createUuid().toString() + ".png";
|
|
|
|
if (!img.save(path)) {
|
|
qWarning() << "Couldn't write clipboard image: " << path;
|
|
return false;
|
|
}
|
|
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::sendFile,
|
|
QUrl::fromLocalFile(path));
|
|
return true;
|
|
} else if (mime->hasUrls()) {
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::sendFiles, mime->urls());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ChatEngine::openUrl(const QByteArray &url)
|
|
{
|
|
QDesktopServices::openUrl(QUrl(url));
|
|
}
|
|
|
|
QList<QString> ChatEngine::findLocalIps()
|
|
{
|
|
QList<QString> out;
|
|
const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
|
|
for (const QHostAddress &address : addresses) {
|
|
if (address.protocol() == QAbstractSocket::IPv4Protocol && !address.isLoopback())
|
|
out.append(address.toString());
|
|
}
|
|
return out;
|
|
}
|
|
|
|
QList<QString> ChatEngine::displayServerPorts()
|
|
{
|
|
return {
|
|
#if QT_CONFIG(ssl)
|
|
"65002 (https)",
|
|
#endif
|
|
"65003 (http)",
|
|
};
|
|
}
|
|
|
|
QString ChatEngine::getSchemeSymbol()
|
|
{
|
|
#ifdef USE_EMOJI_FONT
|
|
const auto s = m_hostUri.scheme();
|
|
if (s == "https")
|
|
return "🛡️";
|
|
return "🔓";
|
|
#else
|
|
return "";
|
|
#endif
|
|
}
|
|
|
|
void ChatEngine::setHostUri(const QUrl &hostUri)
|
|
{
|
|
if (m_hostUri == hostUri)
|
|
return;
|
|
m_hostUri = hostUri;
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::setHostUri, hostUri);
|
|
emit hostUriChanged();
|
|
}
|
|
|
|
bool ChatEngine::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::KeyPress || event->type() == QEvent::MouseMove
|
|
|| event->type() == QEvent::TouchBegin || event->type() == QEvent::Enter) {
|
|
|
|
if (m_userStatus == chat::UserStatus::Type::INACTIVE)
|
|
setUserStatus(chat::UserStatus::Type::ACTIVE);
|
|
|
|
// Restart timer only if user is active
|
|
if (m_userStatus == chat::UserStatus::Type::ACTIVE)
|
|
m_inactivityTimer.start();
|
|
}
|
|
|
|
if (event->type() == QEvent::Close) {
|
|
// Log out active session and delay quitting to ensure graceful shutdown.
|
|
logout();
|
|
QTimer::singleShot(125, QCoreApplication::quit);
|
|
return true;
|
|
}
|
|
|
|
return QObject::eventFilter(obj, event);
|
|
}
|
|
|
|
void ChatEngine::initialize()
|
|
{
|
|
m_userCredentials = {};
|
|
m_userStatus = {};
|
|
m_userStatusModel = std::make_unique<UserStatusModel>();
|
|
m_chatMessageModel = std::make_unique<ChatMessageModel>();
|
|
connect(m_clientWorker, &ClientWorker::fileMessageSent, m_chatMessageModel.get(),
|
|
&ChatMessageModel::appendMessage);
|
|
}
|
|
|
|
void ChatEngine::setUserStatus(chat::UserStatus::Type status)
|
|
{
|
|
if (m_userStatus == status || m_userStatus == chat::UserStatus::Type::NONE)
|
|
return;
|
|
|
|
switch (status) {
|
|
case chat::UserStatus::Type::ACTIVE:
|
|
m_inactivityTimer.start();
|
|
break;
|
|
case chat::UserStatus::Type::INACTIVE:
|
|
m_inactivityTimer.stop();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
m_userStatus = status;
|
|
|
|
auto request = m_clientWorker->createMessage();
|
|
if (!request)
|
|
return;
|
|
|
|
chat::UserStatus userStatus;
|
|
userStatus.setType(m_userStatus);
|
|
request->setUserStatus(std::move(userStatus));
|
|
|
|
// Send status update to the server and update all models.
|
|
m_userStatusModel->updateUserStatus(*request);
|
|
QMetaObject::invokeMethod(m_clientWorker, &ClientWorker::sendMessage, *request);
|
|
m_chatMessageModel->appendMessage(*request);
|
|
}
|