libQuotient
A Qt library for building matrix clients
eventcontent.h
Go to the documentation of this file.
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 
11 #include <QtCore/QJsonObject>
12 #include <QtCore/QMetaType>
13 #include <QtCore/QMimeType>
14 #include <QtCore/QSize>
15 #include <QtCore/QUrl>
16 
17 class QFileInfo;
18 
19 namespace Quotient {
20 constexpr inline auto BodyKey = "body"_L1;
21 constexpr inline auto FormatKey = "format"_L1;
22 constexpr inline auto FormattedBodyKey = "formatted_body"_L1;
23 constexpr inline auto HtmlContentTypeId = "org.matrix.custom.html"_L1;
24 constexpr inline auto InfoKey = "info"_L1;
25 }
26 
27 namespace Quotient::EventContent {
28 //! \brief Base for all content types that can be stored in RoomMessageEvent
29 //!
30 //! Each content type class should have a constructor taking
31 //! a QJsonObject and override fillJson() with an implementation
32 //! that will fill the target QJsonObject with stored values. It is
33 //! assumed but not required that a content object can also be created
34 //! from plain data.
35 class QUOTIENT_API Base {
36 public:
37  explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {}
38  virtual ~Base() = default;
39 
40  QJsonObject toJson() const;
41 
42  virtual QMimeType type() const = 0;
43 
44 public:
45  QJsonObject originalJson;
46 
47  // You can't assign those classes
48  Base& operator=(const Base&) = delete;
49  Base& operator=(Base&&) = delete;
50 
51 protected:
52  Base(const Base&) = default;
53  Base(Base&&) noexcept = default;
54 
55  virtual void fillJson(QJsonObject&) const = 0;
56 };
57 
58 // The below structures fairly follow CS spec 11.2.1.6. The overall
59 // set of attributes for each content types is a superset of the spec
60 // but specific aggregation structure is altered. See doc comments to
61 // each type for the list of available attributes.
62 
63 // A quick class inheritance structure follows:
64 // UrlBasedContent<InfoT> : InfoT + thumbnail data
65 // PlayableContent<InfoT> : + duration attribute
66 // FileInfo
67 // FileContent = UrlBasedContent<FileInfo>
68 // AudioContent = PlayableContent<FileInfo>
69 // ImageInfo : FileInfo + imageSize attribute
70 // ImageContent = UrlBasedContent<ImageInfo>
71 // VideoContent = PlayableContent<ImageInfo>
72 
73 //! \brief Mix-in class representing `info` subobject in content JSON
74 //!
75 //! This is one of base classes for content types that deal with files or URLs. It stores
76 //! file metadata attributes, such as size, MIME type etc. found in the `content/info` subobject of
77 //! event JSON payloads. Actual content classes derive from this class _and_ Base that provides
78 //! a polymorphic interface to access data in the mix-in. FileInfo (as well as ImageInfo, that adds
79 //! image size to the metadata) is NOT polymorphic and is used in a non-polymorphic way to store
80 //! thumbnail metadata (in a separate instance), next to the metadata on the file itself.
81 //!
82 //! If you need to make a new _content_ (not info) class based on files/URLs take UrlBasedContent
83 //! as the example, i.e.:
84 //! 1. Double-inherit from this class (or ImageInfo) and Base.
85 //! 2. Provide a constructor from QJsonObject that will pass the `info` subobject (not the whole
86 //! content JSON) down to FileInfo/ImageInfo.
87 //! 3. Override fillJson() to customise the JSON export logic. Make sure to call toInfoJson()
88 //! from it to produce the payload for the `info` subobject in the JSON payload.
89 //!
90 //! \sa ImageInfo, FileContent, ImageContent, AudioContent, VideoContent, UrlBasedContent
91 struct QUOTIENT_API FileInfo {
92  FileInfo() = default;
93  //! \brief Construct from a QFileInfo object
94  //!
95  //! \param fi a QFileInfo object referring to an existing file
96  explicit FileInfo(const QFileInfo& fi);
97  explicit FileInfo(FileSourceInfo sourceInfo, qint64 payloadSize = -1,
98  const QMimeType& mimeType = {},
99  QString originalFilename = {});
100  //! \brief Construct from a JSON `info` payload
101  //!
102  //! Make sure to pass the `info` subobject of content JSON, not the
103  //! whole JSON content.
104  FileInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson,
105  QString originalFilename = {});
106 
107  bool isValid() const;
108  QUrl url() const;
109 
110  //! \brief Extract media id from the URL
111  //!
112  //! This can be used, e.g., to construct a QML-facing image://
113  //! URI as follows:
114  //! \code "image://provider/" + info.mediaId() \endcode
115  QString mediaId() const { return url().authority() + url().path(); }
116 
117  FileSourceInfo source;
118  QJsonObject originalInfoJson;
119  QMimeType mimeType;
120  qint64 payloadSize = 0;
121  QString originalName;
122 };
123 
124 QUOTIENT_API QJsonObject toInfoJson(const FileInfo& info);
125 
126 //! \brief A content info class for image/video content types and thumbnails
127 struct QUOTIENT_API ImageInfo : public FileInfo {
128  ImageInfo() = default;
129  explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {});
130  explicit ImageInfo(FileSourceInfo sourceInfo, qint64 fileSize = -1,
131  const QMimeType& type = {}, QSize imageSize = {},
132  const QString& originalFilename = {});
133  ImageInfo(FileSourceInfo sourceInfo, const QJsonObject& infoJson,
134  const QString& originalFilename = {});
135 
136  QSize imageSize;
137 };
138 
139 QUOTIENT_API QJsonObject toInfoJson(const ImageInfo& info);
140 
141 //! \brief An auxiliary class for an info type that carries a thumbnail
142 //!
143 //! This class saves/loads a thumbnail to/from `info` subobject of
144 //! the JSON representation of event content; namely, `info/thumbnail_url`
145 //! (or, in case of an encrypted thumbnail, `info/thumbnail_file`) and
146 //! `info/thumbnail_info` fields are used.
147 struct QUOTIENT_API Thumbnail : public ImageInfo {
148  using ImageInfo::ImageInfo;
149  explicit Thumbnail(const QJsonObject& infoJson);
150 
151  //! \brief Add thumbnail information to the passed `info` JSON object
152  void dumpTo(QJsonObject& infoJson) const;
153 };
154 
155 //! The base for all file-based content classes
156 class QUOTIENT_API FileContentBase : public Base {
157 public:
158  explicit FileContentBase(const QJsonObject& contentJson = {})
159  : Base(contentJson), thumbnail(contentJson[InfoKey].toObject())
160  {}
161  virtual QUrl url() const = 0;
162  virtual FileInfo commonInfo() const = 0;
163 
164  Thumbnail thumbnail;
165 
166 protected:
167  virtual QJsonObject makeInfoJson() const = 0;
168 
169  void fillJson(QJsonObject& json) const override
170  {
171  const auto fileInfo = commonInfo();
172  Quotient::fillJson(json, { "url"_L1, "file"_L1 }, fileInfo.source);
173  addParam<IfNotEmpty>(json, "filename"_L1, fileInfo.originalName);
174  auto infoJson = makeInfoJson();
175  if (thumbnail.isValid())
176  thumbnail.dumpTo(infoJson);
177  json.insert(InfoKey, infoJson);
178  }
179 };
180 
181 //! \brief Rich text content for m.text, m.emote, m.notice
182 //!
183 //! Available fields: mimeType, body. The body can be either rich text
184 //! or plain text, depending on what mimeType specifies.
185 class QUOTIENT_API TextContent : public Base {
186 public:
187  TextContent(QString text, const QString& contentType);
188  explicit TextContent(const QJsonObject& json);
189 
190  QMimeType type() const override { return mimeType; }
191 
192  QMimeType mimeType;
193  QString body;
194 
195 protected:
196  void fillJson(QJsonObject& json) const override;
197 };
198 
199 //! \brief Content class for m.location
200 //!
201 //! Available fields:
202 //! - corresponding to the top-level JSON:
203 //! - geoUri ("geo_uri" in JSON)
204 //! - corresponding to the "info" subobject:
205 //! - thumbnail.url ("thumbnail_url" in JSON)
206 //! - corresponding to the "info/thumbnail_info" subobject:
207 //! - thumbnail.payloadSize
208 //! - thumbnail.mimeType
209 //! - thumbnail.imageSize
210 class QUOTIENT_API LocationContent : public Base {
211 public:
212  LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {});
213  explicit LocationContent(const QJsonObject& json);
214 
215  QMimeType type() const override;
216 
217 public:
218  QString geoUri;
219  Thumbnail thumbnail;
220 
221 protected:
222  void fillJson(QJsonObject& o) const override;
223 };
224 
225 //! \brief A template class for content types with a URL and additional info
226 //!
227 //! Types that derive from this class template take `url` (or, if the file
228 //! is encrypted, `file`) and, optionally, `filename` values from
229 //! the top-level JSON object and the rest of information from the `info`
230 //! subobject, as defined by the parameter type.
231 //! \tparam InfoT base info class - FileInfo or ImageInfo
232 template <std::derived_from<FileInfo> InfoT>
233 class UrlBasedContent : public FileContentBase, public InfoT {
234 public:
235  using InfoT::InfoT;
236  explicit UrlBasedContent(const QJsonObject& json)
237  : FileContentBase(json)
238  , InfoT(fileSourceInfoFromJson(json, { "url"_L1, "file"_L1 }), json[InfoKey].toObject(),
239  json["filename"_L1].toString())
240  {
241  // Two small hacks on originalJson to expose mediaIds to QML
242  originalJson.insert("mediaId"_L1, InfoT::mediaId());
243  originalJson.insert("thumbnailMediaId"_L1, thumbnail.mediaId());
244  }
245 
246  QMimeType type() const override { return InfoT::mimeType; }
247  QUrl url() const override { return InfoT::url(); }
248  FileInfo commonInfo() const override { return SLICE(*this, FileInfo); }
249 
250 protected:
251  QJsonObject makeInfoJson() const override { return toInfoJson(*this); }
252 };
253 
254 //! \brief Content class for m.image
255 //!
256 //! Available fields:
257 //! - corresponding to the top-level JSON:
258 //! - source (corresponding to `url` or `file` in JSON)
259 //! - filename (extension to the spec)
260 //! - corresponding to the `info` subobject:
261 //! - payloadSize (`size` in JSON)
262 //! - mimeType (`mimetype` in JSON)
263 //! - imageSize (QSize for a combination of `h` and `w` in JSON)
264 //! - thumbnail.url (`thumbnail_url` in JSON)
265 //! - corresponding to the `info/thumbnail_info` subobject: contents of
266 //! thumbnail field, in the same vein as for the main image:
267 //! - payloadSize
268 //! - mimeType
269 //! - imageSize
270 using ImageContent = UrlBasedContent<ImageInfo>;
271 
272 //! \brief Content class for m.file
273 //!
274 //! Available fields:
275 //! - corresponding to the top-level JSON:
276 //! - source (corresponding to `url` or `file` in JSON)
277 //! - filename
278 //! - corresponding to the `info` subobject:
279 //! - payloadSize (`size` in JSON)
280 //! - mimeType (`mimetype` in JSON)
281 //! - thumbnail.source (`thumbnail_url` or `thumbnail_file` in JSON)
282 //! - corresponding to the `info/thumbnail_info` subobject:
283 //! - thumbnail.payloadSize
284 //! - thumbnail.mimeType
285 //! - thumbnail.imageSize (QSize for `h` and `w` in JSON)
286 using FileContent = UrlBasedContent<FileInfo>;
287 
288 //! A base class for info types that include duration: audio and video
289 template <typename InfoT>
290 class PlayableContent : public UrlBasedContent<InfoT> {
291 public:
292  using UrlBasedContent<InfoT>::UrlBasedContent;
293  explicit PlayableContent(const QJsonObject& json)
294  : UrlBasedContent<InfoT>(json), duration(FileInfo::originalInfoJson["duration"_L1].toInt())
295  {}
296 
297 protected:
298  QJsonObject makeInfoJson() const override
299  {
300  auto infoJson = UrlBasedContent<InfoT>::makeInfoJson();
301  infoJson.insert("duration"_L1, duration);
302  return infoJson;
303 }
304 
305 public:
306  int duration;
307 };
308 
309 //! \brief Content class for m.video
310 //!
311 //! Available fields:
312 //! - corresponding to the top-level JSON:
313 //! - url
314 //! - filename (extension to the CS API spec)
315 //! - corresponding to the "info" subobject:
316 //! - payloadSize ("size" in JSON)
317 //! - mimeType ("mimetype" in JSON)
318 //! - duration
319 //! - imageSize (QSize for a combination of "h" and "w" in JSON)
320 //! - thumbnail.url ("thumbnail_url" in JSON)
321 //! - corresponding to the "info/thumbnail_info" subobject: contents of
322 //! thumbnail field, in the same vein as for "info":
323 //! - payloadSize
324 //! - mimeType
325 //! - imageSize
326 using VideoContent = PlayableContent<ImageInfo>;
327 
328 //! \brief Content class for m.audio
329 //!
330 //! Available fields:
331 //! - corresponding to the top-level JSON:
332 //! - url
333 //! - filename (extension to the CS API spec)
334 //! - corresponding to the "info" subobject:
335 //! - payloadSize ("size" in JSON)
336 //! - mimeType ("mimetype" in JSON)
337 //! - duration
338 //! - thumbnail.url ("thumbnail_url" in JSON)
339 //! - corresponding to the "info/thumbnail_info" subobject: contents of
340 //! thumbnail field, in the same vein as for "info":
341 //! - payloadSize
342 //! - mimeType
343 //! - imageSize
344 using AudioContent = PlayableContent<FileInfo>;
345 } // namespace Quotient::EventContent
346 Q_DECLARE_METATYPE(const Quotient::EventContent::Base*)