Implement file uploads

fixes #24
This commit is contained in:
Konstantinos Sideris 2017-11-29 23:39:35 +02:00
parent b21942a3e3
commit fdb76bb5c1
13 changed files with 210 additions and 101 deletions

View file

@ -80,6 +80,7 @@ private slots:
private: private:
QString calculateFileSize(int nbytes) const; QString calculateFileSize(int nbytes) const;
void openUrl(); void openUrl();
void init();
QUrl url_; QUrl url_;
QString text_; QString text_;

View file

@ -56,6 +56,7 @@ public:
void downloadFile(const QString &event_id, const QUrl &url); void downloadFile(const QString &event_id, const QUrl &url);
void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept; void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept;
void uploadImage(const QString &roomid, const QString &filename); void uploadImage(const QString &roomid, const QString &filename);
void uploadFile(const QString &roomid, const QString &filename);
void joinRoom(const QString &roomIdOrAlias); void joinRoom(const QString &roomIdOrAlias);
void leaveRoom(const QString &roomId); void leaveRoom(const QString &roomId);
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000); void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
@ -92,6 +93,7 @@ signals:
const QString &token); const QString &token);
void versionSuccess(); void versionSuccess();
void imageUploaded(const QString &roomid, const QString &filename, const QString &url); void imageUploaded(const QString &roomid, const QString &filename, const QString &url);
void fileUploaded(const QString &roomid, const QString &filename, const QString &url);
void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); void roomAvatarRetrieved(const QString &roomid, const QPixmap &img);
void userAvatarRetrieved(const QString &userId, const QImage &img); void userAvatarRetrieved(const QString &userId, const QImage &img);

View file

@ -85,6 +85,7 @@ signals:
void sendTextMessage(QString msg); void sendTextMessage(QString msg);
void sendEmoteMessage(QString msg); void sendEmoteMessage(QString msg);
void uploadImage(QString filename); void uploadImage(QString filename);
void uploadFile(QString filename);
void sendJoinRoomRequest(const QString &room); void sendJoinRoomRequest(const QString &room);
void startedTyping(); void startedTyping();

View file

@ -17,12 +17,14 @@
#pragma once #pragma once
#include <QDateTime>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QPainter> #include <QPainter>
#include <QStyle> #include <QStyle>
#include <QStyleOption> #include <QStyleOption>
#include "AvatarProvider.h"
#include "Emote.h" #include "Emote.h"
#include "File.h" #include "File.h"
#include "Image.h" #include "Image.h"
@ -30,6 +32,7 @@
#include "Notice.h" #include "Notice.h"
#include "RoomInfoListItem.h" #include "RoomInfoListItem.h"
#include "Text.h" #include "Text.h"
#include "TimelineViewManager.h"
class ImageItem; class ImageItem;
class FileItem; class FileItem;
@ -61,6 +64,7 @@ public:
QWidget *parent = 0); QWidget *parent = 0);
// m.image // m.image
TimelineItem(ImageItem *item, const QString &userid, bool withSender, QWidget *parent = 0); TimelineItem(ImageItem *item, const QString &userid, bool withSender, QWidget *parent = 0);
TimelineItem(FileItem *item, const QString &userid, bool withSender, QWidget *parent = 0);
TimelineItem(ImageItem *img, TimelineItem(ImageItem *img,
const events::MessageEvent<msgs::Image> &e, const events::MessageEvent<msgs::Image> &e,
@ -83,6 +87,12 @@ protected:
private: private:
void init(); void init();
template<class Widget>
void setupLocalWidgetLayout(Widget *widget,
const QString &userid,
const QString &msgDescription,
bool withSender);
void generateBody(const QString &body); void generateBody(const QString &body);
void generateBody(const QString &userid, const QString &body); void generateBody(const QString &userid, const QString &body);
void generateTimestamp(const QDateTime &time); void generateTimestamp(const QDateTime &time);
@ -110,3 +120,36 @@ private:
QLabel *userName_; QLabel *userName_;
QLabel *body_; QLabel *body_;
}; };
template<class Widget>
void
TimelineItem::setupLocalWidgetLayout(Widget *widget,
const QString &userid,
const QString &msgDescription,
bool withSender)
{
auto displayName = TimelineViewManager::displayName(userid);
auto timestamp = QDateTime::currentDateTime();
descriptionMsg_ = {
"You", userid, QString(" %1").arg(msgDescription), descriptiveTime(timestamp)};
generateTimestamp(timestamp);
auto widgetLayout = new QHBoxLayout();
widgetLayout->setContentsMargins(0, 5, 0, 0);
widgetLayout->addWidget(widget);
widgetLayout->addStretch(1);
if (withSender) {
generateBody(displayName, "");
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
AvatarProvider::resolve(userid, this);
} else {
setupSimpleLayout();
}
mainLayout_->addLayout(widgetLayout);
}

