1// SPDX-FileCopyrightText: 2017 Kitsune Ral <kitsune-ral@users.sf.net>
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
6// This file contains generic event content definitions, applicable to room
7// message events as well as other events (e.g., avatars).
8
9#include "filesourceinfo.h"
10#include <Quotient/quotient_export.h>
11
12#include <QtCore/QJsonObject>
13#include <QtCore/QMetaType>
14#include <QtCore/QMimeType>
15#include <QtCore/QSize>
16#include <QtCore/QUrl>
17
18class QFileInfo;
19
20namespace Quotient::EventContent {
21//! \brief Base for all content types that can be stored in RoomMessageEvent
22//!
23//! Each content type class should have a constructor taking
24//! a QJsonObject and override fillJson() with an implementation
25//! that will fill the target QJsonObject with stored values. It is
26//! assumed but not required that a content object can also be created
27//! from plain data.
28class QUOTIENT_API Base {
29public:
30 explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {}
31 virtual ~Base() = default;
32
33 QJsonObject toJson() const;
34
35public:
36 QJsonObject originalJson;
37
38 // You can't assign those classes
39 Base& operator=(const Base&) = delete;
40 Base& operator=(Base&&) = delete;
41
42protected:
43 Base(const Base&) = default;
44 Base(Base&&) noexcept = default;
45
46 virtual void fillJson(QJsonObject&) const = 0;
47};
48
49// The below structures fairly follow CS spec 11.2.1.6. The overall
50// set of attributes for each content types is a superset of the spec
51// but specific aggregation structure is altered. See doc comments to
52// each type for the list of available attributes.
53
54// A quick classes inheritance structure follows (the definitions are
55// spread across eventcontent.h and roommessageevent.h):
56// UrlBasedContent<InfoT> : InfoT + thumbnail data
57// PlayableContent<InfoT> : + duration attribute
58// FileInfo
59// FileContent = UrlBasedContent<FileInfo>
60// AudioContent = PlayableContent<FileInfo>
61// ImageInfo : FileInfo + imageSize attribute
62// ImageContent = UrlBasedContent<ImageInfo>
63// VideoContent = PlayableContent<ImageInfo>
64
65//! \brief Mix-in class representing `info` subobject in content JSON
66//!
67//! This is one of base classes for content types that deal with files or
68//! URLs. It stores the file metadata attributes, such as size, MIME type
69//! etc. found in the `content/info` subobject of event JSON payloads.
70//! Actual content classes derive from this class _and_ TypedBase that
71//! provides a polymorphic interface to access data in the mix-in. FileInfo
72//! (as well as ImageInfo, that adds image size to the metadata) is NOT
73//! polymorphic and is used in a non-polymorphic way to store thumbnail
74//! metadata (in a separate instance), next to the metadata on the file
75//! itself.
76//!
77//! If you need to make a new _content_ (not info) class based on files/URLs
78//! take UrlBasedContent as the example, i.e.:
79//! 1. Double-inherit from this class (or ImageInfo) and TypedBase.
80//! 2. Provide a constructor from QJsonObject that will pass the `info`
81//! subobject (not the whole content JSON) down to FileInfo/ImageInfo.
82//! 3. Override fillJson() to customise the JSON export logic. Make sure
83//! to call toInfoJson() from it to produce the payload for the `info`
84//! subobject in the JSON payload.
85//!
86//! \sa ImageInfo, FileContent, ImageContent, AudioContent, VideoContent,
87//! UrlBasedContent
88struct QUOTIENT_API FileInfo {
89 FileInfo() = default;
90 //! \brief Construct from a QFileInfo object
91 //!
92 //! \param fi a QFileInfo object referring to an existing file
93 explicit FileInfo(const QFileInfo& fi);
94 explicit FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize = -1,
95 const QMimeType& mimeType = {},
96 QString originalFilename = {});
97 //! \brief Construct from a JSON `info` payload
98 //!
99 //! Make sure to pass the `info` subobject of content JSON, not the
100 //! whole JSON content.
101 FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson,
102 QString originalFilename = {});
103
104 bool isValid() const;
105 QUrl url() const;
106
107 //! \brief Extract media id from the URL
108 //!
109 //! This can be used, e.g., to construct a QML-facing image://
110 //! URI as follows:
111 //! \code "image://provider/" + info.mediaId() \endcode
112 QString mediaId() const { return url().authority() + url().path(); }
113
114 FileSourceInfo source;
115 QJsonObject originalInfoJson;
116 QMimeType mimeType;
117 qint64 payloadSize = 0;
118 QString originalName;
119};
120
121QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info);
122
123//! \brief A content info class for image/video content types and thumbnails
124struct QUOTIENT_API ImageInfo : public FileInfo {
125 ImageInfo() = default;
126 explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {});
127 explicit ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize = -1,
128 const QMimeType& type = {}, QSize imageSize = {},
129 const QString& originalFilename = {});
130 ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson,
131 const QString& originalFilename = {});
132
133 QSize imageSize;
134};
135
136QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info);
137
138//! \brief An auxiliary class for an info type that carries a thumbnail
139//!
140//! This class saves/loads a thumbnail to/from `info` subobject of
141//! the JSON representation of event content; namely, `info/thumbnail_url`
142//! (or, in case of an encrypted thumbnail, `info/thumbnail_file`) and
143//! `info/thumbnail_info` fields are used.
144struct QUOTIENT_API Thumbnail : public ImageInfo {
145 using ImageInfo::ImageInfo;
146 explicit Thumbnail(const QJsonObject& infoJson,
147 const Omittable<EncryptedFileMetadata>& efm = none);
148
149 //! \brief Add thumbnail information to the passed `info` JSON object
150 void dumpTo(QJsonObject& infoJson) const;
151};
152
153class QUOTIENT_API TypedBase : public Base {
154public:
155 virtual QMimeType type() const = 0;
156 virtual const FileInfo* fileInfo() const { return nullptr; }
157 virtual FileInfo* fileInfo() { return nullptr; }
158 virtual const Thumbnail* thumbnailInfo() const { return nullptr; }
159
160protected:
161 explicit TypedBase(QJsonObject o = {}) : Base(std::move(o)) {}
162 using Base::Base;
163};
164
165//! \brief A template class for content types with a URL and additional info
166//!
167//! Types that derive from this class template take `url` (or, if the file
168//! is encrypted, `file`) and, optionally, `filename` values from
169//! the top-level JSON object and the rest of information from the `info`
170//! subobject, as defined by the parameter type.
171//! \tparam InfoT base info class - FileInfo or ImageInfo
172template <class InfoT>
173class UrlBasedContent : public TypedBase, public InfoT {
174public:
175 using InfoT::InfoT;
176 explicit UrlBasedContent(const QJsonObject& json)
177 : TypedBase(json)
178 , InfoT(QUrl(json["url"_ls].toString()), json["info"_ls].toObject(),
179 json["filename"_ls].toString())
180 , thumbnail(FileInfo::originalInfoJson)
181 {
182 if (const auto efmJson = json.value(key: "file"_ls).toObject();
183 !efmJson.isEmpty())
184 InfoT::source = fromJson<EncryptedFileMetadata>(json: efmJson);
185 // Two small hacks on originalJson to expose mediaIds to QML
186 originalJson.insert("mediaId"_ls, InfoT::mediaId());
187 originalJson.insert("thumbnailMediaId"_ls, thumbnail.mediaId());
188 }
189
190 QMimeType type() const override { return InfoT::mimeType; }
191 const FileInfo* fileInfo() const override { return this; }
192 FileInfo* fileInfo() override { return this; }
193 const Thumbnail* thumbnailInfo() const override { return &thumbnail; }
194
195public:
196 Thumbnail thumbnail;
197
198protected:
199 virtual void fillInfoJson(QJsonObject& infoJson [[maybe_unused]]) const
200 {}
201
202 void fillJson(QJsonObject& json) const override
203 {
204 Quotient::fillJson(json, { "url"_ls, "file"_ls }, InfoT::source);
205 if (!InfoT::originalName.isEmpty())
206 json.insert("filename"_ls, InfoT::originalName);
207 auto infoJson = toInfoJson(*this);
208 if (thumbnail.isValid())
209 thumbnail.dumpTo(infoJson);
210 fillInfoJson(infoJson);
211 json.insert("info"_ls, infoJson);
212 }
213};
214
215//! \brief Content class for m.image
216//!
217//! Available fields:
218//! - corresponding to the top-level JSON:
219//! - source (corresponding to `url` or `file` in JSON)
220//! - filename (extension to the spec)
221//! - corresponding to the `info` subobject:
222//! - payloadSize (`size` in JSON)
223//! - mimeType (`mimetype` in JSON)
224//! - imageSize (QSize for a combination of `h` and `w` in JSON)
225//! - thumbnail.url (`thumbnail_url` in JSON)
226//! - corresponding to the `info/thumbnail_info` subobject: contents of
227//! thumbnail field, in the same vein as for the main image:
228//! - payloadSize
229//! - mimeType
230//! - imageSize
231using ImageContent = UrlBasedContent<ImageInfo>;
232
233//! \brief Content class for m.file
234//!
235//! Available fields:
236//! - corresponding to the top-level JSON:
237//! - source (corresponding to `url` or `file` in JSON)
238//! - filename
239//! - corresponding to the `info` subobject:
240//! - payloadSize (`size` in JSON)
241//! - mimeType (`mimetype` in JSON)
242//! - thumbnail.source (`thumbnail_url` or `thumbnail_file` in JSON)
243//! - corresponding to the `info/thumbnail_info` subobject:
244//! - thumbnail.payloadSize
245//! - thumbnail.mimeType
246//! - thumbnail.imageSize (QSize for `h` and `w` in JSON)
247using FileContent = UrlBasedContent<FileInfo>;
248} // namespace Quotient::EventContent
249Q_DECLARE_METATYPE(const Quotient::EventContent::TypedBase*)
250