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:
QString calculateFileSize(int nbytes) const;
void openUrl();
void init();
QUrl url_;
QString text_;

View File

@ -56,6 +56,7 @@ public:
void downloadFile(const QString &event_id, const QUrl &url);
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 joinRoom(const QString &roomIdOrAlias);
void leaveRoom(const QString &roomId);
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
@ -92,6 +93,7 @@ signals:
const QString &token);
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 roomAvatarRetrieved(const QString &roomid, const QPixmap &img);
void userAvatarRetrieved(const QString &userId, const QImage &img);

View File

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

View File

@ -17,12 +17,14 @@
#pragma once
#include <QDateTime>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QStyle>
#include <QStyleOption>
#include "AvatarProvider.h"
#include "Emote.h"
#include "File.h"
#include "Image.h"
@ -30,6 +32,7 @@
#include "Notice.h"
#include "RoomInfoListItem.h"
#include "Text.h"
#include "TimelineViewManager.h"
class ImageItem;
class FileItem;
@ -61,6 +64,7 @@ public:
QWidget *parent = 0);
// m.image
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,
const events::MessageEvent<msgs::Image> &e,
@ -83,6 +87,12 @@ protected:
private:
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 &userid, const QString &body);
void generateTimestamp(const QDateTime &time);
@ -110,3 +120,36 @@ private:
QLabel *userName_;
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
#include <QApplication>
#include <QLayout>
#include <QList>
#include <QQueue>
#include <QScrollArea>
#include <QSettings>
#include <QStyle>
#include <QStyleOption>
#include "Emote.h"
#include "File.h"
#include "Image.h"
#include "MatrixClient.h"
#include "MessageEvent.h"
#include "Notice.h"
#include "Text.h"
#include "TimelineItem.h"
class FloatingButton;
class MatrixClient;
class RoomMessages;
class ScrollBar;
class Timeline;
class TimelineItem;
struct DescInfo;
namespace msgs = matrix::events::messages;
@ -102,6 +104,8 @@ public:
// Add new events at the end of the timeline.
int addEvents(const Timeline &timeline);
void addUserMessage(matrix::events::MessageEventType ty, const QString &msg);
template<class Widget, events::MessageEventType MsgType>
void addUserMessage(const QString &url, const QString &filename);
void updatePendingMessage(int txn_id, QString event_id);
void scrollDown();
@ -193,3 +197,28 @@ private:
QList<PendingMessage> pending_sent_msgs_;
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 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);
private slots:
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);
});
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::imageUploaded,
@ -195,6 +199,13 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
text_input_->hideUploadSpinner();
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(),
SIGNAL(roomAvatarRetrieved(const QString &, const QPixmap &)),

View File

@ -30,25 +30,16 @@
namespace events = matrix::events;
namespace msgs = matrix::events::messages;
FileItem::FileItem(QSharedPointer<MatrixClient> client,
const events::MessageEvent<msgs::File> &event,
QWidget *parent)
: QWidget(parent)
, event_{event}
, client_{client}
void
FileItem::init()
{
setMouseTracking(true);
setCursor(Qt::PointingHandCursor);
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");
QList<QString> url_parts = url_.toString().split("mxc://");
if (url_parts.size() != 2) {
qDebug() << "Invalid format for image" << url_.toString();
return;
@ -61,6 +52,20 @@ FileItem::FileItem(QSharedPointer<MatrixClient> client,
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,
const QString &url,
const QString &filename,
@ -70,25 +75,9 @@ FileItem::FileItem(QSharedPointer<MatrixClient> client,
, text_{QFileInfo(filename).fileName()}
, client_{client}
{
setMouseTracking(true);
setCursor(Qt::PointingHandCursor);
setAttribute(Qt::WA_Hover, true);
readableFileSize_ = calculateFileSize(QFileInfo(filename).size());
// TODO: calculateFileSize
/* 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);
init();
}
QString

View File

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

View File

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

View File

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

View File

@ -18,7 +18,6 @@
#include <QApplication>
#include <QDebug>
#include <QFileInfo>
#include <QSettings>
#include <QTimer>
#include "FileItem.h"
@ -27,7 +26,6 @@
#include "RoomMessages.h"
#include "ScrollBar.h"
#include "Sync.h"
#include "TimelineItem.h"
#include "TimelineView.h"
namespace events = matrix::events;
@ -569,30 +567,6 @@ TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString
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
TimelineView::handleNewUserMessage(PendingMessage msg)
{
@ -610,6 +584,7 @@ TimelineView::sendNextPendingMessage()
PendingMessage &m = pending_msgs_.head();
switch (m.ty) {
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);
break;

View File

@ -22,6 +22,8 @@
#include <QFileInfo>
#include <QSettings>
#include "FileItem.h"
#include "ImageItem.h"
#include "MatrixClient.h"
#include "Sync.h"
#include "TimelineView.h"
@ -92,7 +94,22 @@ TimelineViewManager::queueImageMessage(const QString &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