View file

@ -17,26 +17,28 @@
#pragma once #pragma once
#include <QApplication>
#include <QLayout> #include <QLayout>
#include <QList> #include <QList>
#include <QQueue> #include <QQueue>
#include <QScrollArea> #include <QScrollArea>
#include <QSettings>
#include <QStyle> #include <QStyle>
#include <QStyleOption> #include <QStyleOption>
#include "Emote.h" #include "Emote.h"
#include "File.h" #include "File.h"
#include "Image.h" #include "Image.h"
#include "MatrixClient.h"
#include "MessageEvent.h" #include "MessageEvent.h"
#include "Notice.h" #include "Notice.h"
#include "Text.h" #include "Text.h"
#include "TimelineItem.h"
class FloatingButton; class FloatingButton;
class MatrixClient;
class RoomMessages; class RoomMessages;
class ScrollBar; class ScrollBar;
class Timeline; class Timeline;
class TimelineItem;
struct DescInfo; struct DescInfo;
namespace msgs = matrix::events::messages; namespace msgs = matrix::events::messages;
@ -102,6 +104,8 @@ public:
// Add new events at the end of the timeline. // Add new events at the end of the timeline.
int addEvents(const Timeline &timeline); int addEvents(const Timeline &timeline);
void addUserMessage(matrix::events::MessageEventType ty, const QString &msg); void addUserMessage(matrix::events::MessageEventType ty, const QString &msg);
template<class Widget, events::MessageEventType MsgType>
void addUserMessage(const QString &url, const QString &filename); void addUserMessage(const QString &url, const QString &filename);
void updatePendingMessage(int txn_id, QString event_id); void updatePendingMessage(int txn_id, QString event_id);
void scrollDown(); void scrollDown();
@ -193,3 +197,28 @@ private:
QList<PendingMessage> pending_sent_msgs_; QList<PendingMessage> pending_sent_msgs_;
QSharedPointer<MatrixClient> client_; QSharedPointer<MatrixClient> client_;
}; };
template<class Widget, events::MessageEventType MsgType>
void
TimelineView::addUserMessage(const QString &url, const QString &filename)
{
QSettings settings;
auto user_id = settings.value("auth/user_id").toString();
auto with_sender = lastSender_ != user_id;
auto widget = new Widget(client_, url, filename, this);
TimelineItem *view_item = new TimelineItem(widget, user_id, with_sender, scroll_widget_);
scroll_layout_->addWidget(view_item);
lastMessageDirection_ = TimelineDirection::Bottom;
QApplication::processEvents();
lastSender_ = user_id;
int txn_id = client_->incrementTransactionId();
PendingMessage message(MsgType, txn_id, url, filename, "", view_item);
handleNewUserMessage(message);
}

View file

@ -67,6 +67,7 @@ public slots:
void queueTextMessage(const QString &msg); void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg); void queueEmoteMessage(const QString &msg);
void queueImageMessage(const QString &roomid, const QString &filename, const QString &url); void queueImageMessage(const QString &roomid, const QString &filename, const QString &url);
void queueFileMessage(const QString &roomid, const QString &filename, const QString &url);
private slots: private slots:
void messageSent(const QString &eventid, const QString &roomid, int txnid); void messageSent(const QString &eventid, const QString &roomid, int txnid);

View file

