2023-01-20 16:43:29 +00:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
|
|
|
// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>
|
2024-03-15 13:09:34 +00:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
2023-01-20 16:43:29 +00:00
|
|
|
|
|
|
|
#include "simplechatengine.h"
|
|
|
|
|
|
|
|
#include <QGrpcHttp2Channel>
|
2024-07-30 12:07:28 +00:00
|
|
|
#include <QGrpcChannelOptions>
|
2023-01-20 16:43:29 +00:00
|
|
|
|
2024-01-20 11:44:39 +00:00
|
|
|
#include <qprotobufregistration.h>
|
|
|
|
|
2023-01-20 16:43:29 +00:00
|
|
|
#include <QDebug>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QCryptographicHash>
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <QClipboard>
|
|
|
|
#include <QGuiApplication>
|
|
|
|
#include <QMimeData>
|
|
|
|
#include <QImage>
|
|
|
|
#include <QByteArray>
|
|
|
|
#include <QBuffer>
|
|
|
|
|
2024-09-12 12:54:42 +00:00
|
|
|
#include <memory>
|
|
|
|
|
2023-01-20 16:43:29 +00:00
|
|
|
SimpleChatEngine::SimpleChatEngine(QObject *parent)
|
|
|
|
: QObject(parent),
|
|
|
|
m_state(Disconnected),
|
|
|
|
m_client(new qtgrpc::examples::chat::SimpleChat::Client),
|
|
|
|
m_clipBoard(QGuiApplication::clipboard())
|
|
|
|
{
|
2024-01-17 18:25:15 +00:00
|
|
|
if (m_clipBoard) {
|
2023-01-20 16:43:29 +00:00
|
|
|
QObject::connect(m_clipBoard, &QClipboard::dataChanged, this,
|
|
|
|
&SimpleChatEngine::clipBoardContentTypeChanged);
|
2024-01-17 18:25:15 +00:00
|
|
|
}
|
2023-01-20 16:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SimpleChatEngine::~SimpleChatEngine()
|
|
|
|
{
|
|
|
|
delete m_client;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleChatEngine::login(const QString &name, const QString &password)
|
|
|
|
{
|
|
|
|
if (m_state != Disconnected)
|
|
|
|
return;
|
|
|
|
|
|
|
|
setState(Connecting);
|
|
|
|
QUrl url("http://localhost:65002");
|
|
|
|
|
|
|
|
// ![0]
|
2024-05-07 18:00:38 +00:00
|
|
|
QGrpcChannelOptions channelOptions;
|
2024-07-23 16:10:49 +00:00
|
|
|
QHash<QByteArray, QByteArray> metadata = {
|
2023-05-22 08:22:01 +00:00
|
|
|
{ "user-name", { name.toUtf8() } },
|
|
|
|
{ "user-password", { password.toUtf8() } },
|
|
|
|
};
|
2024-05-22 10:49:51 +00:00
|
|
|
channelOptions.setMetadata(metadata);
|
2024-05-07 18:00:38 +00:00
|
|
|
std::shared_ptr<QAbstractGrpcChannel>
|
|
|
|
channel = std::make_shared<QGrpcHttp2Channel>(url, channelOptions);
|
2023-01-20 16:43:29 +00:00
|
|
|
// ![0]
|
|
|
|
|
|
|
|
m_client->attachChannel(channel);
|
|
|
|
|
|
|
|
// ![1]
|
2024-05-10 12:43:10 +00:00
|
|
|
auto stream = m_client->messageList(qtgrpc::examples::chat::None());
|
Migrate to std::unique_ptr return value for all RPCs
The use of shared pointers has potential risk of storing the
QGrpcOperation children forever and leaking in user code. The problem
is clearly in the lambda connections that we encourage to use in the
docs and examples:
auto stream = testStream(...);
QObject::connect(stream.get(), &QGrpcOperation::finished,
ctx, [ctx, stream]{...});
The above code will hold the 'stream' forever, unless user will make
the explicit disconnect in the lambda.
By using std::unique_ptr we partially solve this, or at least convince
user to solve this. When user creates lambda he knows the 'stream'
lifetime and most probably should consider that after the move, lambda
is owning the QGrpcOperation, so the need of disconnect is more clear
in this case:
auto stream = testStream(...);
auto *streamPtr = stream.get();
QObject::connect(streamPtr, &QGrpcOperation::finished,
ctx, [ctx, stream = std::move(stream)]{...});
The code becomes a bit more complicated, but it points explicitly to
the potential risk. Also it disallows to make this trick to multiple
lambdas at the same time.
Of course using the lambda context to control the QGrpcOperation
lifetime in this case is not necessary. But even if users will decide
to manage the QGrpcOperation lifetime differently, the use of
std::unique_ptr will clearly point to the ownership.
[ChangeLog][Grpc] All generated RPC methods now return std::unique_ptr
instead of std::shared_ptr. This change explicitly defines that caller
takes the ownership of the returned pointers.
Pick-to: 6.8
Change-Id: I271b91454f0c1b12b77127a7e025fa493367e279
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2024-08-28 15:15:33 +00:00
|
|
|
auto streamPtr = stream.get();
|
2024-08-08 09:26:41 +00:00
|
|
|
QObject::connect(
|
|
|
|
streamPtr, &QGrpcServerStream::finished, this,
|
|
|
|
[this, stream = std::move(stream)](const QGrpcStatus &status) {
|
|
|
|
if (!status.isOk()) {
|
|
|
|
qCritical() << "Stream error(" << status.code() << "):" << status.message();
|
|
|
|
}
|
|
|
|
if (status.code() == QtGrpc::StatusCode::Unauthenticated) {
|
|
|
|
emit authFailed();
|
|
|
|
} else if (status.code() != QtGrpc::StatusCode::Ok) {
|
|
|
|
emit networkError(status.message());
|
|
|
|
setState(Disconnected);
|
|
|
|
} else {
|
|
|
|
setState(Disconnected);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Qt::SingleShotConnection);
|
2023-01-20 16:43:29 +00:00
|
|
|
|
Migrate to std::unique_ptr return value for all RPCs
The use of shared pointers has potential risk of storing the
QGrpcOperation children forever and leaking in user code. The problem
is clearly in the lambda connections that we encourage to use in the
docs and examples:
auto stream = testStream(...);
QObject::connect(stream.get(), &QGrpcOperation::finished,
ctx, [ctx, stream]{...});
The above code will hold the 'stream' forever, unless user will make
the explicit disconnect in the lambda.
By using std::unique_ptr we partially solve this, or at least convince
user to solve this. When user creates lambda he knows the 'stream'
lifetime and most probably should consider that after the move, lambda
is owning the QGrpcOperation, so the need of disconnect is more clear
in this case:
auto stream = testStream(...);
auto *streamPtr = stream.get();
QObject::connect(streamPtr, &QGrpcOperation::finished,
ctx, [ctx, stream = std::move(stream)]{...});
The code becomes a bit more complicated, but it points explicitly to
the potential risk. Also it disallows to make this trick to multiple
lambdas at the same time.
Of course using the lambda context to control the QGrpcOperation
lifetime in this case is not necessary. But even if users will decide
to manage the QGrpcOperation lifetime differently, the use of
std::unique_ptr will clearly point to the ownership.
[ChangeLog][Grpc] All generated RPC methods now return std::unique_ptr
instead of std::shared_ptr. This change explicitly defines that caller
takes the ownership of the returned pointers.
Pick-to: 6.8
Change-Id: I271b91454f0c1b12b77127a7e025fa493367e279
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2024-08-28 15:15:33 +00:00
|
|
|
QObject::connect(streamPtr, &QGrpcServerStream::messageReceived, this,
|
|
|
|
[this, name, password, stream = streamPtr]() {
|
2023-08-06 20:53:17 +00:00
|
|
|
if (m_userName != name) {
|
|
|
|
m_userName = name;
|
|
|
|
m_password = password;
|
|
|
|
emit userNameChanged();
|
|
|
|
}
|
|
|
|
setState(Connected);
|
2024-04-15 11:01:07 +00:00
|
|
|
if (const auto msg = stream->read<qtgrpc::examples::chat::ChatMessages>())
|
Migrate to std::unique_ptr return value for all RPCs
The use of shared pointers has potential risk of storing the
QGrpcOperation children forever and leaking in user code. The problem
is clearly in the lambda connections that we encourage to use in the
docs and examples:
auto stream = testStream(...);
QObject::connect(stream.get(), &QGrpcOperation::finished,
ctx, [ctx, stream]{...});
The above code will hold the 'stream' forever, unless user will make
the explicit disconnect in the lambda.
By using std::unique_ptr we partially solve this, or at least convince
user to solve this. When user creates lambda he knows the 'stream'
lifetime and most probably should consider that after the move, lambda
is owning the QGrpcOperation, so the need of disconnect is more clear
in this case:
auto stream = testStream(...);
auto *streamPtr = stream.get();
QObject::connect(streamPtr, &QGrpcOperation::finished,
ctx, [ctx, stream = std::move(stream)]{...});
The code becomes a bit more complicated, but it points explicitly to
the potential risk. Also it disallows to make this trick to multiple
lambdas at the same time.
Of course using the lambda context to control the QGrpcOperation
lifetime in this case is not necessary. But even if users will decide
to manage the QGrpcOperation lifetime differently, the use of
std::unique_ptr will clearly point to the ownership.
[ChangeLog][Grpc] All generated RPC methods now return std::unique_ptr
instead of std::shared_ptr. This change explicitly defines that caller
takes the ownership of the returned pointers.
Pick-to: 6.8
Change-Id: I271b91454f0c1b12b77127a7e025fa493367e279
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2024-08-28 15:15:33 +00:00
|
|
|
m_messages.append(msg->messages());
|
2023-08-06 20:53:17 +00:00
|
|
|
});
|
2023-01-20 16:43:29 +00:00
|
|
|
// ![1]
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleChatEngine::sendMessage(const QString &content)
|
|
|
|
{
|
|
|
|
// ![2]
|
|
|
|
qtgrpc::examples::chat::ChatMessage msg;
|
|
|
|
msg.setContent(content.toUtf8());
|
2023-08-05 15:10:53 +00:00
|
|
|
msg.setType(qtgrpc::examples::chat::ChatMessage::ContentType::Text);
|
2023-01-20 16:43:29 +00:00
|
|
|
msg.setTimestamp(QDateTime::currentMSecsSinceEpoch());
|
|
|
|
msg.setFrom(m_userName);
|
2024-09-12 12:54:42 +00:00
|
|
|
|
|
|
|
std::unique_ptr<QGrpcCallReply> reply = m_client->sendMessage(msg);
|
|
|
|
// We explicitly take a copy of the reply pointer, since moving it into
|
|
|
|
// the lambda would make the get() function invalid.
|
|
|
|
// Reply's lifetime will be extended until finished() is emitted.
|
|
|
|
// Qt::SingleShotConnection is needed to destroy the lambda (and its capture).
|
|
|
|
auto *replyPtr = reply.get();
|
|
|
|
connect(
|
|
|
|
replyPtr, &QGrpcCallReply::finished, this,
|
|
|
|
[reply = std::move(reply)](const QGrpcStatus &status) {
|
|
|
|
if (!status.isOk())
|
|
|
|
qDebug() << "Failed to send message: " << status;
|
|
|
|
},
|
|
|
|
Qt::SingleShotConnection);
|
2023-01-20 16:43:29 +00:00
|
|
|
// ![2]
|
|
|
|
}
|
|
|
|
|
|
|
|
SimpleChatEngine::ContentType SimpleChatEngine::clipBoardContentType() const
|
|
|
|
{
|
|
|
|
if (m_clipBoard != nullptr) {
|
|
|
|
const QMimeData *mime = m_clipBoard->mimeData();
|
|
|
|
if (mime != nullptr) {
|
|
|
|
if (mime->hasImage() || mime->hasUrls())
|
|
|
|
return SimpleChatEngine::ContentType::Image;
|
|
|
|
else if (mime->hasText())
|
|
|
|
return SimpleChatEngine::ContentType::Text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return SimpleChatEngine::ContentType::Unknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleChatEngine::sendImageFromClipboard()
|
|
|
|
{
|
|
|
|
if (m_clipBoard == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QByteArray imgData;
|
|
|
|
const QMimeData *mime = m_clipBoard->mimeData();
|
|
|
|
if (mime != nullptr) {
|
|
|
|
if (mime->hasImage()) {
|
|
|
|
QImage img = mime->imageData().value<QImage>();
|
|
|
|
img = img.scaled(300, 300, Qt::KeepAspectRatio);
|
|
|
|
QBuffer buffer(&imgData);
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
img.save(&buffer, "PNG");
|
|
|
|
buffer.close();
|
|
|
|
} else if (mime->hasUrls()) {
|
|
|
|
QUrl imgUrl = mime->urls().at(0);
|
|
|
|
if (!imgUrl.isLocalFile()) {
|
|
|
|
qWarning() << "Only supports transfer of local images";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QImage img(imgUrl.toLocalFile());
|
|
|
|
if (img.isNull()) {
|
|
|
|
qWarning() << "Invalid image format";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QBuffer buffer(&imgData);
|
|
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
img.save(&buffer, "PNG");
|
|
|
|
buffer.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (imgData.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
qtgrpc::examples::chat::ChatMessage msg;
|
|
|
|
msg.setContent(imgData);
|
2023-08-05 15:10:53 +00:00
|
|
|
msg.setType(qtgrpc::examples::chat::ChatMessage::ContentType::Image);
|
2023-01-20 16:43:29 +00:00
|
|
|
msg.setTimestamp(QDateTime::currentMSecsSinceEpoch());
|
|
|
|
msg.setFrom(m_userName);
|
2024-09-12 12:54:42 +00:00
|
|
|
|
|
|
|
std::unique_ptr<QGrpcCallReply> reply = m_client->sendMessage(msg);
|
|
|
|
auto *replyPtr = reply.get();
|
|
|
|
connect(
|
|
|
|
replyPtr, &QGrpcCallReply::finished, this,
|
|
|
|
[reply = std::move(reply)](const QGrpcStatus &status) {
|
|
|
|
if (!status.isOk())
|
|
|
|
qDebug() << "Failed to send clipboard message: " << status;
|
|
|
|
},
|
|
|
|
Qt::SingleShotConnection);
|
2023-01-20 16:43:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ChatMessageModel *SimpleChatEngine::messages()
|
|
|
|
{
|
|
|
|
return &m_messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString SimpleChatEngine::userName() const
|
|
|
|
{
|
|
|
|
return m_userName;
|
|
|
|
}
|
|
|
|
|
|
|
|
SimpleChatEngine::State SimpleChatEngine::state() const
|
|
|
|
{
|
|
|
|
return m_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleChatEngine::setState(SimpleChatEngine::State state)
|
|
|
|
{
|
|
|
|
if (m_state != state) {
|
|
|
|
m_state = state;
|
|
|
|
emit stateChanged();
|
|
|
|
}
|
|
|
|
}
|