diff --git a/Quotient/eventitem.cpp b/Quotient/eventitem.cpp index f182999e..166e663d 100644 --- a/Quotient/eventitem.cpp +++ b/Quotient/eventitem.cpp @@ -10,12 +10,9 @@ using namespace Quotient; void PendingEventItem::setFileUploaded(const FileSourceInfo& uploadedFileData) { - if (auto* rme = getAs()) { - Q_ASSERT(rme->hasFileContent()); - auto fc = rme->fileContent(); - fc->source = uploadedFileData; - rme->setContent(std::move(fc)); - } + if (auto* rme = getAs()) + rme->updateFileSourceInfo(uploadedFileData); + if (auto* rae = getAs()) { rae->editContent([&uploadedFileData](EventContent::FileInfo& fi) { fi.source = uploadedFileData; diff --git a/Quotient/events/eventcontent.cpp b/Quotient/events/eventcontent.cpp index fb6c3012..5fb5a079 100644 --- a/Quotient/events/eventcontent.cpp +++ b/Quotient/events/eventcontent.cpp @@ -103,14 +103,10 @@ QJsonObject Quotient::EventContent::toInfoJson(const ImageInfo& info) return infoJson; } -Thumbnail::Thumbnail(const QJsonObject& infoJson, - const std::optional &efm) - : ImageInfo(QUrl(infoJson["thumbnail_url"_L1].toString()), +Thumbnail::Thumbnail(const QJsonObject& infoJson) + : ImageInfo(fileSourceInfoFromJson(infoJson, { "thumbnail_url"_L1, "thumbnail_file"_L1 }), infoJson["thumbnail_info"_L1].toObject()) -{ - if (efm) - source = *efm; -} +{} void Thumbnail::dumpTo(QJsonObject& infoJson) const { diff --git a/Quotient/events/eventcontent.h b/Quotient/events/eventcontent.h index 6810f622..4bf0f85d 100644 --- a/Quotient/events/eventcontent.h +++ b/Quotient/events/eventcontent.h @@ -16,6 +16,10 @@ class QFileInfo; +namespace Quotient { +constexpr inline auto InfoKey = "info"_L1; +} + namespace Quotient::EventContent { //! \brief Base for all content types that can be stored in RoomMessageEvent //! @@ -138,8 +142,7 @@ QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info); //! `info/thumbnail_info` fields are used. struct QUOTIENT_API Thumbnail : public ImageInfo { using ImageInfo::ImageInfo; - explicit Thumbnail(const QJsonObject& infoJson, - const std::optional& efm = {}); + explicit Thumbnail(const QJsonObject& infoJson); //! \brief Add thumbnail information to the passed `info` JSON object void dumpTo(QJsonObject& infoJson) const; @@ -148,8 +151,27 @@ struct QUOTIENT_API Thumbnail : public ImageInfo { //! The base for all file-based content classes class QUOTIENT_API FileContentBase : public Base { public: - using Base::Base; + FileContentBase(const QJsonObject& contentJson = {}) + : Base(contentJson), thumbnail(contentJson[InfoKey].toObject()) + {} virtual QUrl url() const = 0; + virtual FileInfo commonInfo() const = 0; + + Thumbnail thumbnail; + +protected: + virtual QJsonObject makeInfoJson() const = 0; + + void fillJson(QJsonObject& json) const override + { + const auto fileInfo = commonInfo(); + Quotient::fillJson(json, { "url"_L1, "file"_L1 }, fileInfo.source); + addParam(json, "filename"_L1, fileInfo.originalName); + auto infoJson = makeInfoJson(); + if (thumbnail.isValid()) + thumbnail.dumpTo(infoJson); + json.insert(InfoKey, infoJson); + } }; //! \brief Rich text content for m.text, m.emote, m.notice @@ -209,13 +231,9 @@ class UrlBasedContent : public FileContentBase, public InfoT { using InfoT::InfoT; explicit UrlBasedContent(const QJsonObject& json) : FileContentBase(json) - , InfoT(fromJson(json["url"_L1]), json["info"_L1].toObject(), + , InfoT(fileSourceInfoFromJson(json, { "url"_L1, "file"_L1 }), json[InfoKey].toObject(), json["filename"_L1].toString()) - , thumbnail(FileInfo::originalInfoJson) { - if (const auto efmJson = json.value("file"_L1).toObject(); - !efmJson.isEmpty()) - InfoT::source = fromJson(efmJson); // Two small hacks on originalJson to expose mediaIds to QML originalJson.insert("mediaId"_L1, InfoT::mediaId()); originalJson.insert("thumbnailMediaId"_L1, thumbnail.mediaId()); @@ -223,25 +241,10 @@ class UrlBasedContent : public FileContentBase, public InfoT { QMimeType type() const override { return InfoT::mimeType; } QUrl url() const override { return InfoT::url(); } - -public: - Thumbnail thumbnail; + FileInfo commonInfo() const override { return SLICE(*this, FileInfo); } protected: - virtual void fillInfoJson(QJsonObject& infoJson [[maybe_unused]]) const - {} - - void fillJson(QJsonObject& json) const override - { - Quotient::fillJson(json, { "url"_L1, "file"_L1 }, InfoT::source); - if (!InfoT::originalName.isEmpty()) - json.insert("filename"_L1, InfoT::originalName); - auto infoJson = toInfoJson(*this); - if (thumbnail.isValid()) - thumbnail.dumpTo(infoJson); - fillInfoJson(infoJson); - json.insert("info"_L1, infoJson); - } + QJsonObject makeInfoJson() const override { return toInfoJson(*this); } }; //! \brief Content class for m.image @@ -288,9 +291,11 @@ class PlayableContent : public UrlBasedContent { {} protected: - void fillInfoJson(QJsonObject& infoJson) const override + QJsonObject makeInfoJson() const override { + auto infoJson = UrlBasedContent::makeInfoJson(); infoJson.insert("duration"_L1, duration); + return infoJson; } public: diff --git a/Quotient/events/filesourceinfo.cpp b/Quotient/events/filesourceinfo.cpp index 205a8a54..3b10af51 100644 --- a/Quotient/events/filesourceinfo.cpp +++ b/Quotient/events/filesourceinfo.cpp @@ -117,18 +117,19 @@ QUrl Quotient::getUrlFromSourceInfo(const FileSourceInfo& fsi) { return getUrl(f void Quotient::setUrlInSourceInfo(FileSourceInfo& fsi, const QUrl& newUrl) { getUrl(fsi) = newUrl; } -void Quotient::fillJson(QJsonObject& jo, - const std::array& jsonKeys, +void Quotient::fillJson(QJsonObject& jo, const FileSourceInfoKeys& jsonKeys, const FileSourceInfo& fsi) { - // NB: Keeping variant_size_v out of the function signature for readability. - // NB2: Can't use jsonKeys directly inside static_assert as its value is - // unknown so the compiler cannot ensure size() is constexpr (go figure...) - static_assert( - std::variant_size_v == decltype(jsonKeys) {}.size()); jo.insert(jsonKeys[fsi.index()], toJson(fsi)); } +FileSourceInfo Quotient::fileSourceInfoFromJson(const QJsonObject& jo, + const FileSourceInfoKeys& jsonKeys) +{ + return jo.contains(jsonKeys[1]) ? fromJson(jo[jsonKeys[1]]) + : FileSourceInfo(fromJson(jo[jsonKeys[0]])); +} + namespace { // A map from roomId/eventId pair to file source info QHash, EncryptedFileMetadata> infos; diff --git a/Quotient/events/filesourceinfo.h b/Quotient/events/filesourceinfo.h index 4f31619c..fb78ad57 100644 --- a/Quotient/events/filesourceinfo.h +++ b/Quotient/events/filesourceinfo.h @@ -69,6 +69,8 @@ struct QUOTIENT_API JsonObjectConverter { using FileSourceInfo = std::variant; +using FileSourceInfoKeys = std::array>; + QUOTIENT_API QUrl getUrlFromSourceInfo(const FileSourceInfo& fsi); QUOTIENT_API void setUrlInSourceInfo(FileSourceInfo& fsi, const QUrl& newUrl); @@ -86,10 +88,12 @@ void fillJson(QJsonObject&, const FileSourceInfo&) = delete; //! - a key-to-object mapping where key is taken from jsonKeys[1] and the object //! is the result of converting EncryptedFileMetadata to JSON, //! if FileSourceInfo stores EncryptedFileMetadata -QUOTIENT_API void fillJson(QJsonObject& jo, - const std::array& jsonKeys, +QUOTIENT_API void fillJson(QJsonObject& jo, const FileSourceInfoKeys& jsonKeys, const FileSourceInfo& fsi); +QUOTIENT_API FileSourceInfo fileSourceInfoFromJson(const QJsonObject& jo, + const FileSourceInfoKeys& jsonKeys); + namespace FileMetadataMap { QUOTIENT_API void add(const QString& roomId, const QString& eventId, diff --git a/Quotient/events/roommessageevent.cpp b/Quotient/events/roommessageevent.cpp index 26e4e656..e2ae7c34 100644 --- a/Quotient/events/roommessageevent.cpp +++ b/Quotient/events/roommessageevent.cpp @@ -23,9 +23,14 @@ constexpr auto RelatesToKey = "m.relates_to"_L1; constexpr auto MsgTypeKey = "msgtype"_L1; constexpr auto FormattedBodyKey = "formatted_body"_L1; constexpr auto FormatKey = "format"_L1; -constexpr auto TextTypeKey = "m.text"_L1; -constexpr auto EmoteTypeKey = "m.emote"_L1; -constexpr auto NoticeTypeKey = "m.notice"_L1; +constexpr auto TextTypeId = "m.text"_L1; +constexpr auto EmoteTypeId = "m.emote"_L1; +constexpr auto NoticeTypeId = "m.notice"_L1; +constexpr auto FileTypeId = "m.file"_L1; +constexpr auto ImageTypeId = "m.image"_L1; +constexpr auto AudioTypeId = "m.audio"_L1; +constexpr auto VideoTypeId = "m.video"_L1; +constexpr auto LocationTypeId = "m.location"_L1; constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_L1; template @@ -50,14 +55,14 @@ struct MsgTypeDesc { }; constexpr auto msgTypes = std::to_array({ - { TextTypeKey, MsgType::Text, false, make }, - { EmoteTypeKey, MsgType::Emote, false, make }, - { NoticeTypeKey, MsgType::Notice, false, make }, - { "m.image"_L1, MsgType::Image, true, make }, - { "m.file"_L1, MsgType::File, true, make }, - { "m.location"_L1, MsgType::Location, false, make }, - { "m.video"_L1, MsgType::Video, true, make }, - { "m.audio"_L1, MsgType::Audio, true, make }, + { TextTypeId, MsgType::Text, false, make }, + { EmoteTypeId, MsgType::Emote, false, make }, + { NoticeTypeId, MsgType::Notice, false, make }, + { ImageTypeId, MsgType::Image, true, make }, + { FileTypeId, MsgType::File, true, make }, + { LocationTypeId, MsgType::Location, false, make }, + { VideoTypeId, MsgType::Video, true, make }, + { AudioTypeId, MsgType::Audio, true, make }, { "m.key.verification.request"_L1, MsgType::Text, false, make }, }); @@ -194,45 +199,58 @@ void RoomMessageEvent::setContent(std::unique_ptr content) assembleContentJson(plainBody(), rawMsgtype(), std::move(content), relatesTo()); } -bool RoomMessageEvent::hasTextContent() const +template <> +bool RoomMessageEvent::has() const { - return !content() - || (msgtype() == MsgType::Text || msgtype() == MsgType::Emote - || msgtype() == MsgType::Notice); + const auto t = msgtype(); + return (t == MsgType::Text || t == MsgType::Emote || t == MsgType::Notice) + && make(contentJson()) != nullptr; } -std::unique_ptr RoomMessageEvent::richTextContent() const +template <> +bool RoomMessageEvent::has() const { - if (!hasTextContent() || !content()) { - return {}; - } + return jsonToMsgTypeDesc(rawMsgtype()).fileBased; +} - return std::make_unique(contentJson()); +template <> +bool RoomMessageEvent::has() const +{ + return rawMsgtype() == FileTypeId; } -bool RoomMessageEvent::hasFileContent() const +template <> +bool RoomMessageEvent::has() const { - return jsonToMsgTypeDesc(rawMsgtype()).fileBased; + return rawMsgtype() == ImageTypeId; } -std::unique_ptr RoomMessageEvent::fileContent() const +template <> +bool RoomMessageEvent::has() const +{ + return rawMsgtype() == AudioTypeId; +} + +template <> +bool RoomMessageEvent::has() const { - return hasFileContent() ? std::make_unique(contentJson()) : nullptr; + return rawMsgtype() == VideoTypeId; } bool RoomMessageEvent::hasThumbnail() const { - return contentJson().contains("thumbnail_url"_L1); + return fromJson(contentJson()[InfoKey]["thumbnail_url"_L1]).isValid(); } -bool RoomMessageEvent::hasLocationContent() const +Thumbnail RoomMessageEvent::getThumbnail() const { - return msgtype() == MsgType::Location; + return contentPart(InfoKey); } -std::unique_ptr RoomMessageEvent::locationContent() const +template <> +bool RoomMessageEvent::has() const { - return hasLocationContent() ? std::make_unique(contentJson()) : nullptr; + return rawMsgtype() == LocationTypeId; } std::optional RoomMessageEvent::relatesTo() const @@ -248,7 +266,7 @@ QString RoomMessageEvent::upstreamEventId() const QString RoomMessageEvent::replacedEvent() const { - if (!content() || !hasTextContent()) + if (!has()) return {}; const auto er = relatesTo(); @@ -315,13 +333,15 @@ QString safeFileName(QString rawName) QString RoomMessageEvent::fileNameToDownload() const { - const auto fileInfo = fileContent(); - if (QUO_ALARM(fileInfo == nullptr)) + const auto fileContent = get(); + if (QUO_ALARM(fileContent == nullptr)) return {}; + const auto fileInfo = fileContent->commonInfo(); + QString fileName; - if (!fileInfo->originalName.isEmpty()) - fileName = QFileInfo(safeFileName(fileInfo->originalName)).fileName(); + if (!fileInfo.originalName.isEmpty()) + fileName = QFileInfo(safeFileName(fileInfo.originalName)).fileName(); else if (QUrl u { plainBody() }; u.isValid()) { qDebug(MAIN) << id() << "has no file name supplied but the event body " @@ -329,26 +349,33 @@ QString RoomMessageEvent::fileNameToDownload() const fileName = u.fileName(); } if (fileName.isEmpty()) - return safeFileName(fileInfo->mediaId()).replace(u'.', u'-') % u'.' - % fileInfo->mimeType.preferredSuffix(); + return safeFileName(fileInfo.mediaId()).replace(u'.', u'-') % u'.' + % fileInfo.mimeType.preferredSuffix(); if (QSysInfo::productType() == "windows"_L1) { - if (const auto& suffixes = fileInfo->mimeType.suffixes(); + if (const auto& suffixes = fileInfo.mimeType.suffixes(); !suffixes.isEmpty() && std::ranges::none_of(suffixes, [&fileName](const QString& s) { return fileName.endsWith(s); })) - return fileName % u'.' % fileInfo->mimeType.preferredSuffix(); + return fileName % u'.' % fileInfo.mimeType.preferredSuffix(); } return fileName; } +void RoomMessageEvent::updateFileSourceInfo(const FileSourceInfo& fsi) +{ + editSubobject(editJson(), ContentKey, [&fsi](QJsonObject& contentJson) { + Quotient::fillJson(contentJson, { "url"_L1, "file"_L1 }, fsi); + }); +} + QString rawMsgTypeForMimeType(const QMimeType& mimeType) { auto name = mimeType.name(); - return name.startsWith("image/"_L1) ? u"m.image"_s - : name.startsWith("video/"_L1) ? u"m.video"_s - : name.startsWith("audio/"_L1) ? u"m.audio"_s - : u"m.file"_s; + return name.startsWith("image/"_L1) ? ImageTypeId + : name.startsWith("video/"_L1) ? VideoTypeId + : name.startsWith("audio/"_L1) ? AudioTypeId + : FileTypeId; } QString RoomMessageEvent::rawMsgTypeForUrl(const QUrl& url) @@ -362,8 +389,7 @@ QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi) } TextContent::TextContent(QString text, const QString& contentType) - : mimeType(QMimeDatabase().mimeTypeForName(contentType)) - , body(std::move(text)) + : mimeType(QMimeDatabase().mimeTypeForName(contentType)), body(std::move(text)) { if (contentType == HtmlContentTypeId) mimeType = QMimeDatabase().mimeTypeForName("text/html"_L1); @@ -401,15 +427,14 @@ void TextContent::fillJson(QJsonObject &json) const } } -LocationContent::LocationContent(const QString& geoUri, - const Thumbnail& thumbnail) +LocationContent::LocationContent(const QString& geoUri, const Thumbnail& thumbnail) : geoUri(geoUri), thumbnail(thumbnail) {} LocationContent::LocationContent(const QJsonObject& json) : Base(json) , geoUri(json["geo_uri"_L1].toString()) - , thumbnail(json["info"_L1].toObject()) + , thumbnail(json[InfoKey].toObject()) {} QMimeType LocationContent::type() const @@ -420,5 +445,5 @@ QMimeType LocationContent::type() const void LocationContent::fillJson(QJsonObject& o) const { o.insert("geo_uri"_L1, geoUri); - o.insert("info"_L1, toInfoJson(thumbnail)); + o.insert(InfoKey, toInfoJson(thumbnail)); } diff --git a/Quotient/events/roommessageevent.h b/Quotient/events/roommessageevent.h index cb4045e6..26d99b97 100644 --- a/Quotient/events/roommessageevent.h +++ b/Quotient/events/roommessageevent.h @@ -62,49 +62,37 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent { //! Update the message JSON with the given content void setContent(std::unique_ptr content); - QMimeType mimeType() const; - - //! \brief Determine whether the message has text content + //! \brief Determine whether the message has content/attachment of a specified type //! - //! \return true, if the message type is one of m.text, m.notice, m.emote, - //! or the message type is unspecified (in which case plainBody() - //! can still be examined); false otherwise - bool hasTextContent() const; + //! \return true, if the message has type and content corresponding to \p ContentT (e.g. + //! `m.file` or `m.audio` for FileContent); false otherwise + template ContentT> + bool has() const { return false; } - //! \brief Get the TextContent object for the event + //! \brief Get the message content and try to cast it to the specified type //! - //! \return A TextContent object if the message has one; std::nullopt otherwise. - std::unique_ptr richTextContent() const; + //! \return A pointer to the object of the requested type if the event has content of this type; + //! nullptr, if the event has no content or the content cannot be cast to this type + template ContentT> + std::unique_ptr get() const + { + return has() + ? std::unique_ptr(static_cast(content().release())) + : nullptr; + } - //! \brief Determine whether the message has a file/attachment - //! - //! \return true, if the message has a data structure corresponding to - //! a file (such as m.file or m.audio); false otherwise - bool hasFileContent() const; - - //! \brief Get the FileContent object for the event - //! - //! \return A FileContent object if the message has one; std::nullopt otherwise. - std::unique_ptr fileContent() const; + QMimeType mimeType() const; //! \brief Determine whether the message has a thumbnail //! //! \return true, if the message has a data structure corresponding to //! a thumbnail (the message type may be one for visual content, - //! such as m.image, or generic binary content, i.e. m.file); + //! such as m.image, or non-visual, i.e. m.file or m.location); //! false otherwise bool hasThumbnail() const; - //! \brief Determine whether the message has a location - //! - //! \return true, if the message has a data structure corresponding to - //! a location; false otherwise - bool hasLocationContent() const; - - //! \brief Get the LocationContent object for the event - //! - //! \return A LocationContent object if the message has one; std::nullopt otherwise. - std::unique_ptr locationContent() const; + //! Retrieve a thumbnail from the message event + EventContent::Thumbnail getThumbnail() const; //! \brief The EventRelation for this event. //! @@ -172,6 +160,8 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent { QString fileNameToDownload() const; + void updateFileSourceInfo(const FileSourceInfo& fsi); + static QString rawMsgTypeForUrl(const QUrl& url); static QString rawMsgTypeForFile(const QFileInfo& fi); @@ -184,5 +174,13 @@ class QUOTIENT_API RoomMessageEvent : public RoomEvent { Q_ENUM(MsgType) }; +template <> QUOTIENT_API bool RoomMessageEvent::has() const; +template <> QUOTIENT_API bool RoomMessageEvent::has() const; +template <> QUOTIENT_API bool RoomMessageEvent::has() const; +template <> QUOTIENT_API bool RoomMessageEvent::has() const; +template <> QUOTIENT_API bool RoomMessageEvent::has() const; +template <> QUOTIENT_API bool RoomMessageEvent::has() const; +template <> QUOTIENT_API bool RoomMessageEvent::has() const; + using MessageEventType = RoomMessageEvent::MsgType; } // namespace Quotient diff --git a/Quotient/room.cpp b/Quotient/room.cpp index 7633037d..83939006 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -1419,22 +1419,24 @@ QUrl Room::makeMediaUrl(const QString& eventId, const QUrl& mxcUrl) const const RoomMessageEvent* Room::Private::getEventWithFile(const QString& eventId) const { - auto evtIt = q->findInTimeline(eventId); - if (evtIt != timeline.rend() && is(**evtIt)) { - auto* event = evtIt->viewAs(); - if (event->hasFileContent()) + if (auto evtIt = q->findInTimeline(eventId); evtIt != historyEdge()) + if (auto* event = evtIt->viewAs(); + event && event->has()) return event; - } + qCWarning(MAIN) << "No files to download in event" << eventId; return nullptr; } QUrl Room::urlToThumbnail(const QString& eventId) const { - if (auto* event = d->getEventWithFile(eventId); event && event->hasThumbnail()) { - const auto thumbnail = event->fileContent()->thumbnail; - return connection()->getUrlForApi(thumbnail.url(), thumbnail.imageSize); - } + if (const auto evtIt = findInTimeline(eventId); evtIt != historyEdge()) + if (const auto* const evt = evtIt->viewAs()) + if (evt->hasThumbnail()) { + const auto thumbnail = evt->getThumbnail(); + return connection()->getUrlForApi(thumbnail.url(), + thumbnail.imageSize); + } qCDebug(MAIN) << "Event" << eventId << "has no thumbnail"; return {}; } @@ -1442,7 +1444,8 @@ QUrl Room::urlToThumbnail(const QString& eventId) const QUrl Room::urlToDownload(const QString& eventId) const { if (const auto* const event = d->getEventWithFile(eventId)) { - if (const auto fileInfo = event->fileContent(); QUO_CHECK(fileInfo != nullptr)) + if (const auto fileInfo = event->get(); + QUO_CHECK(fileInfo != nullptr)) return connection()->getUrlForApi(fileInfo->url()); } return {}; @@ -1738,12 +1741,13 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events, : timeline.emplace_back(std::move(e), ++index); eventsIndex.insert(eId, index); if (usesEncryption) - if (auto* const rme = ti.viewAs()) - if (const auto fileInfo = rme->fileContent()) { - if (const auto* const efm = - std::get_if(&fileInfo->source)) - FileMetadataMap::add(id, eId, *efm); - } + if (const auto* const rme = ti.viewAs()) + if (const auto fileContent = rme->get()) + std::visit(Overloads{ [this, &eId](const EncryptedFileMetadata& efm) { + FileMetadataMap::add(id, eId, efm); + }, + [](QUrl&&) {} }, + fileContent->commonInfo().source); if (auto n = q->checkForNotifications(ti); n.type != Notification::None) notifications.insert(eId, n); @@ -2456,16 +2460,16 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) Q_ASSERT(false); return; } - const auto fileInfo = event->fileContent(); - if (!fileInfo->isValid()) { + const auto fileInfo = event->get()->commonInfo(); + if (!fileInfo.isValid()) { qCWarning(MAIN) << "Event" << eventId << "has an empty or malformed mxc URL; won't download"; return; } - const auto fileUrl = fileInfo->url(); + const auto fileUrl = fileInfo.url(); auto filePath = localFilename.toLocalFile(); if (filePath.isEmpty()) { // Setup default file path - filePath = fileInfo->url().path().mid(1) % u'_' % event->fileNameToDownload(); + filePath = fileUrl.path().mid(1) % u'_' % event->fileNameToDownload(); if (filePath.size() > 200) // If too long, elide in the middle filePath.replace(128, filePath.size() - 192, "---"_L1); @@ -2474,8 +2478,7 @@ void Room::downloadFile(const QString& eventId, const QUrl& localFilename) qDebug(MAIN) << "File path:" << filePath; } DownloadFileJob *job = nullptr; - if (auto* fileMetadata = - std::get_if(&fileInfo->source)) { + if (auto* fileMetadata = std::get_if(&fileInfo.source)) { job = connection()->downloadFile(fileUrl, *fileMetadata, filePath); } else { job = connection()->downloadFile(fileUrl, filePath); diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index 8c1da1c3..ec9735a1 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -517,14 +517,15 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest, targetRoom->whenMessageMerged(txnId).then( this, [this, thisTest, txnId, fileName](const RoomEvent& evt) { clog << "File event " << txnId.toStdString() << " arrived in the timeline" << endl; + using EventContent::FileContent; evt.switchOnType( [&](const RoomMessageEvent& e) { // TODO: check #366 once #368 is implemented FINISH_TEST(!e.id().isEmpty() && evt.transactionId() == txnId - && e.hasFileContent() - && e.fileContent()->originalName == fileName + && e.has() + && e.get()->originalName == fileName && testDownload(targetRoom->connection()->makeMediaUrl( - e.fileContent()->url()))); + e.get()->url()))); }, [this, thisTest](const RoomEvent&) { FAIL_TEST(); }); });