@ -187,6 +187,10 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
client_->uploadImage(current_room_, filename); client_->uploadImage(current_room_, filename);
}); });
connect(text_input_, &TextInputWidget::uploadFile, this, [=](QString filename) {
client_->uploadFile(current_room_, filename);
});
connect(client_.data(), &MatrixClient::joinFailed, this, &ChatPage::showNotification); connect(client_.data(), &MatrixClient::joinFailed, this, &ChatPage::showNotification);
connect(client_.data(), connect(client_.data(),
&MatrixClient::imageUploaded, &MatrixClient::imageUploaded,
@ -195,6 +199,13 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
text_input_->hideUploadSpinner(); text_input_->hideUploadSpinner();
view_manager_->queueImageMessage(roomid, filename, url); view_manager_->queueImageMessage(roomid, filename, url);
}); });
connect(client_.data(),
&MatrixClient::fileUploaded,
this,
[=](QString roomid, QString filename, QString url) {
text_input_->hideUploadSpinner();
view_manager_->queueFileMessage(roomid, filename, url);
});
connect(client_.data(), connect(client_.data(),
SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)), SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)),

View file

@ -30,25 +30,16 @@
namespace events = matrix::events; namespace events = matrix::events;
namespace msgs = matrix::events::messages; namespace msgs = matrix::events::messages;
FileItem::FileItem(QSharedPointer<MatrixClient> client, void
const events::MessageEvent<msgs::File> &event, FileItem::init()
QWidget *parent)
: QWidget(parent)
, event_{event}
, client_{client}
{ {
setMouseTracking(true); setMouseTracking(true);
setCursor(Qt::PointingHandCursor); setCursor(Qt::PointingHandCursor);
setAttribute(Qt::WA_Hover, true); setAttribute(Qt::WA_Hover, true);
url_ = event.msgContent().url();
text_ = event.content().body();
readableFileSize_ = calculateFileSize(event.msgContent().info().size);
icon_.addFile(":/icons/icons/ui/arrow-pointing-down.png"); icon_.addFile(":/icons/icons/ui/arrow-pointing-down.png");
QList<QString> url_parts = url_.toString().split("mxc://"); QList<QString> url_parts = url_.toString().split("mxc://");
if (url_parts.size() != 2) { if (url_parts.size() != 2) {
qDebug() << "Invalid format for image" << url_.toString(); qDebug() << "Invalid format for image" << url_.toString();
return; return;
@ -61,6 +52,20 @@ FileItem::FileItem(QSharedPointer<MatrixClient> client,
connect(client_.data(), &MatrixClient::fileDownloaded, this, &FileItem::fileDownloaded); connect(client_.data(), &MatrixClient::fileDownloaded, this, &FileItem::fileDownloaded);
} }
FileItem::FileItem(QSharedPointer<MatrixClient> client,
const events::MessageEvent<msgs::File> &event,
QWidget *parent)
: QWidget(parent)
, url_{event.msgContent().url()}
, text_{event.content().body()}
, event_{event}
, client_{client}
{
readableFileSize_ = calculateFileSize(event.msgContent().info().size);
init();
}
FileItem::FileItem(QSharedPointer<MatrixClient> client, FileItem::FileItem(QSharedPointer<MatrixClient> client,
const QString &url, const QString &url,
const QString &filename, const QString &filename,
@ -70,25 +75,9 @@ FileItem::FileItem(QSharedPointer<MatrixClient> client,
, text_{QFileInfo(filename).fileName()} , text_{QFileInfo(filename).fileName()}
, client_{client} , client_{client}
{ {
setMouseTracking(true); readableFileSize_ = calculateFileSize(QFileInfo(filename).size());
setCursor(Qt::PointingHandCursor);
setAttribute(Qt::WA_Hover, true);
// TODO: calculateFileSize init();
/* readableFileSize_ = calculateFileSize(event.msgContent().info().size); */
QList<QString> url_parts = url_.toString().split("mxc://");
icon_.addFile(":/icons/icons/ui/arrow-pointing-down.png");
if (url_parts.size() != 2) {
qDebug() << "Invalid format for image" << url_.toString();
return;
}
QString media_params = url_parts[1];
url_ = QString("%1/_matrix/media/r0/download/%2")
.arg(client_.data()->getHomeServer().toString(), media_params);
} }
QString QString

View file

@ -21,6 +21,7 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QMimeDatabase>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QPixmap> #include <QPixmap>
@ -287,6 +288,9 @@ MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty,
case matrix::events::MessageEventType::Image: case matrix::events::MessageEventType::Image:
body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}}; body = {{"msgtype", "m.image"}, {"body", msg}, {"url", url}};
break; break;
case matrix::events::MessageEventType::File:
body = {{"msgtype", "m.file"}, {"body", msg}, {"url", url}};
break;
default: default:
qDebug() << "SendRoomMessage: Unknown message type for" << msg; qDebug() << "SendRoomMessage: Unknown message type for" << msg;
return; return;
@ -755,6 +759,62 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename)
}); });
} }
void
MatrixClient::uploadFile(const QString &roomid, 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;
}
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();
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 fileUploaded(roomid, filename, object.value("content_uri").toString());
});
}
void void
MatrixClient::joinRoom(const QString &roomIdOrAlias) MatrixClient::joinRoom(const QString &roomIdOrAlias)
{ {

View file

@ -276,30 +276,27 @@ TextInputWidget::command(QString command, QString args)
void void
TextInputWidget::openFileSelection() TextInputWidget::openFileSelection()
{ {
QStringList supportedFiles; QStringList imageExtensions;
supportedFiles << "jpeg" imageExtensions << "jpeg"
<< "gif" << "gif"
<< "png" << "png"
<< "bmp" << "bmp"
<< "tiff" << "tiff"
<< "webp"; << "webp";
auto fileName = QFileDialog::getOpenFileName( auto fileName =
this, QFileDialog::getOpenFileName(this, tr("Select an file"), "", tr("All Files (*)"));
tr("Select an image"),
"",
tr("Image Files (*.bmp *.gif *.jpg *.jpeg *.png *.tiff *.webp)"));
if (fileName.isEmpty()) if (fileName.isEmpty())
return; return;
auto imageFormat = QString(QImageReader::imageFormat(fileName)); auto format = QString(QImageReader::imageFormat(fileName));
if (!supportedFiles.contains(imageFormat)) {
qDebug() << "Unsupported image format for" << fileName; if (imageExtensions.contains(format))
return; emit uploadImage(fileName);
} else
emit uploadFile(fileName);
emit uploadImage(fileName);
showUploadSpinner(); showUploadSpinner();
} }

View file

@ -15,20 +15,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QDateTime>
#include <QFontDatabase> #include <QFontDatabase>
#include <QRegExp> #include <QRegExp>
#include <QSettings> #include <QSettings>
#include <QTextEdit> #include <QTextEdit>
#include "Avatar.h" #include "Avatar.h"
#include "AvatarProvider.h"
#include "Config.h" #include "Config.h"
#include "FileItem.h" #include "FileItem.h"
#include "ImageItem.h" #include "ImageItem.h"
#include "Sync.h" #include "Sync.h"
#include "TimelineItem.h" #include "TimelineItem.h"
#include "TimelineViewManager.h"
static const QRegExp URL_REGEX("((?:https?|ftp)://\\S+)"); static const QRegExp URL_REGEX("((?:https?|ftp)://\\S+)");
static const QString URL_HTML = "<a href=\"\\1\">\\1</a>"; static const QString URL_HTML = "<a href=\"\\1\">\\1</a>";
@ -119,29 +116,15 @@ TimelineItem::TimelineItem(ImageItem *image,
{ {
init(); init();
auto displayName = TimelineViewManager::displayName(userid); setupLocalWidgetLayout<ImageItem>(image, userid, "sent an image", withSender);
auto timestamp = QDateTime::currentDateTime(); }
descriptionMsg_ = {"You", userid, " sent an image", descriptiveTime(timestamp)}; TimelineItem::TimelineItem(FileItem *file, const QString &userid, bool withSender, QWidget *parent)
: QWidget{parent}
{
init();
generateTimestamp(timestamp); setupLocalWidgetLayout<FileItem>(file, userid, "sent a file", withSender);
auto imageLayout = new QHBoxLayout();
imageLayout->setMargin(0);
imageLayout->addWidget(image);
imageLayout->addStretch(1);
if (withSender) {
generateBody(displayName, "");
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
AvatarProvider::resolve(userid, this);
} else {
setupSimpleLayout();
}
mainLayout_->addLayout(imageLayout);
} }
/* /*
@ -169,7 +152,7 @@ TimelineItem::TimelineItem(ImageItem *image,
generateTimestamp(timestamp); generateTimestamp(timestamp);
auto imageLayout = new QHBoxLayout(); auto imageLayout = new QHBoxLayout();
imageLayout->setMargin(0); imageLayout->setContentsMargins(0, 5, 0, 0);
imageLayout->addWidget(image); imageLayout->addWidget(image);
imageLayout->addStretch(1); imageLayout->addStretch(1);

View file

@ -18,7 +18,6 @@
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include <QFileInfo> #include <QFileInfo>
#include <QSettings>
#include <QTimer> #include <QTimer>
#include "FileItem.h" #include "FileItem.h"
@ -27,7 +26,6 @@
#include "RoomMessages.h" #include "RoomMessages.h"
#include "ScrollBar.h" #include "ScrollBar.h"
#include "Sync.h" #include "Sync.h"
#include "TimelineItem.h"
#include "TimelineView.h" #include "TimelineView.h"
namespace events = matrix::events; namespace events = matrix::events;
@ -569,30 +567,6 @@ TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString
handleNewUserMessage(message); handleNewUserMessage(message);
} }
void
TimelineView::addUserMessage(const QString &url, const QString &filename)
{
QSettings settings;
auto user_id = settings.value("auth/user_id").toString();
auto with_sender = lastSender_ != user_id;
auto image = new ImageItem(client_, url, filename, this);
TimelineItem *view_item = new TimelineItem(image, user_id, with_sender, scroll_widget_);
scroll_layout_->addWidget(view_item);
lastMessageDirection_ = TimelineDirection::Bottom;
QApplication::processEvents();
lastSender_ = user_id;
int txn_id = client_->incrementTransactionId();
PendingMessage message(
matrix::events::MessageEventType::Image, txn_id, url, filename, "", view_item);
handleNewUserMessage(message);
}
void void
TimelineView::handleNewUserMessage(PendingMessage msg) TimelineView::handleNewUserMessage(PendingMessage msg)
{ {
@ -610,6 +584,7 @@ TimelineView::sendNextPendingMessage()
PendingMessage &m = pending_msgs_.head(); PendingMessage &m = pending_msgs_.head();
switch (m.ty) { switch (m.ty) {
case matrix::events::MessageEventType::Image: case matrix::events::MessageEventType::Image:
case matrix::events::MessageEventType::File:
client_->sendRoomMessage( client_->sendRoomMessage(
m.ty, m.txn_id, room_id_, QFileInfo(m.filename).fileName(), m.body); m.ty, m.txn_id, room_id_, QFileInfo(m.filename).fileName(), m.body);
break; break;

View file

@ -22,6 +22,8 @@
#include <QFileInfo> #include <QFileInfo>
#include <QSettings> #include <QSettings>
#include "FileItem.h"
#include "ImageItem.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "Sync.h" #include "Sync.h"
#include "TimelineView.h" #include "TimelineView.h"
@ -92,7 +94,22 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
auto view = views_[roomid]; auto view = views_[roomid];
view->addUserMessage(url, filename); view->addUserMessage<ImageItem, matrix::events::MessageEventType::Image>(url, filename);
}
void
TimelineViewManager::queueFileMessage(const QString &roomid,
const QString &filename,
const QString &url)
{
if (!views_.contains(roomid)) {
qDebug() << "Cannot send m.file message to a non-managed view";
return;
}
auto view = views_[roomid];
view->addUserMessage<FileItem, matrix::events::MessageEventType::File>(url, filename);
} }
void void