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