From 5573548fb1adbaef6ef8e30fd76855b90c0fe26c Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Fri, 1 Dec 2017 17:33:49 +0200 Subject: [PATCH] Allow audio clip uploads --- include/MatrixClient.h | 6 ++ include/TextInputWidget.h | 3 + include/events/MessageEvent.h | 2 +- include/events/messages/Audio.h | 2 +- include/events/messages/File.h | 2 +- include/events/messages/Image.h | 2 +- include/events/messages/Video.h | 2 +- include/timeline/TimelineViewManager.h | 1 + resources/styles/nheko-dark.qss | 6 ++ resources/styles/nheko.qss | 6 ++ resources/styles/system.qss | 6 ++ src/ChatPage.cc | 11 +++ src/MatrixClient.cc | 121 +++++++++++++++++-------- src/TextInputWidget.cc | 23 +++-- src/timeline/TimelineView.cc | 12 ++- src/timeline/TimelineViewManager.cc | 16 ++++ src/timeline/widgets/AudioItem.cc | 3 + src/timeline/widgets/FileItem.cc | 3 + 18 files changed, 168 insertions(+), 59 deletions(-) diff --git a/include/MatrixClient.h b/include/MatrixClient.h index b0f6993d..722a8611 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include @@ -43,6 +44,7 @@ public: int txnId, const QString &roomid, const QString &msg, + const QFileInfo &info, const QString &url = "") noexcept; void login(const QString &username, const QString &password) noexcept; void registerUser(const QString &username, @@ -57,6 +59,7 @@ public: void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept; void uploadImage(const QString &roomid, const QString &filename); void uploadFile(const QString &roomid, const QString &filename); + void uploadAudio(const QString &roomid, const QString &filename); void joinRoom(const QString &roomIdOrAlias); void leaveRoom(const QString &roomId); void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000); @@ -94,6 +97,7 @@ signals: void versionSuccess(); void imageUploaded(const QString &roomid, const QString &filename, const QString &url); void fileUploaded(const QString &roomid, const QString &filename, const QString &url); + void audioUploaded(const QString &roomid, const QString &filename, const QString &url); void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); void userAvatarRetrieved(const QString &userId, const QImage &img); @@ -116,6 +120,8 @@ signals: void leftRoom(const QString &room_id); private: + QNetworkReply *makeUploadRequest(const QString &filename); + // Client API prefix. QString clientApiUrl_; diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h index 3c338fe3..b208d3f4 100644 --- a/include/TextInputWidget.h +++ b/include/TextInputWidget.h @@ -85,8 +85,11 @@ private slots: signals: void sendTextMessage(QString msg); void sendEmoteMessage(QString msg); + void uploadImage(QString filename); void uploadFile(QString filename); + void uploadAudio(QString filename); + void sendJoinRoomRequest(const QString &room); void startedTyping(); diff --git a/include/events/MessageEvent.h b/include/events/MessageEvent.h index 3c4f83e9..08cd926f 100644 --- a/include/events/MessageEvent.h +++ b/include/events/MessageEvent.h @@ -55,7 +55,7 @@ struct ThumbnailInfo { int h; int w; - int size; + int size = 0; QString mimetype; }; diff --git a/include/events/messages/Audio.h b/include/events/messages/Audio.h index 8466e0e4..b5666d90 100644 --- a/include/events/messages/Audio.h +++ b/include/events/messages/Audio.h @@ -27,7 +27,7 @@ namespace messages { struct AudioInfo { uint64_t duration; - int size; + int size = 0; QString mimetype; }; diff --git a/include/events/messages/File.h b/include/events/messages/File.h index 2fb2aa83..9064a556 100644 --- a/include/events/messages/File.h +++ b/include/events/messages/File.h @@ -27,7 +27,7 @@ namespace events { namespace messages { struct FileInfo { - int size; + int size = 0; QString mimetype; QString thumbnail_url; diff --git a/include/events/messages/Image.h b/include/events/messages/Image.h index 1e709579..03c7a368 100644 --- a/include/events/messages/Image.h +++ b/include/events/messages/Image.h @@ -29,7 +29,7 @@ struct ImageInfo { int h; int w; - int size; + int size = 0; QString mimetype; QString thumbnail_url; diff --git a/include/events/messages/Video.h b/include/events/messages/Video.h index 79713546..6aeaf4d5 100644 --- a/include/events/messages/Video.h +++ b/include/events/messages/Video.h @@ -29,7 +29,7 @@ struct VideoInfo { int h; int w; - int size; + int size = 0; int duration; QString mimetype; diff --git a/include/timeline/TimelineViewManager.h b/include/timeline/TimelineViewManager.h index 854c2550..edb44ecd 100644 --- a/include/timeline/TimelineViewManager.h +++ b/include/timeline/TimelineViewManager.h @@ -68,6 +68,7 @@ public slots: void queueEmoteMessage(const QString &msg); void queueImageMessage(const QString &roomid, const QString &filename, const QString &url); void queueFileMessage(const QString &roomid, const QString &filename, const QString &url); + void queueAudioMessage(const QString &roomid, const QString &filename, const QString &url); private slots: void messageSent(const QString &eventid, const QString &roomid, int txnid); diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss index 0182d7a3..8610c445 100644 --- a/resources/styles/nheko-dark.qss +++ b/resources/styles/nheko-dark.qss @@ -28,6 +28,12 @@ FileItem { qproperty-iconColor: #caccd1; } +AudioItem { + qproperty-textColor: #caccd1; + qproperty-backgroundColor: #414A59; + qproperty-iconColor: #caccd1; +} + RaisedButton { qproperty-foregroundColor: #caccd1; qproperty-backgroundColor: #333; diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss index 4251b4a5..55f9a4f4 100644 --- a/resources/styles/nheko.qss +++ b/resources/styles/nheko.qss @@ -27,6 +27,12 @@ FileItem { qproperty-iconColor: white; } +AudioItem { + qproperty-textColor: #333; + qproperty-backgroundColor: #f2f2f2; + qproperty-iconColor: white; +} + RaisedButton { qproperty-foregroundColor: white; } diff --git a/resources/styles/system.qss b/resources/styles/system.qss index 4cd1bbfe..bb24b7a6 100644 --- a/resources/styles/system.qss +++ b/resources/styles/system.qss @@ -25,6 +25,12 @@ FileItem { qproperty-iconColor: palette(window); } +AudioItem { + qproperty-textColor: palette(text); + qproperty-backgroundColor: palette(base); + qproperty-iconColor: palette(window); +} + RaisedButton { qproperty-foregroundColor: palette(light); } diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 340e75c3..f773ff2c 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -192,6 +192,10 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) client_->uploadFile(current_room_, filename); }); + connect(text_input_, &TextInputWidget::uploadAudio, this, [=](QString filename) { + client_->uploadAudio(current_room_, filename); + }); + connect(client_.data(), &MatrixClient::joinFailed, this, &ChatPage::showNotification); connect(client_.data(), &MatrixClient::imageUploaded, @@ -207,6 +211,13 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) text_input_->hideUploadSpinner(); view_manager_->queueFileMessage(roomid, filename, url); }); + connect(client_.data(), + &MatrixClient::audioUploaded, + this, + [=](QString roomid, QString filename, QString url) { + text_input_->hideUploadSpinner(); + view_manager_->queueAudioMessage(roomid, filename, url); + }); connect(client_.data(), SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 66630c80..3326a47f 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -265,6 +265,7 @@ MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty, int txnId, const QString &roomid, const QString &msg, + const QFileInfo &fileinfo, const QString &url) noexcept { QUrlQuery query; @@ -276,7 +277,13 @@ MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty, endpoint.setQuery(query); QString msgType(""); + + QMimeDatabase db; + QMimeType mime = + db.mimeTypeForFile(fileinfo.absoluteFilePath(), QMimeDatabase::MatchContent); + QJsonObject body; + QJsonObject info = {{"size", fileinfo.size()}, {"mimetype", mime.name()}}; switch (ty) { case matrix::events::MessageEventType::Text: @@ -286,10 +293,13 @@ MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty, body = {{"msgtype", "m.emote"}, {"body", msg}}; break; case matrix::events::MessageEventType::Image: - body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}}; + body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}, {"info", info}}; break; case matrix::events::MessageEventType::File: - body = {{"msgtype", "m.file"}, {"body", msg}, {"url", url}}; + body = {{"msgtype", "m.file"}, {"body", msg}, {"url", url}, {"info", info}}; + break; + case matrix::events::MessageEventType::Audio: + body = {{"msgtype", "m.audio"}, {"body", msg}, {"url", url}, {"info", info}}; break; default: qDebug() << "SendRoomMessage: Unknown message type for" << msg; @@ -706,26 +716,11 @@ MatrixClient::messages(const QString &roomid, const QString &from_token, int lim void MatrixClient::uploadImage(const QString &roomid, const QString &filename) { - QUrlQuery query; - query.addQueryItem("access_token", token_); + auto reply = makeUploadRequest(filename); - QUrl endpoint(server_); - endpoint.setPath(mediaApiUrl_ + "/upload"); - endpoint.setQuery(query); - - QFile file(filename); - if (!file.open(QIODevice::ReadWrite)) { - qDebug() << "Error while reading" << filename; + if (reply == nullptr) return; - } - auto imgFormat = QString(QImageReader::imageFormat(filename)); - - QNetworkRequest request(QString(endpoint.toEncoded())); - request.setHeader(QNetworkRequest::ContentLengthHeader, file.size()); - request.setHeader(QNetworkRequest::ContentTypeHeader, QString("image/%1").arg(imgFormat)); - - auto reply = post(request, file.readAll()); connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { reply->deleteLater(); @@ -762,27 +757,8 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename) void MatrixClient::uploadFile(const QString &roomid, const QString &filename) { - QUrlQuery query; - query.addQueryItem("access_token", token_); + auto reply = makeUploadRequest(filename); - QUrl endpoint(server_); - endpoint.setPath(mediaApiUrl_ + "/upload"); - endpoint.setQuery(query); - - QFile file(filename); - if (!file.open(QIODevice::ReadWrite)) { - qDebug() << "Error while reading" << filename; - return; - } - - QMimeDatabase db; - QMimeType mime = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent); - - QNetworkRequest request(QString(endpoint.toEncoded())); - request.setHeader(QNetworkRequest::ContentLengthHeader, file.size()); - request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name()); - - auto reply = post(request, file.readAll()); connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { reply->deleteLater(); @@ -815,6 +791,45 @@ MatrixClient::uploadFile(const QString &roomid, const QString &filename) emit fileUploaded(roomid, filename, object.value("content_uri").toString()); }); } + +void +MatrixClient::uploadAudio(const QString &roomid, const QString &filename) +{ + auto reply = makeUploadRequest(filename); + + connect(reply, &QNetworkReply::finished, this, [this, reply, roomid, filename]() { + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status == 0 || status >= 400) { + emit syncFailed(reply->errorString()); + return; + } + + auto data = reply->readAll(); + + if (data.isEmpty()) + return; + + auto json = QJsonDocument::fromJson(data); + + if (!json.isObject()) { + qDebug() << "Media upload: Response is not a json object."; + return; + } + + QJsonObject object = json.object(); + if (!object.contains("content_uri")) { + qDebug() << "Media upload: Missing content_uri key"; + qDebug() << object; + return; + } + + emit audioUploaded(roomid, filename, object.value("content_uri").toString()); + }); +} + void MatrixClient::joinRoom(const QString &roomIdOrAlias) { @@ -961,3 +976,31 @@ MatrixClient::readEvent(const QString &room_id, const QString &event_id) } }); } + +QNetworkReply * +MatrixClient::makeUploadRequest(const QString &filename) +{ + QUrlQuery query; + query.addQueryItem("access_token", token_); + + QUrl endpoint(server_); + endpoint.setPath(mediaApiUrl_ + "/upload"); + endpoint.setQuery(query); + + QFile file(filename); + if (!file.open(QIODevice::ReadWrite)) { + qDebug() << "Error while reading" << filename; + return nullptr; + } + + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(filename, QMimeDatabase::MatchContent); + + QNetworkRequest request(QString(endpoint.toEncoded())); + request.setHeader(QNetworkRequest::ContentLengthHeader, file.size()); + request.setHeader(QNetworkRequest::ContentTypeHeader, mime.name()); + + auto reply = post(request, file.readAll()); + + return reply; +} diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 2f6c435a..dc2bebe7 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include @@ -276,24 +278,21 @@ TextInputWidget::command(QString command, QString args) void TextInputWidget::openFileSelection() { - QStringList imageExtensions; - imageExtensions << "jpeg" - << "gif" - << "png" - << "bmp" - << "tiff" - << "webp"; - - auto fileName = - QFileDialog::getOpenFileName(this, tr("Select an file"), "", tr("All Files (*)")); + const auto fileName = + QFileDialog::getOpenFileName(this, tr("Select a file"), "", tr("All Files (*)")); if (fileName.isEmpty()) return; - auto format = QString(QImageReader::imageFormat(fileName)); + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent); - if (imageExtensions.contains(format)) + const auto format = mime.name().split("/")[0]; + + if (format == "image") emit uploadImage(fileName); + else if (format == "audio") + emit uploadAudio(fileName); else emit uploadFile(fileName); diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc index e5fd7f88..af7c0f7f 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc @@ -436,13 +436,19 @@ TimelineView::sendNextPendingMessage() PendingMessage &m = pending_msgs_.head(); switch (m.ty) { + case matrix::events::MessageEventType::Audio: case matrix::events::MessageEventType::Image: case matrix::events::MessageEventType::File: - client_->sendRoomMessage( - m.ty, m.txn_id, room_id_, QFileInfo(m.filename).fileName(), m.body); + // FIXME: Improve the API + client_->sendRoomMessage(m.ty, + m.txn_id, + room_id_, + QFileInfo(m.filename).fileName(), + QFileInfo(m.filename), + m.body); break; default: - client_->sendRoomMessage(m.ty, m.txn_id, room_id_, m.body); + client_->sendRoomMessage(m.ty, m.txn_id, room_id_, m.body, QFileInfo()); break; } } diff --git a/src/timeline/TimelineViewManager.cc b/src/timeline/TimelineViewManager.cc index 39f07639..281cafcd 100644 --- a/src/timeline/TimelineViewManager.cc +++ b/src/timeline/TimelineViewManager.cc @@ -27,6 +27,7 @@ #include "timeline/TimelineView.h" #include "timeline/TimelineViewManager.h" +#include "timeline/widgets/AudioItem.h" #include "timeline/widgets/FileItem.h" #include "timeline/widgets/ImageItem.h" @@ -113,6 +114,21 @@ TimelineViewManager::queueFileMessage(const QString &roomid, view->addUserMessage(url, filename); } +void +TimelineViewManager::queueAudioMessage(const QString &roomid, + const QString &filename, + const QString &url) +{ + if (!views_.contains(roomid)) { + qDebug() << "Cannot send m.audio message to a non-managed view"; + return; + } + + auto view = views_[roomid]; + + view->addUserMessage(url, filename); +} + void TimelineViewManager::clearAll() { diff --git a/src/timeline/widgets/AudioItem.cc b/src/timeline/widgets/AudioItem.cc index 7c4b2d48..2a417b3e 100644 --- a/src/timeline/widgets/AudioItem.cc +++ b/src/timeline/widgets/AudioItem.cc @@ -107,6 +107,9 @@ AudioItem::AudioItem(QSharedPointer client, QString AudioItem::calculateFileSize(int nbytes) const { + if (nbytes == 0) + return QString(""); + if (nbytes < 1024) return QString("%1 B").arg(nbytes); diff --git a/src/timeline/widgets/FileItem.cc b/src/timeline/widgets/FileItem.cc index e70be9da..e4cc02b2 100644 --- a/src/timeline/widgets/FileItem.cc +++ b/src/timeline/widgets/FileItem.cc @@ -94,6 +94,9 @@ FileItem::FileItem(QSharedPointer client, QString FileItem::calculateFileSize(int nbytes) const { + if (nbytes == 0) + return QString(""); + if (nbytes < 1024) return QString("%1 B").arg(nbytes);