1 /******************************************************************************
2  * Copyright (C) 2015 Felix Rohrbach <kde@fxrh.de>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
17  */
18 
19 #pragma once
20 
21 #include "connection.h"
22 #include "eventitem.h"
23 #include "joinstate.h"
24 
25 #include "csapi/message_pagination.h"
26 
27 #include "events/accountdataevents.h"
28 #include "events/encryptedevent.h"
29 #include "events/roomkeyevent.h"
30 #include "events/roommessageevent.h"
31 #include "events/roomcreateevent.h"
32 #include "events/roomtombstoneevent.h"
33 
34 #include <QtCore/QJsonObject>
35 #include <QtGui/QImage>
36 
37 #include <deque>
38 #include <memory>
39 #include <utility>
40 
41 namespace Quotient {
42 class Event;
43 class Avatar;
44 class SyncRoomData;
45 class RoomMemberEvent;
46 class User;
47 class MemberSorter;
48 class LeaveRoomJob;
49 class SetRoomStateWithKeyJob;
50 class RedactEventJob;
51 
52 /** The data structure used to expose file transfer information to views
53  *
54  * This is specifically tuned to work with QML exposing all traits as
55  * Q_PROPERTY values.
56  */
57 class FileTransferInfo {
58     Q_GADGET
59     Q_PROPERTY(bool isUpload MEMBER isUpload CONSTANT)
60     Q_PROPERTY(bool active READ active CONSTANT)
61     Q_PROPERTY(bool started READ started CONSTANT)
62     Q_PROPERTY(bool completed READ completed CONSTANT)
63     Q_PROPERTY(bool failed READ failed CONSTANT)
64     Q_PROPERTY(int progress MEMBER progress CONSTANT)
65     Q_PROPERTY(int total MEMBER total CONSTANT)
66     Q_PROPERTY(QUrl localDir MEMBER localDir CONSTANT)
67     Q_PROPERTY(QUrl localPath MEMBER localPath CONSTANT)
68 public:
69     enum Status { None, Started, Completed, Failed, Cancelled };
70     Status status = None;
71     bool isUpload = false;
72     int progress = 0;
73     int total = -1;
74     QUrl localDir {};
75     QUrl localPath {};
76 
started()77     bool started() const { return status == Started; }
completed()78     bool completed() const { return status == Completed; }
active()79     bool active() const { return started() || completed(); }
failed()80     bool failed() const { return status == Failed; }
81 };
82 
83 class Room : public QObject {
84     Q_OBJECT
85     Q_PROPERTY(Connection* connection READ connection CONSTANT)
86     Q_PROPERTY(User* localUser READ localUser CONSTANT)
87     Q_PROPERTY(QString id READ id CONSTANT)
88     Q_PROPERTY(QString version READ version NOTIFY baseStateLoaded)
89     Q_PROPERTY(bool isUnstable READ isUnstable NOTIFY stabilityUpdated)
90     Q_PROPERTY(QString predecessorId READ predecessorId NOTIFY baseStateLoaded)
91     Q_PROPERTY(QString successorId READ successorId NOTIFY upgraded)
92     Q_PROPERTY(QString name READ name NOTIFY namesChanged)
93     Q_PROPERTY(QStringList aliases READ aliases NOTIFY namesChanged)
94     Q_PROPERTY(QStringList altAliases READ altAliases NOTIFY namesChanged)
95     Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged)
96     Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged)
97     Q_PROPERTY(QString topic READ topic NOTIFY topicChanged)
98     Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged
99                    STORED false)
100     Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarChanged)
101     Q_PROPERTY(bool usesEncryption READ usesEncryption NOTIFY encryption)
102 
103     Q_PROPERTY(int timelineSize READ timelineSize NOTIFY addedMessages)
104     Q_PROPERTY(QStringList memberNames READ memberNames NOTIFY memberListChanged)
105     Q_PROPERTY(int memberCount READ memberCount NOTIFY memberListChanged)
106     Q_PROPERTY(int joinedCount READ joinedCount NOTIFY memberListChanged)
107     Q_PROPERTY(int invitedCount READ invitedCount NOTIFY memberListChanged)
108     Q_PROPERTY(int totalMemberCount READ totalMemberCount NOTIFY memberListChanged)
109 
110     Q_PROPERTY(bool displayed READ displayed WRITE setDisplayed NOTIFY
111                    displayedChanged)
112     Q_PROPERTY(QString firstDisplayedEventId READ firstDisplayedEventId WRITE
113                    setFirstDisplayedEventId NOTIFY firstDisplayedEventChanged)
114     Q_PROPERTY(QString lastDisplayedEventId READ lastDisplayedEventId WRITE
115                    setLastDisplayedEventId NOTIFY lastDisplayedEventChanged)
116 
117     Q_PROPERTY(QString readMarkerEventId READ readMarkerEventId WRITE
118                    markMessagesAsRead NOTIFY readMarkerMoved)
119     Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY
120                    unreadMessagesChanged)
121     Q_PROPERTY(int unreadCount READ unreadCount NOTIFY unreadMessagesChanged)
122     Q_PROPERTY(int highlightCount READ highlightCount NOTIFY
123                    highlightCountChanged RESET resetHighlightCount)
124     Q_PROPERTY(int notificationCount READ notificationCount NOTIFY
125                    notificationCountChanged RESET resetNotificationCount)
126     Q_PROPERTY(bool allHistoryLoaded READ allHistoryLoaded NOTIFY addedMessages
127                    STORED false)
128     Q_PROPERTY(QStringList tagNames READ tagNames NOTIFY tagsChanged)
129     Q_PROPERTY(bool isFavourite READ isFavourite NOTIFY tagsChanged)
130     Q_PROPERTY(bool isLowPriority READ isLowPriority NOTIFY tagsChanged)
131 
132     Q_PROPERTY(GetRoomEventsJob* eventsHistoryJob READ eventsHistoryJob NOTIFY
133                    eventsHistoryJobChanged)
134 
135 public:
136     using Timeline = std::deque<TimelineItem>;
137     using PendingEvents = std::vector<PendingEventItem>;
138     using RelatedEvents = QVector<const RoomEvent*>;
139     using rev_iter_t = Timeline::const_reverse_iterator;
140     using timeline_iter_t = Timeline::const_iterator;
141 
142     enum Change : uint {
143         NoChange = 0x0,
144         NameChange = 0x1,
145         AliasesChange = 0x2,
146         CanonicalAliasChange = AliasesChange,
147         TopicChange = 0x4,
148         UnreadNotifsChange = 0x8,
149         AvatarChange = 0x10,
150         JoinStateChange = 0x20,
151         TagsChange = 0x40,
152         MembersChange = 0x80,
153         /* = 0x100, */
154         AccountDataChange = 0x200,
155         SummaryChange = 0x400,
156         ReadMarkerChange = 0x800,
157         OtherChange = 0x8000,
158         AnyChange = 0xFFFF
159     };
160     Q_DECLARE_FLAGS(Changes, Change)
161     Q_FLAG(Changes)
162 
163     Room(Connection* connection, QString id, JoinState initialJoinState);
164     ~Room() override;
165 
166     // Property accessors
167 
168     Connection* connection() const;
169     User* localUser() const;
170     const QString& id() const;
171     QString version() const;
172     bool isUnstable() const;
173     QString predecessorId() const;
174     /// Room predecessor
175     /** This function validates that the predecessor has a tombstone and
176      * the tombstone refers to the current room. If that's not the case,
177      * or if the predecessor is in a join state not matching \p stateFilter,
178      * the function returns nullptr.
179      */
180     Room* predecessor(JoinStates statesFilter = JoinState::Invite
181                                                 | JoinState::Join) const;
182     QString successorId() const;
183     /// Room successor
184     /** This function validates that the successor room's creation event
185      * refers to the current room. If that's not the case, or if the successor
186      * is in a join state not matching \p stateFilter, it returns nullptr.
187      */
188     Room* successor(JoinStates statesFilter = JoinState::Invite
189                                               | JoinState::Join) const;
190     QString name() const;
191     /// Room aliases defined on the current user's server
192     /// \sa remoteAliases, setLocalAliases
193     [[deprecated("Use aliases()")]]
194     QStringList localAliases() const;
195     /// Room aliases defined on other servers
196     /// \sa localAliases
197     [[deprecated("Use aliases()")]]
198     QStringList remoteAliases() const;
199     QString canonicalAlias() const;
200     QStringList altAliases() const;
201     QStringList aliases() const;
202     QString displayName() const;
203     QString topic() const;
204     QString avatarMediaId() const;
205     QUrl avatarUrl() const;
206     const Avatar& avatarObject() const;
207     Q_INVOKABLE JoinState joinState() const;
208     Q_INVOKABLE QList<Quotient::User*> usersTyping() const;
209     QList<User*> membersLeft() const;
210 
211     Q_INVOKABLE QList<Quotient::User*> users() const;
212     QStringList memberNames() const;
213     [[deprecated("Use joinedCount(), invitedCount(), totalMemberCount()")]]
214     int memberCount() const;
215     int timelineSize() const;
216     bool usesEncryption() const;
217     RoomEventPtr decryptMessage(const EncryptedEvent& encryptedEvent);
218     void handleRoomKeyEvent(const RoomKeyEvent& roomKeyEvent, const QString& senderKey);
219     int joinedCount() const;
220     int invitedCount() const;
221     int totalMemberCount() const;
222 
223     GetRoomEventsJob* eventsHistoryJob() const;
224 
225     /**
226      * Returns a square room avatar with the given size and requests it
227      * from the network if needed
228      * \return a pixmap with the avatar or a placeholder if there's none
229      * available yet
230      */
231     Q_INVOKABLE QImage avatar(int dimension);
232     /**
233      * Returns a room avatar with the given dimensions and requests it
234      * from the network if needed
235      * \return a pixmap with the avatar or a placeholder if there's none
236      * available yet
237      */
238     Q_INVOKABLE QImage avatar(int width, int height);
239 
240     /**
241      * \brief Get a user object for a given user id
242      * This is the recommended way to get a user object in a room
243      * context. The actual object type may be changed in further
244      * versions to provide room-specific user information (display name,
245      * avatar etc.).
246      * \note The method will return a valid user regardless of
247      *       the membership.
248      */
249     Q_INVOKABLE Quotient::User* user(const QString& userId) const;
250 
251     /**
252      * \brief Check the join state of a given user in this room
253      *
254      * \note Banned and invited users are not tracked for now (Leave
255      *       will be returned for them).
256      *
257      * \return Join if the user is a room member; Leave otherwise
258      */
259     Q_INVOKABLE Quotient::JoinState memberJoinState(Quotient::User* user) const;
260 
261     /**
262      * Get a disambiguated name for a given user in
263      * the context of the room
264      */
265     Q_INVOKABLE QString roomMembername(const Quotient::User* u) const;
266     /**
267      * Get a disambiguated name for a user with this id in
268      * the context of the room
269      */
270     Q_INVOKABLE QString roomMembername(const QString& userId) const;
271 
272     /** Get a display-safe member name in the context of this room
273      *
274      * Display-safe means HTML-safe + without RLO/LRO markers
275      * (see https://github.com/quotient-im/Quaternion/issues/545).
276      */
277     Q_INVOKABLE QString safeMemberName(const QString& userId) const;
278 
279     const Timeline& messageEvents() const;
280     const PendingEvents& pendingEvents() const;
281 
282     /// Check whether all historical messages are already loaded
283     /**
284      * \return true if the "oldest" event in the timeline is
285      *         a room creation event and there's no further history
286      *         to load; false otherwise
287      */
288     bool allHistoryLoaded() const;
289     /**
290      * A convenience method returning the read marker to the position
291      * before the "oldest" event; same as messageEvents().crend()
292      */
293     rev_iter_t historyEdge() const;
294     /**
295      * A convenience method returning the iterator beyond the latest
296      * arrived event; same as messageEvents().cend()
297      */
298     Timeline::const_iterator syncEdge() const;
299     /// \deprecated Use historyEdge instead
300     rev_iter_t timelineEdge() const;
301     Q_INVOKABLE Quotient::TimelineItem::index_t minTimelineIndex() const;
302     Q_INVOKABLE Quotient::TimelineItem::index_t maxTimelineIndex() const;
303     Q_INVOKABLE bool
304     isValidIndex(Quotient::TimelineItem::index_t timelineIndex) const;
305 
306     rev_iter_t findInTimeline(TimelineItem::index_t index) const;
307     rev_iter_t findInTimeline(const QString& evtId) const;
308     PendingEvents::iterator findPendingEvent(const QString& txnId);
309     PendingEvents::const_iterator findPendingEvent(const QString& txnId) const;
310 
311     const RelatedEvents relatedEvents(const QString& evtId,
312                                       const char* relType) const;
313     const RelatedEvents relatedEvents(const RoomEvent& evt,
314                                       const char* relType) const;
315 
creation()316     const RoomCreateEvent* creation() const
317     { return getCurrentState<RoomCreateEvent>(); }
tombstone()318     const RoomTombstoneEvent* tombstone() const
319     { return getCurrentState<RoomTombstoneEvent>(); }
320 
321     bool displayed() const;
322     /// Mark the room as currently displayed to the user
323     /**
324      * Marking the room displayed causes the room to obtain the full
325      * list of members if it's been lazy-loaded before; in the future
326      * it may do more things bound to "screen time" of the room, e.g.
327      * measure that "screen time".
328      */
329     void setDisplayed(bool displayed = true);
330     QString firstDisplayedEventId() const;
331     rev_iter_t firstDisplayedMarker() const;
332     void setFirstDisplayedEventId(const QString& eventId);
333     void setFirstDisplayedEvent(TimelineItem::index_t index);
334     QString lastDisplayedEventId() const;
335     rev_iter_t lastDisplayedMarker() const;
336     void setLastDisplayedEventId(const QString& eventId);
337     void setLastDisplayedEvent(TimelineItem::index_t index);
338 
339     rev_iter_t readMarker(const User* user) const;
340     rev_iter_t readMarker() const;
341     QString readMarkerEventId() const;
342     QList<User*> usersAtEventId(const QString& eventId);
343     /**
344      * \brief Mark the event with uptoEventId as read
345      *
346      * Finds in the timeline and marks as read the event with
347      * the specified id; also posts a read receipt to the server either
348      * for this message or, if it's from the local user, for
349      * the nearest non-local message before. uptoEventId must be non-empty.
350      */
351     void markMessagesAsRead(QString uptoEventId);
352 
353     /// Check whether there are unread messages in the room
354     bool hasUnreadMessages() const;
355 
356     /** Get the number of unread messages in the room
357      * Depending on the read marker state, this call may return either
358      * a precise or an estimate number of unread events. Only "notable"
359      * events (non-redacted message events from users other than local)
360      * are counted.
361      *
362      * In a case when readMarker() == timelineEdge() (the local read
363      * marker is beyond the local timeline) only the bottom limit of
364      * the unread messages number can be estimated (and even that may
365      * be slightly off due to, e.g., redactions of events not loaded
366      * to the local timeline).
367      *
368      * If all messages are read, this function will return -1 (_not_ 0,
369      * as zero may mean "zero or more unread messages" in a situation
370      * when the read marker is outside the local timeline.
371      */
372     int unreadCount() const;
373 
374     Q_INVOKABLE int notificationCount() const;
375     Q_INVOKABLE void resetNotificationCount();
376     Q_INVOKABLE int highlightCount() const;
377     Q_INVOKABLE void resetHighlightCount();
378 
379     /** Check whether the room has account data of the given type
380      * Tags and read markers are not supported by this method _yet_.
381      */
382     bool hasAccountData(const QString& type) const;
383 
384     /** Get a generic account data event of the given type
385      * This returns a generic hash map for any room account data event
386      * stored on the server. Tags and read markers cannot be retrieved
387      * using this method _yet_.
388      */
389     const EventPtr& accountData(const QString& type) const;
390 
391     QStringList tagNames() const;
392     TagsMap tags() const;
393     TagRecord tag(const QString& name) const;
394 
395     /** Add a new tag to this room
396      * If this room already has this tag, nothing happens. If it's a new
397      * tag for the room, the respective tag record is added to the set
398      * of tags and the new set is sent to the server to update other
399      * clients.
400      */
401     void addTag(const QString& name, const TagRecord& record = {});
402     Q_INVOKABLE void addTag(const QString& name, float order);
403 
404     /// Remove a tag from the room
405     Q_INVOKABLE void removeTag(const QString& name);
406 
407     /// The scope to apply an action on
408     /*! This enumeration is used to pick a strategy to propagate certain
409      * actions on the room to its predecessors and successors.
410      */
411     enum ActionScope {
412         ThisRoomOnly,    //< Do not apply to predecessors and successors
413         WithinSameState, //< Apply to predecessors and successors in the same
414                          //< state as the current one
415         OmitLeftState,   //< Apply to all reachable predecessors and successors
416                          //< except those in Leave state
417         WholeSequence    //< Apply to all reachable predecessors and successors
418     };
419 
420     /** Overwrite the room's tags
421      * This completely replaces the existing room's tags with a set
422      * of new ones and updates the new set on the server. Unlike
423      * most other methods in Room, this one sends a signal about changes
424      * immediately, not waiting for confirmation from the server
425      * (because tags are saved in account data rather than in shared
426      * room state).
427      * \param applyOn setting this to Room::OnAllConversations will set tags
428      *                on this and all _known_ predecessors and successors;
429      *                by default only the current room is changed
430      */
431     void setTags(TagsMap newTags, ActionScope applyOn = ThisRoomOnly);
432 
433     /// Check whether the list of tags has m.favourite
434     bool isFavourite() const;
435     /// Check whether the list of tags has m.lowpriority
436     bool isLowPriority() const;
437     /// Check whether this room is for server notices (MSC1452)
438     bool isServerNoticeRoom() const;
439 
440     /// Check whether this room is a direct chat
441     Q_INVOKABLE bool isDirectChat() const;
442 
443     /// Get the list of users this room is a direct chat with
444     QList<User*> directChatUsers() const;
445 
446     Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId) const;
447     Q_INVOKABLE QUrl urlToDownload(const QString& eventId) const;
448 
449     /// Get a file name for downloading for a given event id
450     /*!
451      * The event MUST be RoomMessageEvent and have content
452      * for downloading. \sa RoomMessageEvent::hasContent
453      */
454     Q_INVOKABLE QString fileNameToDownload(const QString& eventId) const;
455 
456     /// Get information on file upload/download
457     /*!
458      * \param id uploads are identified by the corresponding event's
459      *           transactionId (because uploads are done before
460      *           the event is even sent), while downloads are using
461      *           the normal event id for identifier.
462      */
463     Q_INVOKABLE Quotient::FileTransferInfo
464     fileTransferInfo(const QString& id) const;
465 
466     /// Get the URL to the actual file source in a unified way
467     /*!
468      * For uploads it will return a URL to a local file; for downloads
469      * the URL will be taken from the corresponding room event.
470      */
471     Q_INVOKABLE QUrl fileSource(const QString& id) const;
472 
473     /** Pretty-prints plain text into HTML
474      * As of now, it's exactly the same as Quotient::prettyPrint();
475      * in the future, it will also linkify room aliases, mxids etc.
476      * using the room context.
477      */
478     Q_INVOKABLE QString prettyPrint(const QString& plainText) const;
479 
480     MemberSorter memberSorter() const;
481 
482     Q_INVOKABLE bool supportsCalls() const;
483 
484     /// Whether the current user is allowed to upgrade the room
485     Q_INVOKABLE bool canSwitchVersions() const;
486 
487     /// Get a state event with the given event type and state key
488     /*! This method returns a (potentially empty) state event corresponding
489      * to the pair of event type \p evtType and state key \p stateKey.
490      */
491     Q_INVOKABLE const Quotient::StateEventBase*
492     getCurrentState(const QString& evtType, const QString& stateKey = {}) const;
493 
494     /// Get a state event with the given event type and state key
495     /*! This is a typesafe overload that accepts a C++ event type instead of
496      * its Matrix name.
497      */
498     template <typename EvT>
499     const EvT* getCurrentState(const QString& stateKey = {}) const
500     {
501         const auto* evt =
502             eventCast<const EvT>(getCurrentState(EvT::matrixTypeId(), stateKey));
503         Q_ASSERT(evt);
504         Q_ASSERT(evt->matrixTypeId() == EvT::matrixTypeId()
505                  && evt->stateKey() == stateKey);
506         return evt;
507     }
508 
509     /// Set a state event of the given type with the given arguments
510     /*! This typesafe overload attempts to send a state event with the type
511      * \p EvT and the content defined by \p args. Specifically, the function
512      * creates a temporary object of type \p EvT passing \p args to
513      * the constructor, and sends a request to the homeserver using
514      * the Matrix event type defined by \p EvT and the event content produced
515      * via EvT::contentJson().
516      */
517     template <typename EvT, typename... ArgTs>
setState(ArgTs &&...args)518     auto setState(ArgTs&&... args) const
519     {
520         return setState(EvT(std::forward<ArgTs>(args)...));
521     }
522 
523 public slots:
524     /** Check whether the room should be upgraded */
525     void checkVersion();
526 
527     QString postMessage(const QString& plainText, MessageEventType type);
528     QString postPlainText(const QString& plainText);
529     QString postHtmlMessage(const QString& plainText, const QString& html,
530                             MessageEventType type = MessageEventType::Text);
531     QString postHtmlText(const QString& plainText, const QString& html);
532     /// Send a reaction on a given event with a given key
533     QString postReaction(const QString& eventId, const QString& key);
534     QString postFile(const QString& plainText, const QUrl& localPath,
535                      bool asGenericFile = false);
536     /** Post a pre-created room message event
537      *
538      * Takes ownership of the event, deleting it once the matching one
539      * arrives with the sync
540      * \return transaction id associated with the event.
541      */
542     QString postEvent(RoomEvent* event);
543     QString postJson(const QString& matrixType, const QJsonObject& eventContent);
544     QString retryMessage(const QString& txnId);
545     void discardMessage(const QString& txnId);
546 
547     /// Send a request to update the room state with the given event
548     SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const;
549     void setName(const QString& newName);
550     void setCanonicalAlias(const QString& newAlias);
551     /// Set room aliases on the user's current server
552     void setLocalAliases(const QStringList& aliases);
553     void setTopic(const QString& newTopic);
554 
555     /// You shouldn't normally call this method; it's here for debugging
556     void refreshDisplayName();
557 
558     void getPreviousContent(int limit = 10);
559 
560     void inviteToRoom(const QString& memberId);
561     LeaveRoomJob* leaveRoom();
562     /// \deprecated - use setState() instead")
563     SetRoomStateWithKeyJob* setMemberState(const QString& memberId,
564                                            const RoomMemberEvent& event) const;
565     void kickMember(const QString& memberId, const QString& reason = {});
566     void ban(const QString& userId, const QString& reason = {});
567     void unban(const QString& userId);
568     void redactEvent(const QString& eventId, const QString& reason = {});
569 
570     void uploadFile(const QString& id, const QUrl& localFilename,
571                     const QString& overrideContentType = {});
572     // If localFilename is empty a temporary file is created
573     void downloadFile(const QString& eventId, const QUrl& localFilename = {});
574     void cancelFileTransfer(const QString& id);
575 
576     /// Mark all messages in the room as read
577     void markAllMessagesAsRead();
578 
579     /// Switch the room's version (aka upgrade)
580     void switchVersion(QString newVersion);
581 
582     void inviteCall(const QString& callId, const int lifetime,
583                     const QString& sdp);
584     void sendCallCandidates(const QString& callId, const QJsonArray& candidates);
585     void answerCall(const QString& callId, const int lifetime,
586                     const QString& sdp);
587     void answerCall(const QString& callId, const QString& sdp);
588     void hangupCall(const QString& callId);
589 
590 signals:
591     /// Initial set of state events has been loaded
592     /**
593      * The initial set is what comes from the initial sync for the room.
594      * This includes all basic things like RoomCreateEvent,
595      * RoomNameEvent, a (lazy-loaded, not full) set of RoomMemberEvents
596      * etc. This is a per-room reflection of Connection::loadedRoomState
597      * \sa Connection::loadedRoomState
598      */
599     void baseStateLoaded();
600     void eventsHistoryJobChanged();
601     void aboutToAddHistoricalMessages(RoomEventsRange events);
602     void aboutToAddNewMessages(RoomEventsRange events);
603     void addedMessages(int fromIndex, int toIndex);
604     /// The event is about to be appended to the list of pending events
605     void pendingEventAboutToAdd(RoomEvent* event);
606     /// An event has been appended to the list of pending events
607     void pendingEventAdded();
608     /// The remote echo has arrived with the sync and will be merged
609     /// with its local counterpart
610     /** NB: Requires a sync loop to be emitted */
611     void pendingEventAboutToMerge(Quotient::RoomEvent* serverEvent,
612                                   int pendingEventIndex);
613     /// The remote and local copies of the event have been merged
614     /** NB: Requires a sync loop to be emitted */
615     void pendingEventMerged();
616     /// An event will be removed from the list of pending events
617     void pendingEventAboutToDiscard(int pendingEventIndex);
618     /// An event has just been removed from the list of pending events
619     void pendingEventDiscarded();
620     /// The status of a pending event has changed
621     /** \sa PendingEventItem::deliveryStatus */
622     void pendingEventChanged(int pendingEventIndex);
623     /// The server accepted the message
624     /** This is emitted when an event sending request has successfully
625      * completed. This does not mean that the event is already in the
626      * local timeline, only that the server has accepted it.
627      * \param txnId transaction id assigned by the client during sending
628      * \param eventId event id assigned by the server upon acceptance
629      * \sa postEvent, postPlainText, postMessage, postHtmlMessage
630      * \sa pendingEventMerged, aboutToAddNewMessages
631      */
632     void messageSent(QString txnId, QString eventId);
633 
634     /** A common signal for various kinds of changes in the room
635      * Aside from all changes in the room state
636      * @param changes a set of flags describing what changes occurred
637      *                upon the last sync
638      * \sa Changes
639      */
640     void changed(Quotient::Room::Changes changes);
641     /**
642      * \brief The room name, the canonical alias or other aliases changed
643      *
644      * Not triggered when display name changes.
645      */
646     void namesChanged(Quotient::Room* room);
647     void displaynameAboutToChange(Quotient::Room* room);
648     void displaynameChanged(Quotient::Room* room, QString oldName);
649     void topicChanged();
650     void avatarChanged();
651     void userAdded(Quotient::User* user);
652     void userRemoved(Quotient::User* user);
653     void memberAboutToRename(Quotient::User* user, QString newName);
654     void memberRenamed(Quotient::User* user);
655     /// The list of members has changed
656     /** Emitted no more than once per sync, this is a good signal to
657      * for cases when some action should be done upon any change in
658      * the member list. If you need per-item granularity you should use
659      * userAdded, userRemoved and memberAboutToRename / memberRenamed
660      * instead.
661      */
662     void memberListChanged();
663     /// The previously lazy-loaded members list is now loaded entirely
664     /// \sa setDisplayed
665     void allMembersLoaded();
666     void encryption();
667 
668     void joinStateChanged(Quotient::JoinState oldState,
669                           Quotient::JoinState newState);
670     void typingChanged();
671 
672     void highlightCountChanged();
673     void notificationCountChanged();
674 
675     void displayedChanged(bool displayed);
676     void firstDisplayedEventChanged();
677     void lastDisplayedEventChanged();
678     void lastReadEventChanged(Quotient::User* user);
679     void readMarkerMoved(QString fromEventId, QString toEventId);
680     void readMarkerForUserMoved(Quotient::User* user, QString fromEventId,
681                                 QString toEventId);
682     void unreadMessagesChanged(Quotient::Room* room);
683 
684     void accountDataAboutToChange(QString type);
685     void accountDataChanged(QString type);
686     void tagsAboutToChange();
687     void tagsChanged();
688 
689     void updatedEvent(QString eventId);
690     void replacedEvent(const Quotient::RoomEvent* newEvent,
691                        const Quotient::RoomEvent* oldEvent);
692 
693     void newFileTransfer(QString id, QUrl localFile);
694     void fileTransferProgress(QString id, qint64 progress, qint64 total);
695     void fileTransferCompleted(QString id, QUrl localFile, QUrl mxcUrl);
696     void fileTransferFailed(QString id, QString errorMessage = {});
697     void fileTransferCancelled(QString id);
698 
699     void callEvent(Quotient::Room* room, const Quotient::RoomEvent* event);
700 
701     /// The room's version stability may have changed
702     void stabilityUpdated(QString recommendedDefault,
703                           QStringList stableVersions);
704     /// This room has been upgraded and won't receive updates any more
705     void upgraded(QString serverMessage, Quotient::Room* successor);
706     /// An attempted room upgrade has failed
707     void upgradeFailed(QString errorMessage);
708 
709     /// The room is about to be deleted
710     void beforeDestruction(Quotient::Room*);
711 
712 protected:
713     virtual Changes processStateEvent(const RoomEvent& e);
714     virtual Changes processEphemeralEvent(EventPtr&& event);
715     virtual Changes processAccountDataEvent(EventPtr&& event);
onAddNewTimelineEvents(timeline_iter_t)716     virtual void onAddNewTimelineEvents(timeline_iter_t /*from*/) {}
onAddHistoricalTimelineEvents(rev_iter_t)717     virtual void onAddHistoricalTimelineEvents(rev_iter_t /*from*/) {}
onRedaction(const RoomEvent &,const RoomEvent &)718     virtual void onRedaction(const RoomEvent& /*prevEvent*/,
719                              const RoomEvent& /*after*/)
720     {}
721     virtual QJsonObject toJson() const;
722     virtual void updateData(SyncRoomData&& data, bool fromCache = false);
723 
724 private:
725     friend class Connection;
726 
727     class Private;
728     Private* d;
729 
730     // This is called from Connection, reflecting a state change that
731     // arrived from the server. Clients should use
732     // Connection::joinRoom() and Room::leaveRoom() to change the state.
733     void setJoinState(JoinState state);
734 };
735 
736 class MemberSorter {
737 public:
MemberSorter(const Room * r)738     explicit MemberSorter(const Room* r) : room(r) {}
739 
740     bool operator()(User* u1, User* u2) const;
741     bool operator()(User* u1, const QString& u2name) const;
742 
743     template <typename ContT, typename ValT>
lowerBoundIndex(const ContT & c,const ValT & v)744     typename ContT::size_type lowerBoundIndex(const ContT& c, const ValT& v) const
745     {
746         return std::lower_bound(c.begin(), c.end(), v, *this) - c.begin();
747     }
748 
749 private:
750     const Room* room;
751 };
752 } // namespace Quotient
753 Q_DECLARE_METATYPE(Quotient::FileTransferInfo)
754 Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::Room::Changes)
755