diff options
-rw-r--r-- | .travis.yml | 40 | ||||
-rw-r--r-- | CMakeLists.txt | 31 | ||||
-rw-r--r-- | CONTRIBUTING.md | 78 | ||||
-rw-r--r-- | README.md | 20 | ||||
-rw-r--r-- | avatar.cpp | 39 | ||||
-rw-r--r-- | avatar.h | 20 | ||||
-rw-r--r-- | connection.cpp | 19 | ||||
-rw-r--r-- | connection.h | 2 | ||||
-rw-r--r-- | converters.h | 2 | ||||
-rw-r--r-- | events/event.cpp | 48 | ||||
-rw-r--r-- | events/event.h | 127 | ||||
-rw-r--r-- | events/receiptevent.cpp | 6 | ||||
-rw-r--r-- | events/receiptevent.h | 4 | ||||
-rw-r--r-- | events/redactionevent.cpp | 1 | ||||
-rw-r--r-- | events/redactionevent.h | 43 | ||||
-rw-r--r-- | events/roommessageevent.cpp | 6 | ||||
-rw-r--r-- | examples/qmc-example.cpp | 22 | ||||
-rw-r--r-- | jobs/passwordlogin.cpp | 17 | ||||
-rw-r--r-- | jobs/roommessagesjob.cpp | 6 | ||||
-rw-r--r-- | jobs/roommessagesjob.h | 2 | ||||
-rw-r--r-- | jobs/syncjob.h | 9 | ||||
-rw-r--r-- | libqmatrixclient.pri | 2 | ||||
-rw-r--r-- | room.cpp | 371 | ||||
-rw-r--r-- | room.h | 43 | ||||
-rw-r--r-- | settings.cpp | 9 | ||||
-rw-r--r-- | settings.h | 8 | ||||
-rw-r--r-- | util.h | 47 |
27 files changed, 661 insertions, 361 deletions
diff --git a/.travis.yml b/.travis.yml index c4cd4326..6c08b428 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,26 @@ language: cpp +os: [ linux, osx ] + +compiler: [ gcc, clang ] + addons: apt: sources: - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.8 + - sourceline: 'ppa:beineri/opt-qt563-trusty' packages: - g++-5 - - clang-3.8 - - qt5-default + - qt56base matrix: - include: - - os: linux - env: [ ENV_EVAL="CC=gcc-5 && CXX=g++-5" ] - - os: linux - env: [ ENV_EVAL="CC=clang-3.8 && CXX=clang++-3.8" ] - - os: osx - env: [ ENV_EVAL="brew update && brew install qt5 && CMAKE_PREFIX_PATH=/usr/local/opt/qt" ] + exclude: [ { os: osx, compiler: gcc } ] + +before_install: +- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; brew install qt5; export PATH="$PATH:/usr/local/opt/qt/bin"; fi +- if [ "$TRAVIS_OS_NAME" = "linux" ]; then . /opt/qt56/bin/qt56-env.sh; fi install: -- eval "${ENV_EVAL}" - git clone https://github.com/QMatrixClient/matrix-doc.git - git clone --recursive https://github.com/KitsuneRal/gtad.git - pushd gtad @@ -34,14 +34,14 @@ before_script: - cmake --build . --target update-api script: - - cmake --build . - - cd .. - - qmake qmc-example.pro && make all +- cmake --build . --target all +- cd .. +- qmake qmc-example.pro && make all notifications: - webhooks: - urls: - - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGtpdHN1bmUlM0FtYXRyaXgub3JnLyUyMVBDelV0eHRPalV5U3hTZWxvZiUzQW1hdHJpeC5vcmc" - on_success: change # always|never|change - on_failure: always - on_start: never + webhooks: + urls: + - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGtpdHN1bmUlM0FtYXRyaXgub3JnLyUyMVBDelV0eHRPalV5U3hTZWxvZiUzQW1hdHJpeC5vcmc" + on_success: change # always|never|change + on_failure: always + on_start: never diff --git a/CMakeLists.txt b/CMakeLists.txt index a1e96368..0ead9ec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,25 +20,16 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() # Setup command line parameters for the compiler and linker -CHECK_CXX_COMPILER_FLAG("-Wall" WALL_FLAG_SUPPORTED) -if ( WALL_FLAG_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "(^| )-Wall($| )") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") -endif ( ) -CHECK_CXX_COMPILER_FLAG("-Wpedantic" PEDANTIC_FLAG_SUPPORTED) -if ( PEDANTIC_FLAG_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "(^| )pedantic($| )") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic") -endif ( ) - -if ( CMAKE_VERSION VERSION_LESS "3.1" ) - CHECK_CXX_COMPILER_FLAG("-std=c++11" STD_FLAG_SUPPORTED) - if ( STD_FLAG_SUPPORTED ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - endif ( STD_FLAG_SUPPORTED ) -else ( CMAKE_VERSION VERSION_LESS "3.1" ) - set(CMAKE_CXX_STANDARD 11) -endif ( CMAKE_VERSION VERSION_LESS "3.1" ) - -find_package(Qt5 5.2.1 REQUIRED Network Gui) +foreach (FLAG all pedantic error=return-type) + CHECK_CXX_COMPILER_FLAG("-W${FLAG}" WARN_${FLAG}_SUPPORTED) + if ( WARN_${FLAG}_SUPPORTED AND NOT CMAKE_CXX_FLAGS MATCHES "(^| )-W?${FLAG}($| )") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W${FLAG}") + endif ( ) +endforeach () + +set(CMAKE_CXX_STANDARD 14) + +find_package(Qt5 5.6 REQUIRED Network Gui) get_filename_component(Qt5_Prefix "${Qt5_DIR}/../../../.." ABSOLUTE) message( STATUS ) @@ -76,10 +67,8 @@ set(libqmatrixclient_SRCS events/receiptevent.cpp jobs/basejob.cpp jobs/checkauthmethods.cpp - jobs/passwordlogin.cpp jobs/sendeventjob.cpp jobs/setroomstatejob.cpp - jobs/postreceiptjob.cpp jobs/joinroomjob.cpp jobs/roommessagesjob.cpp jobs/syncjob.cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81c9d819..e67fabed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,14 +96,48 @@ Any components proposed for reuse should have a license that permits releasing a If you find a significant vulnerability, or evidence of one, use either of the following contacts: -* send an email to Kitsune Ral <Kitsune-Ral@users.sf.net> -* reach out in Matrix to #kitsune:matrix.org (with encryption switched on) +* send an email to Kitsune Ral [Kitsune-Ral@users.sf.net](mailto:Kitsune-Ral@users.sf.net) +* reach out in Matrix to #kitsune:matrix.org (if you can, switch encryption **on**) In any of these two options, _indicate that you have such information_ (do not share the information yet), and we'll tell you the next steps. We will gladly give credit to anyone who reports a vulnerability so that we can fix it. If you want to remain anonymous or pseudonymous instead, please let us know that; we will gladly respect your wishes. +## Code changes + +The code should strive to be DRY (don't repeat yourself), clear, and obviously correct. Some technical debt is inevitable, just don't bankrupt us with it. Refactoring is welcome. + +### Qt-flavoured C++ + +This is our primary language. We don't have a particular code style _as of yet_ but some rules-of-thumb are below: +* 4-space indents, no tabs, no trailing spaces, no last empty lines. If you spot the code abusing these - we'll thank you for fixing it. +* Lines within 80 characters are preferred. +* Braces after if's, while's, do's, function signatures etc. take a separate line. +* A historical deviation from the usual Qt code format conventions is an extra indent inside _classes_ (access specifiers go at +4 spaces to the base, members at +8 spaces) but not _structs_ (members at +4 spaces). This may change in the future for something more conventional. +* Please don't make hypocritical structs with protected or private members. Just make them classes instead. +* Qt containers are generally preferred to STL containers; however, there are notable exceptions, and libqmatrixclient already uses them: + * `std::array` and `std::deque` have no direct counterparts in Qt. + * Because of COW semantics, Qt containers cannot hold uncopyable classes. Classes without a default constructor are a problem too. An example of that is `SyncRoomData` and `EventsBatch<>`. Use standard containers for those but see the next point and also consider if you can supply a reasonable copy/default constructor. + * Standard containers can be used in code internal to a translation unit (i.e., in a certain .cpp file) _as long as it's not exposed in the interface_. It's ok to use, e.g., `std::vector` instead of `QVector` in tighter code, or when dealing with uncopyable (see the previous point) elements. However, exposing standard containers in the API that might be used by QML will not work at all and will never be accepted. +* Use `QVector` instead of `QList` where possible - see a [great article of Marc Mutz on Qt containers](https://marcmutz.wordpress.com/effective-qt/containers/) for details. + +### Automated tests + +There's no testing framework as of now; either Catch or QTest or both will be used eventually (PRs welcome, just don't expect a quick merge of one - we'll hunt you down to actually write some tests first :-D ). + +### Security, privacy, and performance + +Pay attention to security, and work *with* (not against) the usual security hardening mechanisms (however few in C++). `char *` and similar unchecked C-style read/write arrays are forbidden - use Qt containers or at the very least `std::array<>` instead. + +Exercise the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) where reasonable and appropriate. Prefer less-coupled cohesive code. + +Protect private information, in particular passwords and email addresses. Do not forget about local access to data (in particular, be very careful when storing something in temporary files, let alone permanent configuration or state). Avoid mechanisms that could be used for tracking where possible (we do need to verify people are logged in but that's pretty much it), and ensure that third parties can't use interactions for tracking. + +We want the software to have decent performance for typical users. At the same time we keep libqmatrixclient single-threaded as much as possible, to keep the code simple. That means being cautious about operation complexity (read about big-O notation if you need a kickstart on the topic). This especially refers to operations on the whole timeline - it can easily be several thousands elements long so even operations with linear complexity, if heavy enough, can produce noticeable GUI freezing. + +Having said that, there's always a trade-off between various attributes; in particular, readability and maintainability of the code is more important than squeezing every bit out of that clumsy algorithm. + ## Documentation changes Most of the documentation is in "markdown" format. @@ -116,51 +150,19 @@ In practice we use the version of Markdown implemented by GitHub when it renders This version of markdown is sometimes called [GitHub-flavored markdown](https://help.github.com/articles/github-flavored-markdown/). In particular, blank lines separate paragraphs; newlines inside a paragraph do *not* force a line break. -Beware - this is *not* the same markdown algorithm used by GitHub when it renders issue or pull comments; in those cases [newlines in paragraph-like content are considered as real line breaks](https://help.github.com/articles/writing-on-github/); unfortunately this other algorithm is *also* called GitHub rendered markdown. (Yes, it'd be better if there were standard different names -for different things.) +Beware - this is *not* the same markdown algorithm used by GitHub when it renders issue or pull comments; in those cases [newlines in paragraph-like content are considered as real line breaks](https://help.github.com/articles/writing-on-github/); unfortunately this other algorithm is *also* called GitHub rendered markdown. (Yes, it'd be better if there were standard different names for different things.) In your markdown, please don't use tab characters and avoid "bare" URLs (in a hypertext link, the link text and URL should be on the same line). We do not care about the line length in markdown texts (and more often than not put the whole paragraph into one line). This is actually negotiable, and absolutely not enforceable. If you want to fit in a 70-character limit, go ahead, just don't reformat the whole text along the way. Take care to not break URLs when breaking lines. Do not use trailing two spaces for line breaks, since these cannot be seen and may be silently removed by some tools. Instead, use <tt><br /></tt> (an HTML break). -## Code changes - -The code should strive to be DRY (don't repeat yourself), clear, and obviously correct. -Some technical debt is inevitable, just don't bankrupt us with it. -Improved refactorizations are welcome. - -Below are guidelines for specific languages. - -### Qt-flavoured C++ - -This is our primary language. We don't have a particular code style _as of yet_ but some rules-of-thumb are below: -* 4-space indents, no tabs, no trailing spaces, no last empty lines. If you spot abusing code in the existing code base - we'll thank you for fixing it. -* A notable deviation from the usual Qt code format conventions is an extra indent inside _classes_ (access specifiers go at +4 spaces to the base, members at +8 spaces) but not _structs_ (members at +4 spaces). -* While we are at it - please don't make hypocritical structs with protected or private members. Just do them classes instead. -* - -### Automated tests - -There's no testing framework as of now; either Catch or QTest will be used eventually (PRs welcome, just don't expect a quick merge of one - we'll hunt you down to actually write some tests first :-D ). - -### Security, privacy, and performance - -Pay attention to security, and work *with* (not against) the usual security hardening mechanisms (however few in C++). `char *` and similar unchecked C-style read/write arrays are forbidden - use Qt containers or at the very least std::array<> instead. -Protect private information, in particular passwords and email addresses. Do not forget about local access to data (in particular, be very careful when storing something in temporary files, let alone permanent configuration or state). -Avoid mechanisms that could be used for tracking where possible -(we do need to verify people are logged in for some operations), -and ensure that third parties can't use interactions for tracking. - -We want the software to have decent performance for typical users. And the same time we keep libqmatrixclient single-threaded as much as possible, to keep the code simple. That means being cautious about operation complexity (read about big-O notation if you need a kickstart on the topic). This especially refers to operations on the whole timeline - it can easily be several thousands elements long so even operations with linear complexity, if heavy enough, can produce noticeable GUI freezing. -Having said that, there's always a trade-off between various attributes; in particular, readability and maintainability of the code is more important than squeezing every bit out of that clumsy algorithm. - ## How to check proposed changes before submitting them Checking the code on at least one configuration is essential; if you only have a hasty fix that doesn't even compile, better make an issue and put a link to your commit into it (with an explanation what it is about and why). ### Standard checks --Wall -pedantic is used with GCC and Clang. We don't turn those warnings to errors but please treat them as such. +`-Wall -pedantic` is used with GCC and Clang. We don't turn those warnings to errors but please treat them as such. ### Continuous Integration @@ -192,9 +194,9 @@ Regardless of the above paragraph (and as mentioned earlier in the text), we're Some cases need additional explanation: * Before rolling out your own super-optimised container or algorithm written from scratch, take a good long look through documentation on Qt and C++ standard library (including the experimental/future sections). Please try to reuse the existing facilities as much as possible. -* You should have a very good reason (or better several ones) to add a component from KDE Frameworks. We don't rule this out and there's no prejudice against KDE; it just so happened that KDE Frameworks is one of most obvious reuse candidates for us but so far none of these components survived as libqmatrixclient deps. +* You should have a good reason (or better several ones) to add a component from KDE Frameworks. We don't rule this out and there's no prejudice against KDE; it just so happened that KDE Frameworks is one of most obvious reuse candidates for us but so far none of these components survived as libqmatrixclient deps. * Never forget that libqmatrixclient is aimed to be a non-visual library; QtGui in dependencies is only driven by (entirely offscreen) dealing with QPixmaps. While there's a bunch of visual code (in C++ and QML) shared between libqmatrixclient-enabled _applications_, this is likely to end up in a separate (libqmatrixclient-enabled) library, rather than libqmatrixclient. ## Attribution -This text is largely based on CONTRIBUTING.md from CII Best Practices Badge project, which is a collective work of its contributors. The text itself is licensed under CC-BY-4.0. +This text is largely based on CONTRIBUTING.md from CII Best Practices Badge project, which is a collective work of its contributors (many thanks!). The text itself is licensed under CC-BY-4.0. @@ -13,19 +13,21 @@ You can find authors of libqmatrixclient in the Matrix room: [#qmatrixclient:mat You can also file issues at [the project's issue tracker](https://github.com/QMatrixClient/libqmatrixclient/issues). If you have what looks like a security issue, please see respective instructions in CONTRIBUTING.md. ## Building and usage -So far the library is typically used as a git submodule of another project (such as Quaternion); however it can be built separately (both as a static and as a dynamic library). There is no specific installation sequence outside of other projects but since it's a CMake-based project, your mileage should be fairly short (in case it's not, issues and PRs are most welcome). +So far the library is typically used as a git submodule of another project (such as Quaternion); however it can be built separately (either as a static or as a dynamic library). There is no installation sequence outside of other projects so far (PRs are most welcome). -The source code is hosted at GitHub: https://github.com/QMatrixClient/libqmatrixclient - checking out a certain commit or tag from GitHub (rather than downloading the archive) is the recommended way for packagers. +The source code is hosted at GitHub: https://github.com/QMatrixClient/libqmatrixclient - checking out a certain commit or tag from GitHub (rather than downloading the archive) is the recommended way for one-off building. If you want to hack on the library as a part of another project (e.g. Quaternion), you're advised to make a recursive check out of that project and update the library submodule to its master branch. There are very few tags so far; there will be more, as new versions are released. ## Pre-requisites - a Linux, MacOS or Windows system (desktop versions tried; mobile Linux/Windows might work too) -- a Git client (to check out this repo) -- Qt 5 (either Open Source or Commercial), version 5.2.1 or higher as of this writing (check `CMakeLists.txt` for most up-to-date information) -- qmake (from the Qt 5 installation) or CMake (from your package management system or [the official website](https://cmake.org/download/)). +- a Linux, OSX or Windows system (desktop versions tried; mobile Linux/Windows might work too) + - For Ubuntu flavours - zesty or later (or a derivative) is good enough out of the box; older ones will need PPAs at least for a newer Qt; in particular, if you have xenial you're advised to add Kubuntu Backports PPA for it +- a Git client to check out this repo +- CMake (from your package management system or [the official website](https://cmake.org/download/)) +- Qt 5 (either Open Source or Commercial), version 5.6 or higher - a C++ toolchain supported by your version of Qt (see a link for your platform at [the Qt's platform requirements page](http://doc.qt.io/qt-5/gettingstarted.html#platform-requirements)) - - GCC 4.8, Clang 3.5.0, Visual C++ 2015 are the oldest officially supported as of this writing + - GCC 5 (Windows, Linux, OSX), Clang 5 (Linux), Apple Clang 8.1 (OSX) and Visual C++ 2015 (Windows) are the oldest officially supported + - any build system that works with CMake should be fine: GNU Make, ninja (any platform), NMake, jom (Windows) are known to work. #### Linux Just install things from the list above using your preferred package manager. If your Qt package base is fine-grained you might want to take a look at `CMakeLists.txt` to figure out which specific libraries libqmatrixclient uses (or blindly run cmake and look at error messages). The library is entirely offscreen (Qt::Core and Qt::Network are essential) but it also depends on Qt::Gui in order to operate with avatar thumbnails. @@ -34,7 +36,7 @@ Just install things from the list above using your preferred package manager. If `brew install qt5` should get you Qt5. If you plan to use CMake, you may need to tell it about the path to Qt by passing `-DCMAKE_PREFIX_PATH=<where-Qt-installed>` #### Windows -1. Install Qt5, using their official installer. If for some reason you need to use Qt 5.2.1, select its Add-ons component in the installer as well; for later versions, no extras are needed. If you don't have a toolchain and/or IDE, you can easily get one by selecting Qt Creator and at least one toolchain under Qt Creator. Qt 5.3 is recommended on Windows; `windeployqt` in Qt 5.2.1 is not functional enough to generate a proper list of files for installing. +1. Install Qt5, using their official installer. 1. If you plan to build with CMake, install CMake; if you're ok with qmake, you don't need to install anything on top of Qt. The commands in further sections imply that cmake/qmake is in your PATH - otherwise you have to prepend those commands with actual paths. As an option, it's a good idea to run a `qtenv2.bat` script that can be found in `C:\Qt\<Qt version>\<toolchain>\bin` (assuming you installed Qt to `C:\Qt`); the only thing it does is adding necessary paths to PATH. You might not want to run that script on system startup but it's very handy to setup the environment before building. For CMake, setting `CMAKE_PREFIX_PATH` in the same way as for OS X (see above), also helps. There are no official MinGW-based 64-bit packages for Qt. If you're determined to build a 64-bit library, either use a Visual Studio toolchain or build Qt5 yourself as described in Qt documentation. @@ -74,7 +76,7 @@ libqmatrixclient uses Qt's logging categories to make switching certain types of libqmatrixclient.<category>.<level>=<flag> ``` where -- `<category>` is something like `main`, `jobs`, or `events` (the full list is in the file `debug.cpp`) +- `<category>` is one of: `main`, `jobs`, `jobs.sync`, `events`, `events.ephemeral`, and `profiler` (you can always find the full list in the file `logging.cpp`) - `<level>` is one of `debug` and `warning` - `<flag>` is either `true` or `false`. @@ -24,8 +24,38 @@ using namespace QMatrixClient; +class Avatar::Private +{ + public: + Private(Connection* c, QIcon di) : _connection(c), _defaultIcon(di) { } + QPixmap get(int width, int height, Avatar::notifier_t notifier); + + Connection* _connection; + const QIcon _defaultIcon; + QUrl _url; + QPixmap _originalPixmap; + + std::vector<QPair<QSize, QPixmap>> _scaledPixmaps; + + QSize _requestedSize; + bool _valid = false; + MediaThumbnailJob* _ongoingRequest = nullptr; + std::vector<notifier_t> notifiers; +}; + +Avatar::Avatar(Connection* connection, QIcon defaultIcon) + : d(new Private { connection, std::move(defaultIcon) }) +{ } + +Avatar::~Avatar() = default; + QPixmap Avatar::get(int width, int height, Avatar::notifier_t notifier) { + return d->get(width, height, notifier); +} + +QPixmap Avatar::Private::get(int width, int height, Avatar::notifier_t notifier) +{ QSize size(width, height); // FIXME: Alternating between longer-width and longer-height requests @@ -73,12 +103,15 @@ QPixmap Avatar::get(int width, int height, Avatar::notifier_t notifier) return pixmap; } +QUrl Avatar::url() const { return d->_url; } + bool Avatar::updateUrl(const QUrl& newUrl) { - if (newUrl == _url) + if (newUrl == d->_url) return false; - _url = newUrl; - _valid = false; + d->_url = newUrl; + d->_valid = false; return true; } + @@ -31,28 +31,18 @@ namespace QMatrixClient class Avatar { public: - explicit Avatar(Connection* connection, QIcon defaultIcon = {}) - : _defaultIcon(std::move(defaultIcon)), _connection(connection) - { } + explicit Avatar(Connection* connection, QIcon defaultIcon = {}); + ~Avatar(); using notifier_t = std::function<void()>; QPixmap get(int w, int h, notifier_t notifier); - QUrl url() const { return _url; } + QUrl url() const; bool updateUrl(const QUrl& newUrl); private: - QUrl _url; - QPixmap _originalPixmap; - QIcon _defaultIcon; - - std::vector<QPair<QSize, QPixmap>> _scaledPixmaps; - - QSize _requestedSize; - bool _valid = false; - Connection* _connection; - MediaThumbnailJob* _ongoingRequest = nullptr; - std::vector<notifier_t> notifiers; + class Private; + QScopedPointer<Private> d; }; } // namespace QMatrixClient diff --git a/connection.cpp b/connection.cpp index be4a712a..1f554974 100644 --- a/connection.cpp +++ b/connection.cpp @@ -23,8 +23,8 @@ #include "room.h" #include "jobs/generated/login.h" #include "jobs/generated/logout.h" +#include "jobs/generated/receipts.h" #include "jobs/sendeventjob.h" -#include "jobs/postreceiptjob.h" #include "jobs/joinroomjob.h" #include "jobs/roommessagesjob.h" #include "jobs/syncjob.h" @@ -164,7 +164,6 @@ void Connection::doConnectToServer(const QString& user, const QString& password, deviceId, initialDeviceName); connect(loginJob, &BaseJob::success, this, [=] { - setHomeserver(loginJob->homeServer()); d->connectWithToken(loginJob->userId(), loginJob->accessToken(), loginJob->deviceId()); }); @@ -223,7 +222,7 @@ void Connection::checkAndConnect(const QString& userId, void Connection::logout() { auto job = callApi<LogoutJob>(); - connect( job, &LogoutJob::success, [=] { + connect( job, &LogoutJob::success, this, [=] { stopSync(); emit loggedOut(); }); @@ -279,12 +278,15 @@ void Connection::postMessage(Room* room, const QString& type, const QString& mes PostReceiptJob* Connection::postReceipt(Room* room, RoomEvent* event) const { - return callApi<PostReceiptJob>(room->id(), event->id()); + return callApi<PostReceiptJob>(room->id(), "m.read", event->id()); } JoinRoomJob* Connection::joinRoom(const QString& roomAlias) { - return callApi<JoinRoomJob>(roomAlias); + auto job = callApi<JoinRoomJob>(roomAlias); + connect(job, &JoinRoomJob::success, + this, [=] { provideRoom(job->roomId(), JoinState::Join); }); + return job; } void Connection::leaveRoom(Room* room) @@ -382,7 +384,7 @@ QString Connection::token() const return accessToken(); } -QString Connection::accessToken() const +QByteArray Connection::accessToken() const { return d->data->accessToken(); } @@ -444,7 +446,6 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) return nullptr; } d->roomMap.insert(roomKey, room); - qCDebug(MAIN) << "Created Room" << id << ", invited:" << roomKey.second; emit newRoom(room); } if (joinState == JoinState::Invite) @@ -487,11 +488,11 @@ QByteArray Connection::generateTxnId() void Connection::setHomeserver(const QUrl& url) { - if (d->data->baseUrl() == url) + if (homeserver() == url) return; d->data->setBaseUrl(url); - emit homeserverChanged(url); + emit homeserverChanged(homeserver()); } static constexpr int CACHE_VERSION_MAJOR = 1; diff --git a/connection.h b/connection.h index ecebb2e7..eccde170 100644 --- a/connection.h +++ b/connection.h @@ -86,7 +86,7 @@ namespace QMatrixClient Q_INVOKABLE QString deviceId() const; /** @deprecated Use accessToken() instead. */ Q_INVOKABLE QString token() const; - Q_INVOKABLE QString accessToken() const; + Q_INVOKABLE QByteArray accessToken() const; Q_INVOKABLE SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/converters.h b/converters.h index 9b78eb37..0d7f734e 100644 --- a/converters.h +++ b/converters.h @@ -46,7 +46,7 @@ namespace QMatrixClient inline QJsonValue toJson(const QByteArray& bytes) { - return QJsonValue(static_cast<const char*>(bytes)); + return QJsonValue(bytes.constData()); } template <typename T> diff --git a/events/event.cpp b/events/event.cpp index 44b742c1..e74bc114 100644 --- a/events/event.cpp +++ b/events/event.cpp @@ -24,6 +24,7 @@ #include "roomavatarevent.h" #include "typingevent.h" #include "receiptevent.h" +#include "redactionevent.h" #include "logging.h" #include <QtCore/QJsonDocument> @@ -33,13 +34,16 @@ using namespace QMatrixClient; Event::Event(Type type, const QJsonObject& rep) : _type(type), _originalJson(rep) { - if (!rep.contains("content")) + if (!rep.contains("content") && + !rep.value("unsigned").toObject().contains("redacted_because")) { qCWarning(EVENTS) << "Event without 'content' node"; qCWarning(EVENTS) << formatJson << rep; } } +Event::~Event() = default; + QByteArray Event::originalJson() const { return QJsonDocument(_originalJson).toJson(); @@ -70,23 +74,25 @@ inline BaseEventT* makeIfMatches(const QJsonObject& o, const QString& selector) return makeIfMatches<BaseEventT, EventTs...>(o, selector); } -Event* Event::fromJson(const QJsonObject& obj) +template <> +EventPtr _impl::doMakeEvent<Event>(const QJsonObject& obj) { // Check more specific event types first - if (auto e = RoomEvent::fromJson(obj)) - return e; + if (auto e = doMakeEvent<RoomEvent>(obj)) + return EventPtr(move(e)); - return makeIfMatches<Event, - TypingEvent, ReceiptEvent>(obj, obj["type"].toString()); + return EventPtr { makeIfMatches<Event, + TypingEvent, ReceiptEvent>(obj, obj["type"].toString()) }; } +RoomEvent::RoomEvent(Event::Type type) : Event(type) { } + RoomEvent::RoomEvent(Type type, const QJsonObject& rep) : Event(type, rep), _id(rep["event_id"].toString()) - , _serverTimestamp( - QMatrixClient::fromJson<QDateTime>(rep["origin_server_ts"])) , _roomId(rep["room_id"].toString()) , _senderId(rep["sender"].toString()) - , _txnId(rep["unsigned"].toObject().value("transactionId").toString()) + , _serverTimestamp( + QMatrixClient::fromJson<QDateTime>(rep["origin_server_ts"])) { // if (_id.isEmpty()) // { @@ -103,20 +109,38 @@ RoomEvent::RoomEvent(Type type, const QJsonObject& rep) // qCWarning(EVENTS) << "Can't find sender in a room event"; // qCWarning(EVENTS) << formatJson << rep; // } + auto unsignedData = rep["unsigned"].toObject(); + auto redaction = unsignedData.value("redacted_because"); + if (redaction.isObject()) + { + _redactedBecause.reset(new RedactionEvent(redaction.toObject())); + return; + } + + _txnId = unsignedData.value("transactionId").toString(); if (!_txnId.isEmpty()) qCDebug(EVENTS) << "Event transactionId:" << _txnId; } +RoomEvent::~RoomEvent() = default; // Let the smart pointer do its job + +QString RoomEvent::redactionReason() const +{ + return isRedacted() ? _redactedBecause->reason() : QString{}; +} + void RoomEvent::addId(const QString& id) { Q_ASSERT(_id.isEmpty()); Q_ASSERT(!id.isEmpty()); _id = id; } -RoomEvent* RoomEvent::fromJson(const QJsonObject& obj) +template <> +RoomEventPtr _impl::doMakeEvent(const QJsonObject& obj) { - return makeIfMatches<RoomEvent, + return RoomEventPtr { makeIfMatches<RoomEvent, RoomMessageEvent, RoomNameEvent, RoomAliasesEvent, RoomCanonicalAliasEvent, RoomMemberEvent, RoomTopicEvent, - RoomAvatarEvent, EncryptionEvent>(obj, obj["type"].toString()); + RoomAvatarEvent, EncryptionEvent, RedactionEvent> + (obj, obj["type"].toString()) }; } diff --git a/events/event.h b/events/event.h index cc99b57b..d4923f1f 100644 --- a/events/event.h +++ b/events/event.h @@ -25,8 +25,19 @@ #include "util.h" +#include <memory> + namespace QMatrixClient { + template <typename EventT> + using event_ptr_tt = std::unique_ptr<EventT>; + + namespace _impl + { + template <typename EventT> + event_ptr_tt<EventT> doMakeEvent(const QJsonObject& obj); + } + class Event { Q_GADGET @@ -37,17 +48,19 @@ namespace QMatrixClient Typing, Receipt, RoomEventBase = 0x1000, RoomMessage = RoomEventBase + 1, - RoomEncryptedMessage, + RoomEncryptedMessage, Redaction, RoomStateEventBase = 0x1800, RoomName = RoomStateEventBase + 1, RoomAliases, RoomCanonicalAlias, RoomMember, RoomTopic, - RoomAvatar, RoomEncryption, + RoomAvatar, RoomEncryption, RoomCreate, RoomJoinRules, + RoomPowerLevels, Reserved = 0x2000 }; explicit Event(Type type) : _type(type) { } Event(Type type, const QJsonObject& rep); Event(const Event&) = delete; + virtual ~Event(); Type type() const { return _type; } bool isStateEvent() const @@ -63,12 +76,6 @@ namespace QMatrixClient // (and in most cases it will be a combination of other fields // instead of "content" field). - /** Create an event with proper type from a JSON object - * Use this factory to detect the type from the JSON object contents - * and create an event object of that type. - */ - static Event* fromJson(const QJsonObject& obj); - protected: const QJsonObject contentJson() const; @@ -81,28 +88,67 @@ namespace QMatrixClient Q_PROPERTY(QJsonObject contentJson READ contentJson CONSTANT) }; using EventType = Event::Type; + using EventPtr = event_ptr_tt<Event>; + /** Create an event with proper type from a JSON object + * Use this factory template to detect the type from the JSON object + * contents (the detected event type should derive from the template + * parameter type) and create an event object of that type. + */ template <typename EventT> - class EventsBatch : public std::vector<EventT*> + event_ptr_tt<EventT> makeEvent(const QJsonObject& obj) + { + auto e = _impl::doMakeEvent<EventT>(obj); + if (!e) + e.reset(new EventT(EventType::Unknown, obj)); + return e; + } + + namespace _impl + { + template <> + EventPtr doMakeEvent<Event>(const QJsonObject& obj); + } + + /** + * \brief A vector of pointers to events with deserialisation capabilities + * + * This is a simple wrapper over a generic vector type that adds + * a convenience method to deserialise events from QJsonArray. + * \tparam EventT base type of all events in the vector + */ + template <typename EventT> + class EventsBatch : public std::vector<event_ptr_tt<EventT>> { public: + /** + * \brief Deserialise events from an array + * + * Given the following JSON construct, creates events from + * the array stored at key "node": + * \code + * "container": { + * "node": [ { "event_id": "!evt1:srv.org", ... }, ... ] + * } + * \endcode + * \param container - the wrapping JSON object + * \param node - the key in container that holds the array of events + */ void fromJson(const QJsonObject& container, const QString& node) { const auto objs = container.value(node).toArray(); - using size_type = typename std::vector<EventT*>::size_type; + using size_type = typename std::vector<event_ptr_tt<EventT>>::size_type; // The below line accommodates the difference in size types of // STL and Qt containers. this->reserve(static_cast<size_type>(objs.size())); for (auto objValue: objs) - { - const auto o = objValue.toObject(); - auto e = EventT::fromJson(o); - this->push_back(e ? e : new EventT(EventType::Unknown, o)); - } + this->emplace_back(makeEvent<EventT>(objValue.toObject())); } }; using Events = EventsBatch<Event>; + class RedactionEvent; + /** This class corresponds to m.room.* events */ class RoomEvent : public Event { @@ -111,15 +157,26 @@ namespace QMatrixClient Q_PROPERTY(QDateTime timestamp READ timestamp CONSTANT) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString senderId READ senderId CONSTANT) - Q_PROPERTY(QString transactionId READ transactionId CONSTANT) + Q_PROPERTY(QString redactionReason READ redactionReason) + Q_PROPERTY(bool isRedacted READ isRedacted) + Q_PROPERTY(QString transactionId READ transactionId) public: - explicit RoomEvent(Type type) : Event(type) { } + // RedactionEvent is an incomplete type here so we cannot inline + // constructors and destructors + explicit RoomEvent(Type type); RoomEvent(Type type, const QJsonObject& rep); + ~RoomEvent(); const QString& id() const { return _id; } const QDateTime& timestamp() const { return _serverTimestamp; } const QString& roomId() const { return _roomId; } const QString& senderId() const { return _senderId; } + bool isRedacted() const { return bool(_redactedBecause); } + const RedactionEvent* redactedBecause() const + { + return _redactedBecause.get(); + } + QString redactionReason() const; const QString& transactionId() const { return _txnId; } /** @@ -143,17 +200,45 @@ namespace QMatrixClient */ void addId(const QString& id); - // "Static override" of the one in Event - static RoomEvent* fromJson(const QJsonObject& obj); - private: QString _id; - QDateTime _serverTimestamp; QString _roomId; QString _senderId; + QDateTime _serverTimestamp; + event_ptr_tt<RedactionEvent> _redactedBecause; QString _txnId; }; using RoomEvents = EventsBatch<RoomEvent>; + using RoomEventPtr = event_ptr_tt<RoomEvent>; + + namespace _impl + { + template <> + RoomEventPtr doMakeEvent<RoomEvent>(const QJsonObject& obj); + } + + /** + * Conceptually similar to QStringView (but much more primitive), it's a + * simple abstraction over a pair of RoomEvents::const_iterator values + * referring to the beginning and the end of a range in a RoomEvents + * container. + */ + struct RoomEventsRange + { + RoomEvents::iterator from; + RoomEvents::iterator to; + + RoomEvents::size_type size() const + { + Q_ASSERT(std::distance(from, to) >= 0); + return RoomEvents::size_type(std::distance(from, to)); + } + bool empty() const { return from == to; } + RoomEvents::const_iterator begin() const { return from; } + RoomEvents::const_iterator end() const { return to; } + RoomEvents::iterator begin() { return from; } + RoomEvents::iterator end() { return to; } + }; template <typename ContentT> class StateEvent: public RoomEvent diff --git a/events/receiptevent.cpp b/events/receiptevent.cpp index b36ddb23..e30fe4e4 100644 --- a/events/receiptevent.cpp +++ b/events/receiptevent.cpp @@ -56,15 +56,15 @@ ReceiptEvent::ReceiptEvent(const QJsonObject& obj) continue; } const QJsonObject reads = eventIt.value().toObject().value("m.read").toObject(); - std::vector<Receipt> receipts; - receipts.reserve(static_cast<size_t>(reads.size())); + QVector<Receipt> receipts; + receipts.reserve(reads.size()); for( auto userIt = reads.begin(); userIt != reads.end(); ++userIt ) { const QJsonObject user = userIt.value().toObject(); receipts.push_back({userIt.key(), QMatrixClient::fromJson<QDateTime>(user["ts"])}); } - _eventsWithReceipts.push_back({eventIt.key(), receipts}); + _eventsWithReceipts.push_back({eventIt.key(), std::move(receipts)}); } static const auto UnreadMsgsKey = QStringLiteral("x-qmatrixclient.unread_messages"); diff --git a/events/receiptevent.h b/events/receiptevent.h index 15fdf946..9494c7c6 100644 --- a/events/receiptevent.h +++ b/events/receiptevent.h @@ -30,9 +30,9 @@ namespace QMatrixClient struct ReceiptsForEvent { QString evtId; - std::vector<Receipt> receipts; + QVector<Receipt> receipts; }; - using EventsWithReceipts = std::vector<ReceiptsForEvent>; + using EventsWithReceipts = QVector<ReceiptsForEvent>; class ReceiptEvent: public Event { diff --git a/events/redactionevent.cpp b/events/redactionevent.cpp new file mode 100644 index 00000000..bf467718 --- /dev/null +++ b/events/redactionevent.cpp @@ -0,0 +1 @@ +#include "redactionevent.h" diff --git a/events/redactionevent.h b/events/redactionevent.h new file mode 100644 index 00000000..fa6902ab --- /dev/null +++ b/events/redactionevent.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (C) 2017 Kitsune Ral <kitsune-ral@users.sf.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "event.h" + +namespace QMatrixClient +{ + class RedactionEvent : public RoomEvent + { + public: + static constexpr const char* const TypeId = "m.room.redaction"; + + RedactionEvent(const QJsonObject& obj) + : RoomEvent(Type::Redaction, obj) + , _redactedEvent(obj.value("redacts").toString()) + , _reason(contentJson().value("reason").toString()) + { } + + const QString& redactedEvent() const { return _redactedEvent; } + const QString& reason() const { return _reason; } + + private: + QString _redactedEvent; + QString _reason; + }; +} // namespace QMatrixClient diff --git a/events/roommessageevent.cpp b/events/roommessageevent.cpp index f06474e9..bc41abf6 100644 --- a/events/roommessageevent.cpp +++ b/events/roommessageevent.cpp @@ -58,7 +58,6 @@ QString msgTypeToJson(MsgType enumType) if (it != msgTypes.end()) return it->jsonType; - qCCritical(EVENTS) << "Unknown msgtype:" << enumType; return {}; } @@ -69,8 +68,7 @@ MsgType jsonToMsgType(const QString& jsonType) if (it != msgTypes.end()) return it->enumType; - qCCritical(EVENTS) << "Unknown msgtype:" << jsonType; - return {}; + return MsgType::Unknown; } RoomMessageEvent::RoomMessageEvent(const QString& plainBody, @@ -81,6 +79,8 @@ RoomMessageEvent::RoomMessageEvent(const QString& plainBody, RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) : RoomEvent(Type::RoomMessage, obj), _content(nullptr) { + if (isRedacted()) + return; const QJsonObject content = contentJson(); if ( content.contains("msgtype") && content.contains("body") ) { diff --git a/examples/qmc-example.cpp b/examples/qmc-example.cpp index dc0c94e4..dbb9912b 100644 --- a/examples/qmc-example.cpp +++ b/examples/qmc-example.cpp @@ -15,15 +15,21 @@ void onNewRoom(Room* r) { cout << "New room: " << r->id().toStdString() << endl; QObject::connect(r, &Room::namesChanged, [=] { - cout << "Room " << r->id().toStdString() << ", name(s) changed:" << endl; - cout << " Name: " << r->name().toStdString() << endl; - cout << " Canonical alias: " << r->canonicalAlias().toStdString() << endl; + cout << "Room " << r->id().toStdString() << ", name(s) changed:" << endl + << " Name: " << r->name().toStdString() << endl + << " Canonical alias: " << r->canonicalAlias().toStdString() + << endl << endl; }); - QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEvents evs) { - cout << "New events in room " << r->id().toStdString() << ":" << endl; - for (auto e: evs) + QObject::connect(r, &Room::aboutToAddNewMessages, [=] (RoomEventsRange timeline) { + cout << timeline.size() << " new event(s) in room " + << r->id().toStdString() << ":" << endl; + for (const auto& item: timeline) { - cout << string(e->originalJson()) << endl; + cout << "From: " + << r->roomMembername(item->senderId()).toStdString() + << endl << "Timestamp:" + << item->timestamp().toString().toStdString() << endl + << "JSON:" << endl << string(item->originalJson()) << endl; } }); } @@ -36,7 +42,7 @@ int main(int argc, char* argv[]) auto conn = new Connection(QUrl("https://matrix.org")); conn->connectToServer(argv[1], argv[2], "QMatrixClient example application"); - QObject::connect(conn, &Connection::connected, [=] { + auto c = QObject::connect(conn, &Connection::connected, [=] { cout << "Connected" << endl; conn->sync(); }); diff --git a/jobs/passwordlogin.cpp b/jobs/passwordlogin.cpp index 9af025e6..8abfe66a 100644 --- a/jobs/passwordlogin.cpp +++ b/jobs/passwordlogin.cpp @@ -29,18 +29,15 @@ class PasswordLogin::Private }; PasswordLogin::PasswordLogin(QString user, QString password) - : BaseJob(HttpVerb::Post, "PasswordLogin" - , "_matrix/client/r0/login" - , Query() - , Data( - { { "type", QStringLiteral("m.login.password") } - , { "user", user } - , { "password", password } - }) - , false - ) + : BaseJob(HttpVerb::Post, "PasswordLogin", + "_matrix/client/r0/login", Query(), Data(), false) , d(new Private) { + QJsonObject _data; + _data.insert("type", QStringLiteral("m.login.password")); + _data.insert("user", user); + _data.insert("password", password); + setRequestData(_data); } PasswordLogin::~PasswordLogin() diff --git a/jobs/roommessagesjob.cpp b/jobs/roommessagesjob.cpp index 9af1b3a6..e5568f17 100644 --- a/jobs/roommessagesjob.cpp +++ b/jobs/roommessagesjob.cpp @@ -23,7 +23,7 @@ using namespace QMatrixClient; class RoomMessagesJob::Private { public: - Owning<RoomEvents> events; + RoomEvents events; QString end; }; @@ -46,9 +46,9 @@ RoomMessagesJob::~RoomMessagesJob() delete d; } -RoomEvents RoomMessagesJob::releaseEvents() +RoomEvents&& RoomMessagesJob::releaseEvents() { - return d->events.release(); + return move(d->events); } QString RoomMessagesJob::end() const diff --git a/jobs/roommessagesjob.h b/jobs/roommessagesjob.h index 9680d52c..7b3fd9c9 100644 --- a/jobs/roommessagesjob.h +++ b/jobs/roommessagesjob.h @@ -34,7 +34,7 @@ namespace QMatrixClient FetchDirection dir = FetchDirection::Backward); virtual ~RoomMessagesJob(); - RoomEvents releaseEvents(); + RoomEvents&& releaseEvents(); QString end() const; protected: diff --git a/jobs/syncjob.h b/jobs/syncjob.h index 08bd773e..aed36e0b 100644 --- a/jobs/syncjob.h +++ b/jobs/syncjob.h @@ -30,7 +30,7 @@ namespace QMatrixClient { public: template <typename EventT> - class Batch : public Owning<EventsBatch<EventT>> + class Batch : public EventsBatch<EventT> { public: explicit Batch(QString k) : jsonKey(std::move(k)) { } @@ -58,12 +58,9 @@ namespace QMatrixClient SyncRoomData(const QString& roomId, JoinState joinState_, const QJsonObject& room_); + SyncRoomData(SyncRoomData&&) = default; + SyncRoomData& operator=(SyncRoomData&&) = default; }; -} // namespace QMatrixClient -Q_DECLARE_TYPEINFO(QMatrixClient::SyncRoomData, Q_MOVABLE_TYPE); - -namespace QMatrixClient -{ // QVector cannot work with non-copiable objects, std::vector can. using SyncDataList = std::vector<SyncRoomData>; diff --git a/libqmatrixclient.pri b/libqmatrixclient.pri index 86648860..49442197 100644 --- a/libqmatrixclient.pri +++ b/libqmatrixclient.pri @@ -18,6 +18,7 @@ HEADERS += \ $$PWD/events/roomavatarevent.h \ $$PWD/events/typingevent.h \ $$PWD/events/receiptevent.h \ + $$PWD/events/redactionevent.h \ $$PWD/jobs/basejob.h \ $$PWD/jobs/checkauthmethods.h \ $$PWD/jobs/passwordlogin.h \ @@ -44,6 +45,7 @@ SOURCES += \ $$PWD/events/roommemberevent.cpp \ $$PWD/events/typingevent.cpp \ $$PWD/events/receiptevent.cpp \ + $$PWD/events/redactionevent.cpp \ $$PWD/jobs/basejob.cpp \ $$PWD/jobs/checkauthmethods.cpp \ $$PWD/jobs/passwordlogin.cpp \ @@ -22,15 +22,17 @@ #include "jobs/generated/inviting.h" #include "jobs/generated/banning.h" #include "jobs/generated/leaving.h" +#include "jobs/generated/receipts.h" +#include "jobs/generated/redaction.h" #include "jobs/setroomstatejob.h" #include "events/simplestateevents.h" #include "events/roomavatarevent.h" #include "events/roommemberevent.h" #include "events/typingevent.h" #include "events/receiptevent.h" +#include "events/redactionevent.h" #include "jobs/sendeventjob.h" #include "jobs/roommessagesjob.h" -#include "jobs/postreceiptjob.h" #include "avatar.h" #include "connection.h" #include "user.h" @@ -42,6 +44,9 @@ #include <array> using namespace QMatrixClient; +using namespace std::placeholders; + +enum EventsPlacement : int { Older = -1, Newer = 1 }; class Room::Private { @@ -97,32 +102,49 @@ class Room::Private void getPreviousContent(int limit = 10); - bool isEventNotable(const RoomEvent* e) const + bool isEventNotable(const TimelineItem& ti) const { - return e->senderId() != connection->userId() && - e->type() == EventType::RoomMessage; + return !ti->isRedacted() && + ti->senderId() != connection->userId() && + ti->type() == EventType::RoomMessage; } - void appendEvent(RoomEvent* e) - { - insertEvent(e, timeline.end(), - timeline.empty() ? 0 : q->maxTimelineIndex() + 1); - } - void prependEvent(RoomEvent* e) - { - insertEvent(e, timeline.begin(), - timeline.empty() ? 0 : q->minTimelineIndex() - 1); - } + void addNewMessageEvents(RoomEvents&& events); + void addHistoricalMessageEvents(RoomEvents&& events); + + /** + * @brief Move events into the timeline + * + * Insert events into the timeline, either new or historical. + * Pointers in the original container become empty, the ownership + * is passed to the timeline container. + * @param events - the range of events to be inserted + * @param placement - position and direction of insertion: Older for + * historical messages, Newer for new ones + */ + Timeline::size_type insertEvents(RoomEventsRange&& events, + EventsPlacement placement); /** * Removes events from the passed container that are already in the timeline */ void dropDuplicateEvents(RoomEvents* events) const; + void checkUnreadMessages(timeline_iter_t from); void setLastReadEvent(User* u, const QString& eventId); rev_iter_pair_t promoteReadMarker(User* u, rev_iter_t newMarker, bool force = false); + void markMessagesAsRead(rev_iter_t upToMarker); + + /** + * @brief Apply redaction to the timeline + * + * Tries to find an event in the timeline and redact it; deletes the + * redaction event whether the redacted event was found or not. + */ + void processRedaction(RoomEventPtr redactionEvent); + QJsonObject toJson() const; private: @@ -132,8 +154,6 @@ class Room::Private void insertMemberIntoMap(User* u); void removeMemberFromMap(const QString& username, User* u); - void insertEvent(RoomEvent* e, Timeline::iterator where, - TimelineItem::index_t index); bool isLocalUser(const User* u) const { return u == connection->user(); @@ -146,7 +166,7 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) // See "Accessing the Public Class" section in // https://marcmutz.wordpress.com/translated-articles/pimp-my-pimpl-%E2%80%94-reloaded/ d->q = this; - qCDebug(MAIN) << "New Room:" << id; + qCDebug(MAIN) << "New" << toCString(initialJoinState) << "Room:" << id; } Room::~Room() @@ -251,9 +271,8 @@ Room::Private::promoteReadMarker(User* u, Room::rev_iter_t newMarker, setLastReadEvent(u, (*(eagerMarker - 1))->id()); if (isLocalUser(u) && unreadMessages) { - auto stillUnreadMessagesCount = - count_if(eagerMarker, timeline.cend(), - [=](const TimelineItem& ti) { return isEventNotable(ti.event()); }); + auto stillUnreadMessagesCount = count_if(eagerMarker, timeline.cend(), + std::bind(&Room::Private::isEventNotable, this, _1)); if (stillUnreadMessagesCount == 0) { @@ -270,20 +289,21 @@ Room::Private::promoteReadMarker(User* u, Room::rev_iter_t newMarker, return { prevMarker, newMarker }; } -void Room::markMessagesAsRead(Room::rev_iter_t upToMarker) +void Room::Private::markMessagesAsRead(Room::rev_iter_t upToMarker) { - Private::rev_iter_pair_t markers = d->promoteReadMarker(localUser(), upToMarker); + rev_iter_pair_t markers = promoteReadMarker(q->localUser(), upToMarker); if (markers.first != markers.second) - qCDebug(MAIN) << "Marked messages as read until" << *readMarker(); + qCDebug(MAIN) << "Marked messages as read until" << *q->readMarker(); // We shouldn't send read receipts for the local user's own messages - so // search earlier messages for the latest message not from the local user // until the previous last-read message, whichever comes first. for (; markers.second < markers.first; ++markers.second) { - if ((*markers.second)->senderId() != localUser()->id()) + if ((*markers.second)->senderId() != q->localUser()->id()) { - connection()->callApi<PostReceiptJob>(id(), (*markers.second)->id()); + connection->callApi<PostReceiptJob>( + id, "m.read", (*markers.second)->id()); break; } } @@ -291,13 +311,13 @@ void Room::markMessagesAsRead(Room::rev_iter_t upToMarker) void Room::markMessagesAsRead(QString uptoEventId) { - markMessagesAsRead(findInTimeline(uptoEventId)); + d->markMessagesAsRead(findInTimeline(uptoEventId)); } void Room::markAllMessagesAsRead() { if (!d->timeline.empty()) - markMessagesAsRead(d->timeline.crbegin()); + d->markMessagesAsRead(d->timeline.crbegin()); } bool Room::hasUnreadMessages() @@ -437,29 +457,40 @@ void Room::Private::removeMemberFromMap(const QString& username, User* u) emit q->memberRenamed(formerNamesakes[0]); } -inline QByteArray makeErrorStr(const Event* e, QByteArray msg) +inline QByteArray makeErrorStr(const Event& e, QByteArray msg) { - return msg.append("; event dump follows:\n").append(e->originalJson()); + return msg.append("; event dump follows:\n").append(e.originalJson()); } -void Room::Private::insertEvent(RoomEvent* e, Timeline::iterator where, - TimelineItem::index_t index) +Room::Timeline::size_type Room::Private::insertEvents(RoomEventsRange&& events, + EventsPlacement placement) { - Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); - Q_ASSERT_X(!e->id().isEmpty(), __FUNCTION__, - makeErrorStr(e, "Event with empty id cannot be in the timeline")); - Q_ASSERT_X(where == timeline.end() || where == timeline.begin(), __FUNCTION__, - "Events can only be appended or prepended to the timeline"); - if (eventsIndex.contains(e->id())) + // Historical messages arrive in newest-to-oldest order, so the process for + // them is symmetric to the one for new messages. + auto index = timeline.empty() ? -int(placement) : + placement == Older ? timeline.front().index() : + timeline.back().index(); + auto baseIndex = index; + for (auto&& e: events) { - qCWarning(MAIN) << "Event" << e->id() << "is already in the timeline."; - qCWarning(MAIN) << "Either dropDuplicateEvents() wasn't called or duplicate " - "events within the same batch arrived from the server."; - return; + const auto eId = e->id(); + Q_ASSERT_X(e, __FUNCTION__, "Attempt to add nullptr to timeline"); + Q_ASSERT_X(!eId.isEmpty(), __FUNCTION__, + makeErrorStr(*e, + "Event with empty id cannot be in the timeline")); + Q_ASSERT_X(!eventsIndex.contains(eId), __FUNCTION__, + makeErrorStr(*e, "Event is already in the timeline; " + "incoming events were not properly deduplicated")); + if (placement == Older) + timeline.emplace_front(move(e), --index); + else + timeline.emplace_back(move(e), ++index); + eventsIndex.insert(eId, index); + Q_ASSERT(q->findInTimeline(eId)->event()->id() == eId); } - timeline.emplace(where, e, index); - eventsIndex.insert(e->id(), index); - Q_ASSERT(q->findInTimeline(e->id())->event() == e); + // Pointers in "events" are empty now, but events.size() didn't change + Q_ASSERT(int(events.size()) == (index - baseIndex) * int(placement)); + return events.size(); } void Room::Private::addMember(User *u) @@ -572,15 +603,15 @@ void Room::updateData(SyncRoomData&& data) << et.elapsed() << "ms," << data.timeline.size() << "events"; et.restart(); - addNewMessageEvents(data.timeline.release()); + d->addNewMessageEvents(move(data.timeline)); qCDebug(PROFILER) << "*** Room::addNewMessageEvents():" << et.elapsed() << "ms"; } if (!data.ephemeral.empty()) { et.restart(); - for( auto ephemeralEvent: data.ephemeral ) - processEphemeralEvent(ephemeralEvent); + for( auto&& ephemeralEvent: data.ephemeral ) + processEphemeralEvent(move(ephemeralEvent)); qCDebug(PROFILER) << "*** Room::processEphemeralEvents():" << et.elapsed() << "ms"; } @@ -632,7 +663,7 @@ void Room::Private::getPreviousContent(int limit) connect( roomMessagesJob, &RoomMessagesJob::result, [=]() { if( !roomMessagesJob->error() ) { - q->addHistoricalMessageEvents(roomMessagesJob->releaseEvents()); + addHistoricalMessageEvents(roomMessagesJob->releaseEvents()); prevBatch = roomMessagesJob->end(); } roomMessagesJob = nullptr; @@ -665,17 +696,120 @@ void Room::unban(const QString& userId) connection()->callApi<UnbanJob>(id(), userId); } +void Room::redactEvent(const QString& eventId, const QString& reason) +{ + connection()->callApi<RedactEventJob>( + id(), eventId, connection()->generateTxnId(), reason); +} + void Room::Private::dropDuplicateEvents(RoomEvents* events) const { - // Collect all duplicate events at the end of the container - auto dupsBegin = - std::stable_partition(events->begin(), events->end(), - [&] (RoomEvent* e) { return !eventsIndex.contains(e->id()); }); - // Dispose of those dups - std::for_each(dupsBegin, events->end(), [] (Event* e) { delete e; }); + if (events->empty()) + return; + + // Multiple-remove (by different criteria), single-erase + // 1. Check for duplicates against the timeline. + auto dupsBegin = remove_if(events->begin(), events->end(), + [&] (const RoomEventPtr& e) + { return eventsIndex.contains(e->id()); }); + + // 2. Check for duplicates within the batch if there are still events. + for (auto eIt = events->begin(); distance(eIt, dupsBegin) > 1; ++eIt) + dupsBegin = remove_if(eIt + 1, dupsBegin, + [&] (const RoomEventPtr& e) + { return e->id() == (*eIt)->id(); }); + if (dupsBegin == events->end()) + return; + + qCDebug(EVENTS) << "Dropping" << distance(dupsBegin, events->end()) + << "duplicate event(s)"; events->erase(dupsBegin, events->end()); } +inline bool isRedaction(const RoomEventPtr& e) +{ + return e->type() == EventType::Redaction; +} + +void Room::Private::processRedaction(RoomEventPtr redactionEvent) +{ + Q_ASSERT(redactionEvent && isRedaction(redactionEvent)); + const auto& redaction = + static_cast<const RedactionEvent*>(redactionEvent.get()); + + const auto pIdx = eventsIndex.find(redaction->redactedEvent()); + if (pIdx == eventsIndex.end()) + { + qCDebug(MAIN) << "Redaction" << redaction->id() + << "ignored: target event not found"; + return; // If the target events comes later, it comes already redacted. + } + Q_ASSERT(q->isValidIndex(*pIdx)); + + auto& ti = timeline[Timeline::size_type(*pIdx - q->minTimelineIndex())]; + + // Apply the redaction procedure from chapter 6.5 of The Spec + auto originalJson = ti->originalJsonObject(); + if (originalJson.value("unsigned").toObject() + .value("redacted_because").toObject() + .value("event_id") == redaction->id()) + { + qCDebug(MAIN) << "Redaction" << redaction->id() + << "of event" << ti.event()->id() << "already done, skipping"; + return; + } + static const QStringList keepKeys = + { "event_id", "type", "room_id", "sender", "state_key", + "prev_content", "content", "origin_server_ts" }; + static const + std::vector<std::pair<EventType, QStringList>> keepContentKeysMap + { { Event::Type::RoomMember, { "membership" } } + , { Event::Type::RoomCreate, { "creator" } } + , { Event::Type::RoomJoinRules, { "join_rule" } } + , { Event::Type::RoomPowerLevels, + { "ban", "events", "events_default", "kick", "redact", + "state_default", "users", "users_default" } } + , { Event::Type::RoomAliases, { "alias" } } + }; + for (auto it = originalJson.begin(); it != originalJson.end();) + { + if (!keepKeys.contains(it.key())) + it = originalJson.erase(it); // TODO: shred the value + else + ++it; + } + auto keepContentKeys = + find_if(keepContentKeysMap.begin(), keepContentKeysMap.end(), + [&](const std::pair<EventType,QStringList>& t) + { return ti->type() == t.first; } ); + if (keepContentKeys == keepContentKeysMap.end()) + { + originalJson.remove("content"); + originalJson.remove("prev_content"); + } else { + auto content = originalJson.take("content").toObject(); + for (auto it = content.begin(); it != content.end(); ) + { + if (!keepContentKeys->second.contains(it.key())) + it = content.erase(it); + else + ++it; + } + originalJson.insert("content", content); + } + auto unsignedData = originalJson.take("unsigned").toObject(); + unsignedData["redacted_because"] = redaction->originalJsonObject(); + originalJson.insert("unsigned", unsignedData); + + // Make a new event from the redacted JSON, exchange events, + // notify everyone and delete the old event + RoomEventPtr oldEvent + { ti.replaceEvent(makeEvent<RoomEvent>(originalJson)) }; + q->onRedaction(oldEvent.get(), ti.event()); + qCDebug(MAIN) << "Redacted" << oldEvent->id() << "with" << redaction->id(); + emit q->replacedEvent(ti.event(), oldEvent.get()); +} + Connection* Room::connection() const { Q_ASSERT(d->connection); @@ -687,89 +821,106 @@ User* Room::localUser() const return connection()->user(); } -void Room::addNewMessageEvents(RoomEvents events) +void Room::Private::addNewMessageEvents(RoomEvents&& events) { - d->dropDuplicateEvents(&events); - if (events.empty()) - return; - emit aboutToAddNewMessages(events); - doAddNewMessageEvents(events); - emit addedMessages(); -} + auto timelineSize = timeline.size(); -void Room::doAddNewMessageEvents(const RoomEvents& events) -{ - Q_ASSERT(!events.empty()); + dropDuplicateEvents(&events); + // We want to process redactions in the order of arrival (covering the + // case of one redaction superseding another one), hence stable partition. + const auto normalsBegin = + stable_partition(events.begin(), events.end(), isRedaction); + RoomEventsRange redactions { events.begin(), normalsBegin }, + normalEvents { normalsBegin, events.end() }; - Timeline::size_type newUnreadMessages = 0; - for (auto e: events) + if (!normalEvents.empty()) + emit q->aboutToAddNewMessages(normalEvents); + const auto insertedSize = insertEvents(std::move(normalEvents), Newer); + if (insertedSize > 0) + { + qCDebug(MAIN) + << "Room" << displayname << "received" << insertedSize + << "new events; the last event is now" << timeline.back(); + q->onAddNewTimelineEvents(timeline.cend() - insertedSize); + } + for (auto&& r: redactions) + processRedaction(move(r)); + if (insertedSize > 0) { - d->appendEvent(e); - newUnreadMessages += d->isEventNotable(e); + checkUnreadMessages(timeline.cend() - insertedSize); + emit q->addedMessages(); } - qCDebug(MAIN) << "Room" << displayName() << "received" << events.size() - << "(with" << newUnreadMessages << "notable)" - << "new events; the last event is now" << d->timeline.back(); - - // The first event in the batch defines whose read marker can possibly be - // promoted any further over the same author's events newly arrived. - // Others will need explicit read receipts from the server (or, for - // the local user, markMessagesAsRead() invocation) to promote their - // read markers over the new message events. - User* firstWriter = connection()->user(events.front()->senderId()); - if (readMarker(firstWriter) != timelineEdge()) + + Q_ASSERT(timeline.size() == timelineSize + insertedSize); +} + +void Room::Private::checkUnreadMessages(timeline_iter_t from) +{ + Q_ASSERT(from < timeline.cend()); + const auto newUnreadMessages = count_if(from, timeline.cend(), + std::bind(&Room::Private::isEventNotable, this, _1)); + + // The first event in the just-added batch (referred to by upTo.base()) + // defines whose read marker can possibly be promoted any further over + // the same author's events newly arrived. Others will need explicit + // read receipts from the server (or, for the local user, + // markMessagesAsRead() invocation) to promote their read markers over + // the new message events. + auto firstWriter = connection->user((*from)->senderId()); + if (q->readMarker(firstWriter) != timeline.crend()) { - d->promoteReadMarker(firstWriter, findInTimeline(events.front()->id())); + promoteReadMarker(firstWriter, q->findInTimeline((*from)->id())); qCDebug(MAIN) << "Auto-promoted read marker for" << firstWriter->id() - << "to" << *readMarker(firstWriter); + << "to" << *q->readMarker(firstWriter); } - if( !d->unreadMessages && newUnreadMessages > 0) + if(!unreadMessages && newUnreadMessages > 0) { - d->unreadMessages = true; - emit unreadMessagesChanged(this); - qCDebug(MAIN) << "Room" << displayName() << "has unread messages"; + unreadMessages = true; + emit q->unreadMessagesChanged(q); + qCDebug(MAIN) << "Room" << displayname << "has unread messages"; } } -void Room::addHistoricalMessageEvents(RoomEvents events) +void Room::Private::addHistoricalMessageEvents(RoomEvents&& events) { - d->dropDuplicateEvents(&events); - if (events.empty()) - return; - emit aboutToAddHistoricalMessages(events); - doAddHistoricalMessageEvents(events); - emit addedMessages(); -} + const auto timelineSize = timeline.size(); -void Room::doAddHistoricalMessageEvents(const RoomEvents& events) -{ - Q_ASSERT(!events.empty()); + dropDuplicateEvents(&events); + const auto redactionsBegin = + remove_if(events.begin(), events.end(), isRedaction); + RoomEventsRange normalEvents { events.begin(), redactionsBegin }; + if (normalEvents.empty()) + return; - const bool thereWasNoReadMarker = readMarker() == timelineEdge(); - // Historical messages arrive in newest-to-oldest order - for (auto e: events) - d->prependEvent(e); + emit q->aboutToAddHistoricalMessages(normalEvents); + const bool thereWasNoReadMarker = q->readMarker() == timeline.crend(); + const auto insertedSize = insertEvents(std::move(normalEvents), Older); // Catch a special case when the last read event id refers to an event // that was outside the loaded timeline and has just arrived. Depending on // other messages next to the last read one, we might need to promote // the read marker and update unreadMessages flag. - const auto curReadMarker = readMarker(); - if (thereWasNoReadMarker && curReadMarker != timelineEdge()) + const auto curReadMarker = q->readMarker(); + if (thereWasNoReadMarker && curReadMarker != timeline.crend()) { qCDebug(MAIN) << "Discovered last read event in a historical batch"; - d->promoteReadMarker(localUser(), curReadMarker, true); + promoteReadMarker(q->localUser(), curReadMarker, true); } - qCDebug(MAIN) << "Room" << displayName() << "received" << events.size() - << "past events; the oldest event is now" << d->timeline.front(); + qCDebug(MAIN) << "Room" << displayname << "received" << insertedSize + << "past events; the oldest event is now" << timeline.front(); + q->onAddHistoricalTimelineEvents(timeline.crend() - insertedSize); + emit q->addedMessages(); + + Q_ASSERT(timeline.size() == timelineSize + insertedSize); } void Room::processStateEvents(const RoomEvents& events) { bool emitNamesChanged = false; - for (auto event: events) + for (const auto& e: events) { + auto* event = e.get(); switch (event->type()) { case EventType::RoomName: { @@ -835,12 +986,12 @@ void Room::processStateEvents(const RoomEvents& events) d->updateDisplayname(); } -void Room::processEphemeralEvent(Event* event) +void Room::processEphemeralEvent(EventPtr event) { switch (event->type()) { case EventType::Typing: { - auto typingEvent = static_cast<TypingEvent*>(event); + auto typingEvent = static_cast<TypingEvent*>(event.get()); d->usersTyping.clear(); for( const QString& userId: typingEvent->users() ) { @@ -851,7 +1002,7 @@ void Room::processEphemeralEvent(Event* event) break; } case EventType::Receipt: { - auto receiptEvent = static_cast<ReceiptEvent*>(event); + auto receiptEvent = static_cast<ReceiptEvent*>(event.get()); for( const auto &p: receiptEvent->eventsWithReceipts() ) { { @@ -38,22 +38,32 @@ namespace QMatrixClient class User; class MemberSorter; class LeaveRoomJob; + class RedactEventJob; + class Room; class TimelineItem { public: // For compatibility with Qt containers, even though we use - // a std:: container now + // a std:: container now for the room timeline using index_t = int; - TimelineItem(RoomEvent* e, index_t number) : evt(e), idx(number) { } + TimelineItem(RoomEventPtr&& e, index_t number) + : evt(move(e)), idx(number) { } RoomEvent* event() const { return evt.get(); } - RoomEvent* operator->() const { return event(); } //< Synonym for event() + RoomEvent* operator->() const { return evt.operator->(); } index_t index() const { return idx; } + // Used for event redaction + RoomEventPtr replaceEvent(RoomEventPtr&& other) + { + evt.swap(other); + return move(other); + } + private: - std::unique_ptr<RoomEvent> evt; + RoomEventPtr evt; index_t idx; }; inline QDebug& operator<<(QDebug& d, const TimelineItem& ti) @@ -78,6 +88,7 @@ namespace QMatrixClient public: using Timeline = std::deque<TimelineItem>; using rev_iter_t = Timeline::const_reverse_iterator; + using timeline_iter_t = Timeline::const_iterator; Room(Connection* connection, QString id, JoinState initialJoinState); ~Room() override; @@ -159,7 +170,8 @@ namespace QMatrixClient void postMessage(const QString& plainText, MessageEventType type = MessageEventType::Text); void postMessage(const RoomMessageEvent& event); - /** @deprecated */ + /** @deprecated If you have a custom event type, construct the event + * and pass it as a whole to postMessage() */ void postMessage(const QString& type, const QString& plainText); void setTopic(const QString& newTopic); @@ -170,13 +182,15 @@ namespace QMatrixClient void kickMember(const QString& memberId, const QString& reason = {}); void ban(const QString& userId, const QString& reason = {}); void unban(const QString& userId); + void redactEvent(const QString& eventId, + const QString& reason = {}); /** Mark all messages in the room as read */ void markAllMessagesAsRead(); signals: - void aboutToAddHistoricalMessages(const RoomEvents& events); - void aboutToAddNewMessages(const RoomEvents& events); + void aboutToAddHistoricalMessages(RoomEventsRange events); + void aboutToAddNewMessages(RoomEventsRange events); void addedMessages(); /** @@ -199,21 +213,20 @@ namespace QMatrixClient void lastReadEventChanged(User* user); void readMarkerMoved(); void unreadMessagesChanged(Room* room); + void replacedEvent(const RoomEvent* newEvent, + const RoomEvent* oldEvent); protected: - virtual void doAddNewMessageEvents(const RoomEvents& events); - virtual void doAddHistoricalMessageEvents(const RoomEvents& events); virtual void processStateEvents(const RoomEvents& events); - virtual void processEphemeralEvent(Event* event); + virtual void processEphemeralEvent(EventPtr event); + virtual void onAddNewTimelineEvents(timeline_iter_t from) { } + virtual void onAddHistoricalTimelineEvents(rev_iter_t from) { } + virtual void onRedaction(const RoomEvent* prevEvent, + const RoomEvent* after) { } private: class Private; Private* d; - - void addNewMessageEvents(RoomEvents events); - void addHistoricalMessageEvents(RoomEvents events); - - void markMessagesAsRead(rev_iter_t upToMarker); }; class MemberSorter diff --git a/settings.cpp b/settings.cpp index 68914642..ac9c091c 100644 --- a/settings.cpp +++ b/settings.cpp @@ -1,7 +1,8 @@ #include "settings.h"
+#include "logging.h"
+
#include <QtCore/QUrl>
-#include <QtCore/QDebug>
using namespace QMatrixClient;
@@ -19,6 +20,8 @@ void Settings::setValue(const QString& key, const QVariant& value) {
// qCDebug() << "Setting" << key << "to" << value;
QSettings::setValue(key, value);
+ if (legacySettings.contains(key))
+ legacySettings.remove(key);
}
QVariant Settings::value(const QString& key, const QVariant& defaultValue) const
@@ -133,10 +136,14 @@ QString AccountSettings::accessToken() const void AccountSettings::setAccessToken(const QString& accessToken)
{
+ qCWarning(MAIN) << "Saving access_token to QSettings is insecure."
+ " Developers, please save access_token separately.";
setValue("access_token", accessToken);
}
void AccountSettings::clearAccessToken()
{
+ legacySettings.remove("access_token");
+ legacySettings.remove("device_id"); // Force the server to re-issue it
remove("access_token");
}
@@ -59,8 +59,8 @@ namespace QMatrixClient static QString legacyApplicationName;
protected:
- const QSettings legacySettings { legacyOrganizationName,
- legacyApplicationName };
+ QSettings legacySettings { legacyOrganizationName,
+ legacyApplicationName };
};
class SettingsGroup: public Settings
@@ -94,6 +94,7 @@ namespace QMatrixClient Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName)
Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver)
Q_PROPERTY(bool keepLoggedIn READ keepLoggedIn WRITE setKeepLoggedIn)
+ /** \deprecated \sa setToken */
Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken)
public:
template <typename... ArgTs>
@@ -115,7 +116,10 @@ namespace QMatrixClient bool keepLoggedIn() const;
void setKeepLoggedIn(bool newSetting);
+ /** \deprecated \sa setToken */
QString accessToken() const;
+ /** \deprecated Storing accessToken in QSettings is unsafe,
+ * see QMatrixClient/Quaternion#181 */
void setAccessToken(const QString& accessToken);
Q_INVOKABLE void clearAccessToken();
};
@@ -26,53 +26,6 @@ namespace QMatrixClient { /** - * @brief A crude wrapper around a container of pointers that owns pointers - * to contained objects - * - * Similar to vector<unique_ptr<>>, upon deletion, this wrapper - * will delete all events contained in it. This wrapper can be used - * over Qt containers, which are incompatible with unique_ptr and even - * with QScopedPointer (which is the reason of its creation). - */ - template <typename ContainerT> - class Owning : public ContainerT - { - public: - Owning() = default; - Owning(const Owning&) = delete; - Owning(Owning&&) = default; - Owning& operator=(Owning&& other) - { - assign(other.release()); - return *this; - } - - ~Owning() { cleanup(); } - - void assign(ContainerT&& other) - { - if (&other == this) - return; - cleanup(); - ContainerT::operator=(other); - } - - /** - * @brief returns the underlying container and releases the ownership - * - * Acts similar to unique_ptr::release. - */ - ContainerT release() - { - ContainerT c; - ContainerT::swap(c); - return c; - } - private: - void cleanup() { for (auto e: *this) delete e; } - }; - - /** * @brief Lookup a value by a key in a varargs list * * This function template takes the value of its first argument (selector) |