aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Rusakov <Kitsune-Ral@users.sf.net>2022-02-13 19:25:24 +0100
committerAlexey Rusakov <Kitsune-Ral@users.sf.net>2022-02-13 19:25:24 +0100
commit044ae4a029b710571420f830a497647f7a54698a (patch)
tree14d50ba0eb1a5cfac995f13cd6059aed74e506b2
parentb0aef4af9cbf00755c7b70c71d77f0bf7ce0d200 (diff)
parent10ac7c13cdcd62b62af6e89cb726376cfbc53302 (diff)
downloadlibquotient-044ae4a029b710571420f830a497647f7a54698a.tar.gz
libquotient-044ae4a029b710571420f830a497647f7a54698a.zip
Merge branch 'dev'
The result is FTBFS as yet; next commits will fix that, along with a few other things.
-rw-r--r--.clang-format4
-rw-r--r--.github/workflows/ci.yml120
-rw-r--r--.lgtm.yml2
-rw-r--r--CMakeLists.txt201
-rw-r--r--autotests/CMakeLists.txt1
-rw-r--r--autotests/callcandidateseventtest.cpp2
-rw-r--r--autotests/utiltests.cpp45
-rw-r--r--gtad/gtad.yaml2
-rw-r--r--gtad/operation.h.mustache2
-rw-r--r--lib/accountregistry.cpp73
-rw-r--r--lib/accountregistry.h51
-rw-r--r--lib/avatar.cpp12
-rw-r--r--lib/avatar.h10
-rw-r--r--lib/connection.cpp15
-rw-r--r--lib/connection.h42
-rw-r--r--lib/connectiondata.cpp2
-rw-r--r--lib/connectiondata.h5
-rw-r--r--lib/converters.h72
-rw-r--r--lib/csapi/account-data.h8
-rw-r--r--lib/csapi/admin.h2
-rw-r--r--lib/csapi/administrative_contact.h16
-rw-r--r--lib/csapi/appservice_room_directory.h3
-rw-r--r--lib/csapi/banning.h4
-rw-r--r--lib/csapi/capabilities.h2
-rw-r--r--lib/csapi/content-repo.h12
-rw-r--r--lib/csapi/create_room.h2
-rw-r--r--lib/csapi/cross_signing.h4
-rw-r--r--lib/csapi/device_management.h10
-rw-r--r--lib/csapi/directory.h8
-rw-r--r--lib/csapi/event_context.h2
-rw-r--r--lib/csapi/filter.h4
-rw-r--r--lib/csapi/inviting.h2
-rw-r--r--lib/csapi/joining.h4
-rw-r--r--lib/csapi/keys.h8
-rw-r--r--lib/csapi/kicking.h2
-rw-r--r--lib/csapi/knocking.h2
-rw-r--r--lib/csapi/leaving.h4
-rw-r--r--lib/csapi/list_joined_rooms.h2
-rw-r--r--lib/csapi/list_public_rooms.h8
-rw-r--r--lib/csapi/login.h4
-rw-r--r--lib/csapi/logout.h4
-rw-r--r--lib/csapi/message_pagination.h2
-rw-r--r--lib/csapi/notifications.h2
-rw-r--r--lib/csapi/openid.h2
-rw-r--r--lib/csapi/peeking_events.h2
-rw-r--r--lib/csapi/presence.h4
-rw-r--r--lib/csapi/profile.h10
-rw-r--r--lib/csapi/pusher.h4
-rw-r--r--lib/csapi/pushrules.h16
-rw-r--r--lib/csapi/read_markers.h2
-rw-r--r--lib/csapi/receipts.h2
-rw-r--r--lib/csapi/redaction.h2
-rw-r--r--lib/csapi/registration.h16
-rw-r--r--lib/csapi/report_content.h2
-rw-r--r--lib/csapi/room_send.h2
-rw-r--r--lib/csapi/room_state.h2
-rw-r--r--lib/csapi/room_upgrades.h2
-rw-r--r--lib/csapi/rooms.h10
-rw-r--r--lib/csapi/search.h2
-rw-r--r--lib/csapi/sso_login_redirect.h4
-rw-r--r--lib/csapi/tags.h6
-rw-r--r--lib/csapi/third_party_lookup.h12
-rw-r--r--lib/csapi/third_party_membership.h2
-rw-r--r--lib/csapi/to_device.h2
-rw-r--r--lib/csapi/typing.h2
-rw-r--r--lib/csapi/users.h2
-rw-r--r--lib/csapi/versions.h2
-rw-r--r--lib/csapi/voip.h2
-rw-r--r--lib/csapi/wellknown.h2
-rw-r--r--lib/csapi/whoami.h2
-rw-r--r--lib/eventitem.cpp4
-rw-r--r--lib/eventitem.h11
-rw-r--r--lib/events/accountdataevents.h32
-rw-r--r--lib/events/callanswerevent.h3
-rw-r--r--lib/events/callcandidatesevent.cpp27
-rw-r--r--lib/events/callhangupevent.cpp36
-rw-r--r--lib/events/callhangupevent.h10
-rw-r--r--lib/events/callinviteevent.h2
-rw-r--r--lib/events/directchatevent.h2
-rw-r--r--lib/events/encryptedevent.h2
-rw-r--r--lib/events/encryptionevent.h26
-rw-r--r--lib/events/event.cpp20
-rw-r--r--lib/events/event.h301
-rw-r--r--lib/events/eventcontent.cpp1
-rw-r--r--lib/events/eventcontent.h25
-rw-r--r--lib/events/eventloader.h18
-rw-r--r--lib/events/eventrelation.cpp38
-rw-r--r--lib/events/eventrelation.h52
-rw-r--r--lib/events/reactionevent.cpp29
-rw-r--r--lib/events/reactionevent.h34
-rw-r--r--lib/events/receiptevent.cpp1
-rw-r--r--lib/events/receiptevent.h2
-rw-r--r--lib/events/roomavatarevent.h3
-rw-r--r--lib/events/roomcreateevent.h3
-rw-r--r--lib/events/roomevent.cpp4
-rw-r--r--lib/events/roomevent.h6
-rw-r--r--lib/events/roomkeyevent.h2
-rw-r--r--lib/events/roommemberevent.cpp9
-rw-r--r--lib/events/roommemberevent.h47
-rw-r--r--lib/events/roommessageevent.cpp127
-rw-r--r--lib/events/roommessageevent.h35
-rw-r--r--lib/events/roompowerlevelsevent.h12
-rw-r--r--lib/events/roomtombstoneevent.h3
-rw-r--r--lib/events/simplestateevents.h17
-rw-r--r--lib/events/stateevent.cpp21
-rw-r--r--lib/events/stateevent.h23
-rw-r--r--lib/events/stickerevent.h2
-rw-r--r--lib/events/typingevent.h2
-rw-r--r--lib/eventstats.h4
-rw-r--r--lib/function_traits.cpp53
-rw-r--r--lib/function_traits.h93
-rw-r--r--lib/jobs/basejob.cpp6
-rw-r--r--lib/jobs/basejob.h16
-rw-r--r--lib/jobs/downloadfilejob.cpp6
-rw-r--r--lib/jobs/downloadfilejob.h4
-rw-r--r--lib/jobs/mediathumbnailjob.h2
-rw-r--r--lib/jobs/requestdata.h4
-rw-r--r--lib/jobs/syncjob.cpp6
-rw-r--r--lib/mxcreply.cpp5
-rw-r--r--lib/mxcreply.h8
-rw-r--r--lib/networkaccessmanager.cpp6
-rw-r--r--lib/networkaccessmanager.h10
-rw-r--r--lib/networksettings.cpp6
-rw-r--r--lib/networksettings.h8
-rw-r--r--lib/omittable.h223
-rw-r--r--lib/qt_connection_util.h2
-rw-r--r--lib/quotient_common.h60
-rw-r--r--lib/quotient_export.h23
-rw-r--r--lib/room.cpp225
-rw-r--r--lib/room.h87
-rw-r--r--lib/roomstateview.cpp35
-rw-r--r--lib/roomstateview.h127
-rw-r--r--lib/settings.cpp16
-rw-r--r--lib/settings.h18
-rw-r--r--lib/ssosession.cpp44
-rw-r--r--lib/ssosession.h14
-rw-r--r--lib/syncdata.cpp20
-rw-r--r--lib/syncdata.h2
-rw-r--r--lib/uri.cpp25
-rw-r--r--lib/uri.h2
-rw-r--r--lib/uriresolver.h6
-rw-r--r--lib/user.cpp23
-rw-r--r--lib/user.h8
-rw-r--r--lib/util.cpp32
-rw-r--r--lib/util.h258
-rw-r--r--quotest/CMakeLists.txt22
-rw-r--r--quotest/quotest.cpp105
147 files changed, 2043 insertions, 1463 deletions
diff --git a/.clang-format b/.clang-format
index 6e13223e..8375204a 100644
--- a/.clang-format
+++ b/.clang-format
@@ -104,8 +104,8 @@ PenaltyBreakComment: 45
#PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 200
#PenaltyBreakTemplateDeclaration: 10
-PenaltyExcessCharacter: 20
-PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyExcessCharacter: 40
+PenaltyReturnTypeOnItsOwnLine: 150
#PointerAlignment: Left
#ReflowComments: true
#SortIncludes: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d0c391be..1d902bd3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,28 +21,36 @@ jobs:
matrix:
os: [ubuntu-20.04, macos-10.15]
compiler: [ GCC, Clang ]
- platform: [ '' ]
- qt-version: [ '5.12.10' ]
- qt-arch: [ '' ]
+ qt-version: [ '5.12.12' ]
# Not using binary values here, to make the job captions more readable
e2ee: [ '' ]
update-api: [ '', 'update-api' ]
+ sonar: [ '' ]
+ platform: [ '' ]
+ qt-arch: [ '' ]
exclude:
- os: macos-10.15
compiler: GCC
include:
+ - os: ubuntu-latest
+ compiler: GCC
+ qt-version: '5.12.12'
+ sonar: 'sonar'
- os: windows-2019
compiler: MSVC
platform: x64
- qt-version: '5.12.10'
+ qt-version: '5.12.12'
qt-arch: win64_msvc2017_64
- os: windows-2019
compiler: MSVC
platform: x64
- qt-version: '5.12.10'
+ qt-version: '5.12.12'
qt-arch: win64_msvc2017_64
update-api: update-api
+ env:
+ SONAR_SERVER_URL: 'https://sonarcloud.io'
+
steps:
- uses: actions/checkout@v2
with:
@@ -71,7 +79,7 @@ jobs:
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get -qq install ninja-build valgrind
- echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=quotest/.valgrind.supp" >>$GITHUB_ENV
+ echo "VALGRIND=valgrind --tool=memcheck --leak-check=yes --gen-suppressions=all --suppressions=$GITHUB_WORKSPACE/quotest/.valgrind.supp" >>$GITHUB_ENV
- name: Setup build environment
run: |
@@ -96,9 +104,31 @@ jobs:
VERSION="$(git describe --all --contains)-ci${{ github.run_number }}-$(git rev-parse --short HEAD)"
fi
echo "QUOTEST_ORIGIN=$VERSION @ ${{ runner.os }}/${{ matrix.compiler }}" >>$GITHUB_ENV
- echo "CMAKE_ARGS=-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SHARED_LIBS=false \
- -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local" >>$GITHUB_ENV
+
+ # Build libQuotient as a shared library across platforms but also
+ # check the static configuration somewhere
+ CMAKE_ARGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DBUILD_SHARED_LIBS=${{ !matrix.sonar && runner.os == 'Linux' }} \
+ -DCMAKE_INSTALL_PREFIX=~/.local \
+ -DCMAKE_PREFIX_PATH=~/.local \
+ -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON"
+
+ if [ -n "${{ matrix.sonar }}" ]; then
+ mkdir -p $HOME/.sonar
+ CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_CXX_FLAGS=--coverage"
+ echo "COV=gcov$CXX_VERSION_POSTFIX" >>$GITHUB_ENV
+ fi
+ echo "CMAKE_ARGS=$CMAKE_ARGS" >>$GITHUB_ENV
+
+ if [[ '${{ runner.os }}' != 'Windows' ]]; then
+ BIN_DIR=/bin
+ echo "LIB_PATH=$HOME/.local/lib" >>$GITHUB_ENV
+ fi
+ echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV
+ echo "~/.local$BIN_DIR" >>$GITHUB_PATH
+
cmake -E make_directory ${{ runner.workspace }}/build
+ echo "BUILD_PATH=${{ runner.workspace }}/build/libQuotient" >>$GITHUB_ENV
- name: Setup MSVC environment
uses: ilammy/msvc-dev-cmd@v1
@@ -106,6 +136,20 @@ jobs:
with:
arch: ${{ matrix.platform }}
+ - name: Download and set up Sonar Cloud tools
+ if: matrix.sonar != ''
+ env:
+ SONAR_SCANNER_VERSION: 4.6.2.2472
+ run: |
+ pushd $HOME/.sonar
+ curl -sSL --remote-name-all \
+ $SONAR_SERVER_URL/static/cpp/build-wrapper-linux-x86.zip \
+ https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip
+ unzip -o build-wrapper*.zip
+ echo "BUILD_WRAPPER=$HOME/.sonar/build-wrapper-linux-x86/build-wrapper-linux* --out-dir $BUILD_PATH/sonar" >>$GITHUB_ENV
+ unzip -o sonar-scanner-cli*.zip
+ popd
+
- name: Install OpenSSL
if: ${{ contains(matrix.os, 'ubuntu') && matrix.e2ee }}
run: |
@@ -114,11 +158,11 @@ jobs:
- name: Build and install olm
if: matrix.e2ee
+ working-directory: ${{ runner.workspace }}
run: |
- cd ..
git clone https://gitlab.matrix.org/matrix-org/olm.git
- cmake -S olm -B olm/build $CMAKE_ARGS
- cmake --build olm/build --target install
+ cmake -S olm -B build/olm $CMAKE_ARGS
+ cmake --build build/olm --target install
echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with E2EE" >>$GITHUB_ENV
- name: Build and install QtKeychain
@@ -130,14 +174,14 @@ jobs:
- name: Pull CS API and build GTAD
if: matrix.update-api
+ working-directory: ${{ runner.workspace }}
run: |
- cd ..
git clone https://github.com/matrix-org/matrix-doc.git
git clone --recursive https://github.com/KitsuneRal/gtad.git
- cmake -S gtad -B gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF
- cmake --build gtad
- echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=$GITHUB_WORKSPACE/../matrix-doc \
- -DGTAD_PATH=$GITHUB_WORKSPACE/../gtad/gtad" \
+ cmake -S gtad -B build/gtad $CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF
+ cmake --build build/gtad
+ echo "CMAKE_ARGS=$CMAKE_ARGS -DMATRIX_DOC_PATH=${{ runner.workspace }}/matrix-doc \
+ -DGTAD_PATH=${{ runner.workspace }}/build/gtad/gtad" \
>>$GITHUB_ENV
echo "QUOTEST_ORIGIN=$QUOTEST_ORIGIN with API files regeneration" >>$GITHUB_ENV
@@ -153,33 +197,51 @@ jobs:
- name: Configure libQuotient
run: |
- if [[ '${{ runner.os }}' == 'Windows' ]]; then
- BIN_DIR=.
- else
- BIN_DIR=bin
- fi
- echo "BIN_DIR=$BIN_DIR" >>$GITHUB_ENV
- cmake -S $GITHUB_WORKSPACE -B build $CMAKE_ARGS -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }}
+ cmake -S $GITHUB_WORKSPACE -B $BUILD_PATH $CMAKE_ARGS \
+ -DQuotient_ENABLE_E2EE=${{ matrix.e2ee }} -DQuotient_INSTALL_TESTS=ON
- name: Regenerate API code
if: matrix.update-api
- run: cmake --build build --target update-api
+ run: cmake --build ../build/libQuotient --target update-api
- name: Build and install libQuotient
run: |
- cmake --build build --target install
- ls ~/.local/$BIN_DIR/quotest
+ $BUILD_WRAPPER cmake --build $BUILD_PATH --target all
+ cmake --build $BUILD_PATH --target install
+ ls ~/.local$BIN_DIR/quotest
- name: Run tests
env:
TEST_USER: ${{ secrets.TEST_USER }}
TEST_PWD: ${{ secrets.TEST_PWD }}
- QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true'
+ QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true;quotient.events.debug=true'
QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}'
run: |
- [[ -z "$TEST_USER" ]] || $VALGRIND build/quotest/quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN"
+ ctest --test-dir $BUILD_PATH --output-on-failure
+ [[ -z "$TEST_USER" ]] || \
+ LD_LIBRARY_PATH=$LIB_PATH \
+ $VALGRIND quotest "$TEST_USER" "$TEST_PWD" quotest-gha '#quotest:matrix.org' "$QUOTEST_ORIGIN"
timeout-minutes: 4 # quotest is supposed to finish within 3 minutes, actually
-
+
- name: Perform CodeQL analysis
if: env.CODEQL_ANALYSIS
uses: github/codeql-action/analyze@v1
+
+ - name: Run sonar-scanner
+ if: matrix.sonar != ''
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ run: |
+ mkdir .coverage && pushd .coverage
+ find $BUILD_PATH -name '*.gcda' -print0 \
+ | xargs -0 $COV -s $GITHUB_WORKSPACE -pr
+ # Coverage of the test source code is not tracked, as it is always 100%
+ # (if not, some tests failed and broke the build at an earlier stage)
+ rm -f quotest* autotests*
+ popd
+ $HOME/.sonar/sonar-scanner*/bin/sonar-scanner \
+ -Dsonar.host.url="$SONAR_SERVER_URL" \
+ -Dsonar.cfamily.build-wrapper-output="$BUILD_PATH/sonar" \
+ -Dsonar.cfamily.threads=2 \
+ -Dsonar.cfamily.gcov.reportsPath=.coverage
diff --git a/.lgtm.yml b/.lgtm.yml
index 308675a8..a01e1de9 100644
--- a/.lgtm.yml
+++ b/.lgtm.yml
@@ -15,4 +15,4 @@ extraction:
# - cmake --build build
# - popd
configure:
- command: "cmake . -GNinja" # -DOlm_DIR=olm/build"
+ command: "CXX=clang++-9 cmake . -GNinja" # -DOlm_DIR=olm/build"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 69ac7e20..15726240 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -40,7 +40,7 @@ if (MSVC)
/wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027)
else()
foreach (FLAG Wall Wpedantic Wextra Werror=return-type Wno-unused-parameter
- Wno-gnu-zero-variadic-macro-arguments fvisibility-inlines-hidden)
+ Wno-gnu-zero-variadic-macro-arguments)
CHECK_CXX_COMPILER_FLAG("-${FLAG}" COMPILER_${FLAG}_SUPPORTED)
if ( COMPILER_${FLAG}_SUPPORTED AND
NOT CMAKE_CXX_FLAGS MATCHES "(^| )-?${FLAG}($| )")
@@ -115,66 +115,75 @@ endif()
# Set up source files
list(APPEND lib_SRCS
lib/quotient_common.h
- lib/networkaccessmanager.cpp
- lib/connectiondata.cpp
- lib/connection.cpp
- lib/ssosession.cpp
- lib/logging.cpp
- lib/room.cpp
- lib/user.cpp
- lib/avatar.cpp
- lib/uri.cpp
- lib/uriresolver.cpp
- lib/eventstats.cpp
- lib/syncdata.cpp
- lib/settings.cpp
- lib/networksettings.cpp
- lib/converters.cpp
- lib/util.cpp
- lib/eventitem.cpp
- lib/accountregistry.cpp
- lib/mxcreply.cpp
- lib/events/event.cpp
- lib/events/roomevent.cpp
- lib/events/stateevent.cpp
- lib/events/eventcontent.cpp
- lib/events/roomcreateevent.cpp
- lib/events/roomtombstoneevent.cpp
- lib/events/roommessageevent.cpp
- lib/events/roommemberevent.cpp
- lib/events/roompowerlevelsevent.cpp
- lib/events/typingevent.cpp
- lib/events/receiptevent.cpp
- lib/events/reactionevent.cpp
- lib/events/callanswerevent.cpp
- lib/events/callcandidatesevent.cpp
- lib/events/callhangupevent.cpp
- lib/events/callinviteevent.cpp
- lib/events/directchatevent.cpp
- lib/events/encryptionevent.cpp
- lib/events/encryptedevent.cpp
- lib/events/roomkeyevent.cpp
- lib/events/stickerevent.cpp
- lib/events/keyverificationevent.cpp
- lib/events/encryptedfile.cpp
- lib/jobs/requestdata.cpp
- lib/jobs/basejob.cpp
- lib/jobs/syncjob.cpp
- lib/jobs/mediathumbnailjob.cpp
- lib/jobs/downloadfilejob.cpp
+ lib/quotient_export.h
+ lib/function_traits.h lib/function_traits.cpp
+ lib/omittable.h
+ lib/networkaccessmanager.h lib/networkaccessmanager.cpp
+ lib/connectiondata.h lib/connectiondata.cpp
+ lib/connection.h lib/connection.cpp
+ lib/ssosession.h lib/ssosession.cpp
+ lib/logging.h lib/logging.cpp
+ lib/room.h lib/room.cpp
+ lib/roomstateview.h lib/roomstateview.cpp
+ lib/user.h lib/user.cpp
+ lib/avatar.h lib/avatar.cpp
+ lib/uri.h lib/uri.cpp
+ lib/uriresolver.h lib/uriresolver.cpp
+ lib/eventstats.h lib/eventstats.cpp
+ lib/syncdata.h lib/syncdata.cpp
+ lib/settings.h lib/settings.cpp
+ lib/networksettings.h lib/networksettings.cpp
+ lib/converters.h lib/converters.cpp
+ lib/util.h lib/util.cpp
+ lib/eventitem.h lib/eventitem.cpp
+ lib/accountregistry.h lib/accountregistry.cpp
+ lib/mxcreply.h lib/mxcreply.cpp
+ lib/events/event.h lib/events/event.cpp
+ lib/events/eventloader.h
+ lib/events/roomevent.h lib/events/roomevent.cpp
+ lib/events/stateevent.h lib/events/stateevent.cpp
+ lib/events/simplestateevents.h
+ lib/events/eventcontent.h lib/events/eventcontent.cpp
+ lib/events/eventrelation.h lib/events/eventrelation.cpp
+ lib/events/roomcreateevent.h lib/events/roomcreateevent.cpp
+ lib/events/roomtombstoneevent.h lib/events/roomtombstoneevent.cpp
+ lib/events/roommessageevent.h lib/events/roommessageevent.cpp
+ lib/events/roommemberevent.h lib/events/roommemberevent.cpp
+ lib/events/roomavatarevent.h
+ lib/events/roompowerlevelsevent.h lib/events/roompowerlevelsevent.cpp
+ lib/events/typingevent.h lib/events/typingevent.cpp
+ lib/events/accountdataevents.h
+ lib/events/receiptevent.h lib/events/receiptevent.cpp
+ lib/events/reactionevent.h
+ lib/events/callinviteevent.h lib/events/callinviteevent.cpp
+ lib/events/callcandidatesevent.h
+ lib/events/callanswerevent.h lib/events/callanswerevent.cpp
+ lib/events/callhangupevent.h
+ lib/events/directchatevent.h lib/events/directchatevent.cpp
+ lib/events/encryptionevent.h lib/events/encryptionevent.cpp
+ lib/events/encryptedevent.h lib/events/encryptedevent.cpp
+ lib/events/roomkeyevent.h lib/events/roomkeyevent.cpp
+ lib/events/stickerevent.h lib/events/stickerevent.cpp
+ lib/events/keyverificationevent.h lib/events/keyverificationevent.cpp
+ lib/events/encryptedfile.h lib/events/encryptedfile.cpp
+ lib/jobs/requestdata.h lib/jobs/requestdata.cpp
+ lib/jobs/basejob.h lib/jobs/basejob.cpp
+ lib/jobs/syncjob.h lib/jobs/syncjob.cpp
+ lib/jobs/mediathumbnailjob.h lib/jobs/mediathumbnailjob.cpp
+ lib/jobs/downloadfilejob.h lib/jobs/downloadfilejob.cpp
)
if (${PROJECT_NAME}_ENABLE_E2EE)
list(APPEND lib_SRCS
- lib/database.cpp
- lib/e2ee/qolmaccount.cpp
- lib/e2ee/qolmsession.cpp
- lib/e2ee/qolminboundsession.cpp
- lib/e2ee/qolmoutboundsession.cpp
- lib/e2ee/qolmutils.cpp
- lib/e2ee/qolmutility.cpp
- lib/e2ee/qolmerrors.cpp
- lib/e2ee/qolmsession.cpp
- lib/e2ee/qolmmessage.cpp
+ lib/database.h lib/database.cpp
+ lib/e2ee/qolmaccount.h lib/e2ee/qolmaccount.cpp
+ lib/e2ee/qolmsession.h lib/e2ee/qolmsession.cpp
+ lib/e2ee/qolminboundsession.h lib/e2ee/qolminboundsession.cpp
+ lib/e2ee/qolmoutboundsession.h lib/e2ee/qolmoutboundsession.cpp
+ lib/e2ee/qolmutils.h lib/e2ee/qolmutils.cpp
+ lib/e2ee/qolmutility.h lib/e2ee/qolmutility.cpp
+ lib/e2ee/qolmerrors.h lib/e2ee/qolmerrors.cpp
+ lib/e2ee/qolmsession.h lib/e2ee/qolmsession.cpp
+ lib/e2ee/qolmmessage.h lib/e2ee/qolmmessage.cpp
)
endif()
@@ -186,7 +195,6 @@ set(ASAPI_DEF_DIR application-service/definitions)
set(ISAPI_DEF_DIR identity/definitions)
set(API_GENERATION_ENABLED 0)
-set(API_FORMATTING_ENABLED 0)
if (GTAD_PATH AND MATRIX_DOC_PATH)
# REALPATH resolves ~ (home directory) while PROGRAM doesn't
get_filename_component(ABS_GTAD_PATH "${GTAD_PATH}" REALPATH)
@@ -213,28 +221,17 @@ if (API_GENERATION_ENABLED)
set(CLANG_FORMAT clang-format)
endif()
get_filename_component(ABS_CLANG_FORMAT "${CLANG_FORMAT}" PROGRAM PROGRAM_ARGS CLANG_FORMAT_ARGS)
- if (ABS_CLANG_FORMAT)
- set(API_FORMATTING_ENABLED 1)
- message( STATUS "clang-format is at ${ABS_CLANG_FORMAT}")
- else ()
- message( STATUS "${CLANG_FORMAT} is NOT FOUND; API files won't be reformatted")
+ if (NOT ABS_CLANG_FORMAT)
+ message( WARNING "${CLANG_FORMAT} is NOT FOUND; API files won't be formatted")
endif ()
- # We use globbing with CONFIGURE_DEPENDS to produce two file lists:
- # one of all API files for clang-format and another of just .cpp
- # files to supply for library source files. Since we expect these
- # file lists to only change due to GTAD invocation, we only use
- # CONFIGURE_DEPENDS when pre-requisites to update API are met.
- # Read comments next to each file(GLOB_RECURSE) for caveats.
- set(add_CONFIGURE_DEPENDS "CONFIGURE_DEPENDS")
-
set(FULL_CSAPI_SRC_DIR ${ABS_API_DEF_PATH}/client-server)
file(GLOB_RECURSE API_DEFS RELATIVE ${PROJECT_SOURCE_DIR}
${FULL_CSAPI_SRC_DIR}/*.yaml
${ABS_API_DEF_PATH}/${ASAPI_DEF_DIR}/*.yaml
${ABS_API_DEF_PATH}/${ISAPI_DEF_DIR}/*.yaml
)
- add_custom_target(generate-unformatted-api
+ add_custom_target(update-api
${ABS_GTAD_PATH} --config ../gtad/gtad.yaml --out ${CSAPI_DIR}
${FULL_CSAPI_SRC_DIR}
old_sync.yaml- room_initial_sync.yaml- # deprecated
@@ -249,46 +246,28 @@ if (API_GENERATION_ENABLED)
${API_DEFS}
VERBATIM
)
- add_custom_target(update-api DEPENDS generate-unformatted-api)
- if (EXISTS ${ABS_CLANG_FORMAT})
- set(CLANG_FORMAT_ARGS -i -sort-includes ${CLANG_FORMAT_ARGS})
- # FIXME: the list of files should be produced after GTAD has run.
- # For now it's produced at CMake invocation. If file() hasn't found
- # any files at that moment, clang-format will be called with an empty
- # list of files and choke. Taking outfiles.txt from GTAD could be
- # an option but this, too, must be done during the build stage, and
- # there's no crossplatform way to use the contents of a file as
- # input for a build target command.
- file(GLOB_RECURSE api_ALL_SRCS ${add_CONFIGURE_DEPENDS}
- ${FULL_CSAPI_DIR}/*.*
- lib/${ASAPI_DEF_DIR}/*.*
- lib/${ISAPI_DEF_DIR}/*.*)
- if (api_ALL_SRCS)
- add_custom_target(format-api
- ${ABS_CLANG_FORMAT} ${CLANG_FORMAT_ARGS} ${api_ALL_SRCS}
- DEPENDS generate-unformatted-api
- VERBATIM)
- add_dependencies(update-api format-api)
- endif()
- endif()
endif()
add_feature_info(EnableApiCodeGeneration "${API_GENERATION_ENABLED}"
"build target update-api")
-if (API_GENERATION_ENABLED)
- add_feature_info(EnableApiFormatting "${API_FORMATTING_ENABLED}"
- "formatting of generated API files with clang-format")
-endif()
-
-# Make no mistake: CMake cannot run gtad first and then populate the list of
-# resulting api_SRCS files. In other words, placing the below statement after
-# the block above will not lead to CMake magically reconfiguring the build
-# after building the update-api target. CONFIGURE_DEPENDS somewhat helps,
-# at least forcing the build system to reevaluate the glob before building
-# the next target. Otherwise, you have to watch out if gtad has created
-# new files and if it has, re-run cmake.
-file(GLOB_RECURSE api_SRCS ${add_CONFIGURE_DEPENDS} ${FULL_CSAPI_DIR}/*.cpp)
-add_library(${PROJECT_NAME} ${lib_SRCS} ${api_SRCS})
+# Produce the list of all Matrix API files for building the library. When this
+# list changes (normally after calling GTAD), CONFIGURE_DEPENDS will force
+# the build system to call CMake again. Checking for the glob change slows down
+# each build (even if the target does not involve API generation). It would be
+# ideal if GTAD could compare the initial (saved somewhere) and the generated
+# file list itself and write down to some .cmake file if those are different,
+# which would trigger the reconfiguration specifically before the next build.
+# For now CONFIGURE_DEPENDS is the best approximation of that.
+file(GLOB_RECURSE api_ALL_SRCS CONFIGURE_DEPENDS
+ ${FULL_CSAPI_DIR}/*.* lib/${ASAPI_DEF_DIR}/*.* lib/${ISAPI_DEF_DIR}/*.*)
+
+add_library(${PROJECT_NAME} ${lib_SRCS} ${api_ALL_SRCS})
+# Set BUILDING_SHARED_QUOTIENT if building as a shared library
+target_compile_definitions(${PROJECT_NAME} PRIVATE
+ $<$<STREQUAL:$<TARGET_PROPERTY:${PROJECT_NAME},TYPE>,SHARED_LIBRARY>:BUILDING_SHARED_QUOTIENT>)
+# Set QUOTIENT_STATIC in a static library setting
+target_compile_definitions(${PROJECT_NAME} PUBLIC
+ $<$<STREQUAL:$<TARGET_PROPERTY:${PROJECT_NAME},TYPE>,STATIC_LIBRARY>:QUOTIENT_STATIC>)
target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_JAVA_STYLE_ITERATORS QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_TO_ASCII)
target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
@@ -300,6 +279,8 @@ endif()
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 20
CXX_EXTENSIONS OFF
+ VISIBILITY_INLINES_HIDDEN ON
+ CXX_VISIBILITY_PRESET hidden
VERSION "${PROJECT_VERSION}"
SOVERSION ${API_VERSION}
INTERFACE_${PROJECT_NAME}_MAJOR_VERSION ${API_VERSION}
@@ -351,8 +332,8 @@ endif()
# Configure installation
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets
+ LIBRARY RUNTIME
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR}
)
install(DIRECTORY lib/ DESTINATION ${${PROJECT_NAME}_INSTALL_INCLUDEDIR}
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 0354172b..671d6c08 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -12,6 +12,7 @@ function(QUOTIENT_ADD_TEST)
endfunction()
quotient_add_test(NAME callcandidateseventtest)
+quotient_add_test(NAME utiltests)
if(${PROJECT_NAME}_ENABLE_E2EE)
quotient_add_test(NAME testolmaccount)
quotient_add_test(NAME testgroupsession)
diff --git a/autotests/callcandidateseventtest.cpp b/autotests/callcandidateseventtest.cpp
index 1a69cb66..0d5a543b 100644
--- a/autotests/callcandidateseventtest.cpp
+++ b/autotests/callcandidateseventtest.cpp
@@ -53,5 +53,5 @@ void TestCallCandidatesEvent::fromJson()
QStringLiteral("candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0"));
}
-QTEST_MAIN(TestCallCandidatesEvent)
+QTEST_APPLESS_MAIN(TestCallCandidatesEvent)
#include "callcandidateseventtest.moc"
diff --git a/autotests/utiltests.cpp b/autotests/utiltests.cpp
new file mode 100644
index 00000000..e3ec63d0
--- /dev/null
+++ b/autotests/utiltests.cpp
@@ -0,0 +1,45 @@
+// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "omittable.h"
+
+#include <QtTest/QtTest>
+
+// compile-time Omittable<> tests
+using namespace Quotient;
+
+Omittable<int> testFn(bool) { return 0; }
+bool testFn2(int) { return false; }
+static_assert(
+ std::is_same_v<decltype(std::declval<Omittable<bool>>().then(testFn)),
+ Omittable<int>>);
+static_assert(
+ std::is_same_v<
+ decltype(std::declval<Omittable<bool>>().then_or(testFn, 0)), int>);
+static_assert(
+ std::is_same_v<decltype(std::declval<Omittable<bool>>().then(testFn)),
+ Omittable<int>>);
+static_assert(std::is_same_v<decltype(std::declval<Omittable<int>>()
+ .then(testFn2)
+ .then(testFn)),
+ Omittable<int>>);
+static_assert(std::is_same_v<decltype(std::declval<Omittable<bool>>()
+ .then(testFn)
+ .then_or(testFn2, false)),
+ bool>);
+
+constexpr auto visitTestFn(int, bool) { return false; }
+static_assert(
+ std::is_same_v<Omittable<bool>, decltype(lift(testFn2, Omittable<int>()))>);
+static_assert(std::is_same_v<Omittable<bool>,
+ decltype(lift(visitTestFn, Omittable<int>(),
+ Omittable<bool>()))>);
+
+class TestUtils : public QObject {
+ Q_OBJECT
+private Q_SLOTS:
+ // TODO
+};
+
+QTEST_APPLESS_MAIN(TestUtils)
+#include "utiltests.moc"
diff --git a/gtad/gtad.yaml b/gtad/gtad.yaml
index 943ac013..03c23886 100644
--- a/gtad/gtad.yaml
+++ b/gtad/gtad.yaml
@@ -45,7 +45,7 @@ analyzer:
types:
- +set: &UseOmittable
useOmittable:
- omittedValue: 'none' # Quotient::none in lib/util.h
+ omittedValue: 'none' # Quotient::none in lib/omittable.h
+on:
- integer:
- int64: qint64
diff --git a/gtad/operation.h.mustache b/gtad/operation.h.mustache
index f91dc66c..063f0bbd 100644
--- a/gtad/operation.h.mustache
+++ b/gtad/operation.h.mustache
@@ -16,7 +16,7 @@ namespace Quotient {
/*!{{>docCommentSummary}}{{#description}}
* {{_}}{{/description}}
*/
-class {{camelCaseOperationId}}Job : public BaseJob {
+class QUOTIENT_API {{camelCaseOperationId}}Job : public BaseJob {
public:
{{#models}}
// Inner data structures
diff --git a/lib/accountregistry.cpp b/lib/accountregistry.cpp
index a292ed45..616b54b4 100644
--- a/lib/accountregistry.cpp
+++ b/lib/accountregistry.cpp
@@ -8,90 +8,59 @@
using namespace Quotient;
-void AccountRegistry::add(Connection* c)
+void AccountRegistry::add(Connection* a)
{
- if (m_accounts.contains(c))
+ if (contains(a))
return;
- beginInsertRows(QModelIndex(), m_accounts.size(), m_accounts.size());
- m_accounts += c;
+ beginInsertRows(QModelIndex(), size(), size());
+ push_back(a);
endInsertRows();
}
-void AccountRegistry::drop(Connection* c)
+void AccountRegistry::drop(Connection* a)
{
- beginRemoveRows(QModelIndex(), m_accounts.indexOf(c), m_accounts.indexOf(c));
- m_accounts.removeOne(c);
+ const auto idx = indexOf(a);
+ beginRemoveRows(QModelIndex(), idx, idx);
+ remove(idx);
endRemoveRows();
- Q_ASSERT(!m_accounts.contains(c));
+ Q_ASSERT(!contains(a));
}
bool AccountRegistry::isLoggedIn(const QString &userId) const
{
- return std::any_of(m_accounts.cbegin(), m_accounts.cend(),
- [&userId](Connection* a) { return a->userId() == userId; });
+ return std::any_of(cbegin(), cend(), [&userId](const Connection* a) {
+ return a->userId() == userId;
+ });
}
-bool AccountRegistry::contains(Connection *c) const
+QVariant AccountRegistry::data(const QModelIndex& index, int role) const
{
- return m_accounts.contains(c);
-}
-
-AccountRegistry::AccountRegistry() = default;
-
-QVariant AccountRegistry::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid()) {
- return {};
- }
-
- if (index.row() >= m_accounts.count()) {
+ if (!index.isValid() || index.row() >= count())
return {};
- }
- auto account = m_accounts[index.row()];
-
- if (role == ConnectionRole) {
- return QVariant::fromValue(account);
- }
+ if (role == AccountRole)
+ return QVariant::fromValue(at(index.row()));
return {};
}
-int AccountRegistry::rowCount(const QModelIndex &parent) const
+int AccountRegistry::rowCount(const QModelIndex& parent) const
{
- if (parent.isValid()) {
- return 0;
- }
-
- return m_accounts.count();
+ return parent.isValid() ? 0 : count();
}
QHash<int, QByteArray> AccountRegistry::roleNames() const
{
- return {{ConnectionRole, "connection"}};
+ return { { AccountRole, "connection" } };
}
-bool AccountRegistry::isEmpty() const
-{
- return m_accounts.isEmpty();
-}
-int AccountRegistry::count() const
-{
- return m_accounts.count();
-}
-
-const QVector<Connection*> AccountRegistry::accounts() const
-{
- return m_accounts;
-}
Connection* AccountRegistry::get(const QString& userId)
{
- for (const auto &connection : m_accounts) {
- if(connection->userId() == userId) {
+ for (const auto &connection : *this) {
+ if (connection->userId() == userId)
return connection;
- }
}
return nullptr;
}
diff --git a/lib/accountregistry.h b/lib/accountregistry.h
index 5efda459..2f6dffdf 100644
--- a/lib/accountregistry.h
+++ b/lib/accountregistry.h
@@ -4,42 +4,57 @@
#pragma once
-#include <QtCore/QObject>
-#include <QtCore/QList>
+#include "quotient_export.h"
+
#include <QtCore/QAbstractListModel>
namespace Quotient {
class Connection;
-class AccountRegistry : public QAbstractListModel {
+class QUOTIENT_API AccountRegistry : public QAbstractListModel,
+ private QVector<Connection*> {
Q_OBJECT
public:
+ using const_iterator = QVector::const_iterator;
+ using const_reference = QVector::const_reference;
+
enum EventRoles {
- ConnectionRole = Qt::UserRole + 1,
+ AccountRole = Qt::UserRole + 1,
+ ConnectionRole = AccountRole
};
- static AccountRegistry &instance() {
- static AccountRegistry _instance;
- return _instance;
- }
+ [[deprecated("Use Accounts variable instead")]] //
+ static AccountRegistry& instance();
+
+ // Expose most of QVector's const-API but only provide add() and drop()
+ // for changing it. In theory other changing operations could be supported
+ // too; but then boilerplate begin/end*() calls has to be tucked into each
+ // and this class gives no guarantees on the order of entries, so why care.
- const QVector<Connection*> accounts() const;
+ const QVector<Connection*>& accounts() const { return *this; }
void add(Connection* a);
void drop(Connection* a);
+ const_iterator begin() const { return QVector::begin(); }
+ const_iterator end() const { return QVector::end(); }
+ const_reference front() const { return QVector::front(); }
+ const_reference back() const { return QVector::back(); }
bool isLoggedIn(const QString& userId) const;
- bool isEmpty() const;
- int count() const;
- bool contains(Connection*) const;
Connection* get(const QString& userId);
- [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ using QVector::isEmpty, QVector::empty;
+ using QVector::size, QVector::count, QVector::capacity;
+ using QVector::cbegin, QVector::cend, QVector::contains;
+ // QAbstractItemModel interface implementation
+
+ [[nodiscard]] QVariant data(const QModelIndex& index,
+ int role) const override;
+ [[nodiscard]] int rowCount(
+ const QModelIndex& parent = QModelIndex()) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
+};
-private:
- AccountRegistry();
+inline QUOTIENT_API AccountRegistry Accounts {};
- QVector<Connection *> m_accounts;
-};
+inline AccountRegistry& AccountRegistry::instance() { return Accounts; }
}
diff --git a/lib/avatar.cpp b/lib/avatar.cpp
index 77648562..9304a3de 100644
--- a/lib/avatar.cpp
+++ b/lib/avatar.cpp
@@ -47,15 +47,11 @@ public:
mutable std::vector<get_callback_t> callbacks;
};
-Avatar::Avatar() : d(std::make_unique<Private>()) {}
+Avatar::Avatar()
+ : d(makeImpl<Private>())
+{}
-Avatar::Avatar(QUrl url) : d(std::make_unique<Private>(std::move(url))) {}
-
-Avatar::Avatar(Avatar&&) = default;
-
-Avatar::~Avatar() = default;
-
-Avatar& Avatar::operator=(Avatar&&) = default;
+Avatar::Avatar(QUrl url) : d(makeImpl<Private>(std::move(url))) {}
QImage Avatar::get(Connection* connection, int dimension,
get_callback_t callback) const
diff --git a/lib/avatar.h b/lib/avatar.h
index d4634aea..c94dc369 100644
--- a/lib/avatar.h
+++ b/lib/avatar.h
@@ -3,22 +3,20 @@
#pragma once
+#include "util.h"
+
#include <QtCore/QUrl>
#include <QtGui/QIcon>
#include <functional>
-#include <memory>
namespace Quotient {
class Connection;
-class Avatar {
+class QUOTIENT_API Avatar {
public:
explicit Avatar();
explicit Avatar(QUrl url);
- Avatar(Avatar&&);
- ~Avatar();
- Avatar& operator=(Avatar&&);
using get_callback_t = std::function<void()>;
using upload_callback_t = std::function<void(QUrl)>;
@@ -39,6 +37,6 @@ public:
private:
class Private;
- std::unique_ptr<Private> d;
+ ImplPtr<Private> d;
};
} // namespace Quotient
diff --git a/lib/connection.cpp b/lib/connection.cpp
index a8de4030..14188ace 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -348,7 +348,8 @@ public:
};
Connection::Connection(const QUrl& server, QObject* parent)
- : QObject(parent), d(new Private(std::make_unique<ConnectionData>(server)))
+ : QObject(parent)
+ , d(makeImpl<Private>(std::make_unique<ConnectionData>(server)))
{
#ifdef Quotient_E2EE_ENABLED
//connect(qApp, &QCoreApplication::aboutToQuit, this, &Connection::saveOlmAccount);
@@ -362,7 +363,7 @@ Connection::~Connection()
{
qCDebug(MAIN) << "deconstructing connection object for" << userId();
stopSync();
- AccountRegistry::instance().drop(this);
+ Accounts.drop(this);
}
void Connection::resolveServer(const QString& mxid)
@@ -541,7 +542,7 @@ void Connection::Private::completeSetup(const QString& mxId)
qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString()
<< "by user" << data->userId()
<< "from device" << data->deviceId();
- AccountRegistry::instance().add(q);
+ Accounts.add(q);
#ifndef Quotient_E2EE_ENABLED
qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off.";
#else // Quotient_E2EE_ENABLED
@@ -825,7 +826,7 @@ void Connection::Private::consumeAccountData(Events&& accountDataEvents)
// After running this loop, the account data events not saved in
// accountData (see the end of the loop body) are auto-cleaned away
for (auto&& eventPtr: accountDataEvents) {
- visit(*eventPtr,
+ switchOnType(*eventPtr,
[this](const DirectChatEvent& dce) {
// https://github.com/quotient-im/libQuotient/wiki/Handling-direct-chat-events
const auto& usersToDCs = dce.usersToDirectChats();
@@ -925,7 +926,7 @@ void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents)
return;
}
- visit(*decryptedEvent,
+ switchOnType(*decryptedEvent,
[this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) {
if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) {
detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey);
@@ -1713,8 +1714,8 @@ room_factory_t Connection::roomFactory() { return _roomFactory; }
user_factory_t Connection::userFactory() { return _userFactory; }
-room_factory_t Connection::_roomFactory = defaultRoomFactory<>();
-user_factory_t Connection::_userFactory = defaultUserFactory<>();
+room_factory_t Connection::_roomFactory = defaultRoomFactory<>;
+user_factory_t Connection::_userFactory = defaultUserFactory<>;
QByteArray Connection::generateTxnId() const
{
diff --git a/lib/connection.h b/lib/connection.h
index 28ea6ff3..165d8d68 100644
--- a/lib/connection.h
+++ b/lib/connection.h
@@ -8,6 +8,7 @@
#include "ssosession.h"
#include "qt_connection_util.h"
#include "quotient_common.h"
+#include "util.h"
#include "csapi/login.h"
#include "csapi/create_room.h"
@@ -48,6 +49,7 @@ class SendToDeviceJob;
class SendMessageJob;
class LeaveRoomJob;
class Database;
+struct EncryptedFile;
class QOlmAccount;
class QOlmInboundGroupSession;
@@ -55,11 +57,11 @@ class QOlmInboundGroupSession;
using LoginFlow = GetLoginFlowsJob::LoginFlow;
/// Predefined login flows
-struct LoginFlows {
- static inline const LoginFlow Password { "m.login.password" };
- static inline const LoginFlow SSO { "m.login.sso" };
- static inline const LoginFlow Token { "m.login.token" };
-};
+namespace LoginFlows {
+ inline const LoginFlow Password { "m.login.password" };
+ inline const LoginFlow SSO { "m.login.sso" };
+ inline const LoginFlow Token { "m.login.token" };
+}
// To simplify comparisons of LoginFlows
@@ -85,11 +87,9 @@ using user_factory_t = std::function<User*(Connection*, const QString&)>;
* \sa Connection::setRoomFactory, Connection::setRoomType
*/
template <typename T = Room>
-static inline room_factory_t defaultRoomFactory()
+auto defaultRoomFactory(Connection* c, const QString& id, JoinState js)
{
- return [](Connection* c, const QString& id, JoinState js) {
- return new T(c, id, js);
- };
+ return new T(c, id, js);
}
/** The default factory to create user objects
@@ -98,9 +98,9 @@ static inline room_factory_t defaultRoomFactory()
* \sa Connection::setUserFactory, Connection::setUserType
*/
template <typename T = User>
-static inline user_factory_t defaultUserFactory()
+auto defaultUserFactory(Connection* c, const QString& id)
{
- return [](Connection* c, const QString& id) { return new T(id, c); };
+ return new T(id, c);
}
// Room ids, rather than room pointers, are used in the direct chat
@@ -111,7 +111,7 @@ using DirectChatsMap = QMultiHash<const User*, QString>;
using DirectChatUsersMap = QMultiHash<QString, User*>;
using IgnoredUsersList = IgnoredUsersEvent::content_type;
-class Connection : public QObject {
+class QUOTIENT_API Connection : public QObject {
Q_OBJECT
Q_PROPERTY(User* localUser READ user NOTIFY stateChanged)
@@ -128,6 +128,7 @@ class Connection : public QObject {
Q_PROPERTY(bool supportsPasswordAuth READ supportsPasswordAuth NOTIFY loginFlowsChanged STORED false)
Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged)
+ Q_PROPERTY(bool canChangePassword READ canChangePassword NOTIFY capabilitiesLoaded)
public:
using UsersToDevicesToEvents =
@@ -451,6 +452,17 @@ public:
std::forward<JobArgTs>(jobArgs)...);
}
+ //! \brief Start a local HTTP server and generate a single sign-on URL
+ //!
+ //! This call does the preparatory steps to carry out single sign-on
+ //! sequence
+ //! \sa https://matrix.org/docs/guides/sso-for-client-developers
+ //! \return A proxy object holding two URLs: one for SSO on the chosen
+ //! homeserver and another for the local callback address. Normally
+ //! you won't need the callback URL unless you proxy the response
+ //! with a custom UI. You do not need to delete the SsoSession
+ //! object; the Connection that issued it will dispose of it once
+ //! the login sequence completes (with any outcome).
Q_INVOKABLE SsoSession* prepareForSso(const QString& initialDeviceName,
const QString& deviceId = {});
@@ -475,14 +487,14 @@ public:
template <typename T>
static void setRoomType()
{
- setRoomFactory(defaultRoomFactory<T>());
+ setRoomFactory(defaultRoomFactory<T>);
}
/// Set the user factory to default with the overriden user type
template <typename T>
static void setUserType()
{
- setUserFactory(defaultUserFactory<T>());
+ setUserFactory(defaultUserFactory<T>);
}
/// Saves the olm account data to disk. Usually doesn't need to be called manually.
@@ -864,7 +876,7 @@ protected Q_SLOTS:
private:
class Private;
- QScopedPointer<Private> d;
+ ImplPtr<Private> d;
static room_factory_t _roomFactory;
static user_factory_t _userFactory;
diff --git a/lib/connectiondata.cpp b/lib/connectiondata.cpp
index 87ad4577..aca218be 100644
--- a/lib/connectiondata.cpp
+++ b/lib/connectiondata.cpp
@@ -41,7 +41,7 @@ public:
};
ConnectionData::ConnectionData(QUrl baseUrl)
- : d(std::make_unique<Private>(std::move(baseUrl)))
+ : d(makeImpl<Private>(std::move(baseUrl)))
{
// Each lambda invocation below takes no more than one job from the
// queues (first foreground, then background) and resumes it; then
diff --git a/lib/connectiondata.h b/lib/connectiondata.h
index e16a2dac..75fc332f 100644
--- a/lib/connectiondata.h
+++ b/lib/connectiondata.h
@@ -4,9 +4,10 @@
#pragma once
+#include "util.h"
+
#include <QtCore/QUrl>
-#include <memory>
#include <chrono>
class QNetworkAccessManager;
@@ -42,6 +43,6 @@ public:
private:
class Private;
- std::unique_ptr<Private> d;
+ ImplPtr<Private> d;
};
} // namespace Quotient
diff --git a/lib/converters.h b/lib/converters.h
index 90349019..515c96fd 100644
--- a/lib/converters.h
+++ b/lib/converters.h
@@ -3,6 +3,7 @@
#pragma once
+#include "omittable.h"
#include "util.h"
#include <QtCore/QDate>
@@ -13,6 +14,7 @@
#include <QtCore/QUrlQuery>
#include <QtCore/QVector>
+#include <type_traits>
#include <vector>
class QVariant;
@@ -20,15 +22,34 @@ class QVariant;
namespace Quotient {
template <typename T>
struct JsonObjectConverter {
- static void dumpTo(QJsonObject& jo, const T& pod) { jo = pod.toJson(); }
- static void fillFrom(const QJsonObject& jo, T& pod) { pod = T(jo); }
+ // To be implemented in specialisations
+ static void dumpTo(QJsonObject&, const T&) = delete;
+ static void fillFrom(const QJsonObject&, T&) = delete;
};
+namespace _impl {
+ template <typename T, typename = void>
+ struct JsonExporter {
+ static QJsonObject dump(const T& data)
+ {
+ QJsonObject jo;
+ JsonObjectConverter<T>::dumpTo(jo, data);
+ return jo;
+ }
+ };
+
+ template <typename T>
+ struct JsonExporter<
+ T, std::enable_if_t<std::is_invocable_v<decltype(&T::toJson), T>>> {
+ static auto dump(const T& data) { return data.toJson(); }
+ };
+}
+
//! \brief The switchboard for extra conversion algorithms behind from/toJson
//!
//! This template is mainly intended for partial conversion specialisations
-//! since from/toJson are functions and cannot be partially specialised;
-//! another case for JsonConverter is to insulate types that can be constructed
+//! since from/toJson are functions and cannot be partially specialised.
+//! Another case for JsonConverter is to insulate types that can be constructed
//! from basic types - namely, QVariant and QUrl can be directly constructed
//! from QString and having an overload or specialisation for those leads to
//! ambiguity between these and QJsonValue. For trivial (converting
@@ -40,18 +61,25 @@ struct JsonObjectConverter {
//! that they are not supported and it's not feasible to support those by means
//! of overloading toJson() and specialising fromJson().
template <typename T>
-struct JsonConverter {
- static QJsonObject dump(const T& pod)
- {
- QJsonObject jo;
- JsonObjectConverter<T>::dumpTo(jo, pod);
- return jo;
- }
+struct JsonConverter : _impl::JsonExporter<T> {
+ // Unfortunately, if constexpr doesn't work with dump() and T::toJson
+ // because trying to check invocability of T::toJson hits a hard
+ // (non-SFINAE) compilation error if the member is not there. Hence a bit
+ // more verbose SFINAE construct in _impl::JsonExporter.
+
static T doLoad(const QJsonObject& jo)
{
- T pod;
- JsonObjectConverter<T>::fillFrom(jo, pod);
- return pod;
+ // 'else' below are required to suppress code generation for unused
+ // branches - 'return' is not enough
+ if constexpr (std::is_same_v<T, QJsonObject>)
+ return jo;
+ else if constexpr (std::is_constructible_v<T, QJsonObject>)
+ return T(jo);
+ else {
+ T pod;
+ JsonObjectConverter<T>::fillFrom(jo, pod);
+ return pod;
+ }
}
static T load(const QJsonValue& jv) { return doLoad(jv.toObject()); }
static T load(const QJsonDocument& jd) { return doLoad(jd.object()); }
@@ -183,7 +211,7 @@ struct JsonConverter<QUrl> {
};
template <>
-struct JsonConverter<QVariant> {
+struct QUOTIENT_API JsonConverter<QVariant> {
static QJsonValue dump(const QVariant& v);
static QVariant load(const QJsonValue& jv);
};
@@ -281,12 +309,14 @@ template <typename T>
struct JsonObjectConverter<QHash<QString, T>>
: public HashMapFromJson<QHash<QString, T>> {};
-QJsonObject toJson(const QVariantHash& vh);
+QJsonObject QUOTIENT_API toJson(const QVariantHash& vh);
template <>
-QVariantHash fromJson(const QJsonValue& jv);
+QVariantHash QUOTIENT_API fromJson(const QJsonValue& jv);
// Conditional insertion into a QJsonObject
+constexpr bool IfNotEmpty = false;
+
namespace _impl {
template <typename ValT>
inline void addTo(QJsonObject& o, const QString& k, ValT&& v)
@@ -332,7 +362,7 @@ namespace _impl {
// This one is for types that have isEmpty() when Force is false
template <typename ValT>
- struct AddNode<ValT, false, decltype(std::declval<ValT>().isEmpty())> {
+ struct AddNode<ValT, IfNotEmpty, decltype(std::declval<ValT>().isEmpty())> {
template <typename ContT, typename ForwardedT>
static void impl(ContT& container, const QString& key,
ForwardedT&& value)
@@ -342,9 +372,9 @@ namespace _impl {
}
};
- // This one unfolds Omittable<> (also only when Force is false)
+ // This one unfolds Omittable<> (also only when IfNotEmpty is requested)
template <typename ValT>
- struct AddNode<Omittable<ValT>, false> {
+ struct AddNode<Omittable<ValT>, IfNotEmpty> {
template <typename ContT, typename OmittableT>
static void impl(ContT& container, const QString& key,
const OmittableT& value)
@@ -355,8 +385,6 @@ namespace _impl {
};
} // namespace _impl
-static constexpr bool IfNotEmpty = false;
-
/*! Add a key-value pair to QJsonObject or QUrlQuery
*
* Adds a key-value pair(s) specified by \p key and \p value to
diff --git a/lib/csapi/account-data.h b/lib/csapi/account-data.h
index 0c760e80..5140d340 100644
--- a/lib/csapi/account-data.h
+++ b/lib/csapi/account-data.h
@@ -14,7 +14,7 @@ namespace Quotient {
* that set the account_data. The config will be synced to clients in the
* top-level `account_data`.
*/
-class SetAccountDataJob : public BaseJob {
+class QUOTIENT_API SetAccountDataJob : public BaseJob {
public:
/*! \brief Set some account_data for the user.
*
@@ -38,7 +38,7 @@ public:
* Get some account_data for the client. This config is only visible to the user
* that set the account_data.
*/
-class GetAccountDataJob : public BaseJob {
+class QUOTIENT_API GetAccountDataJob : public BaseJob {
public:
/*! \brief Get some account_data for the user.
*
@@ -67,7 +67,7 @@ public:
* visible to the user that set the account_data. The config will be synced to
* clients in the per-room `account_data`.
*/
-class SetAccountDataPerRoomJob : public BaseJob {
+class QUOTIENT_API SetAccountDataPerRoomJob : public BaseJob {
public:
/*! \brief Set some account_data for the user.
*
@@ -95,7 +95,7 @@ public:
* Get some account_data for the client on a given room. This config is only
* visible to the user that set the account_data.
*/
-class GetAccountDataPerRoomJob : public BaseJob {
+class QUOTIENT_API GetAccountDataPerRoomJob : public BaseJob {
public:
/*! \brief Get some account_data for the user.
*
diff --git a/lib/csapi/admin.h b/lib/csapi/admin.h
index 570bf24a..c53ddd7e 100644
--- a/lib/csapi/admin.h
+++ b/lib/csapi/admin.h
@@ -16,7 +16,7 @@ namespace Quotient {
* up, or by a server admin. Server-local administrator privileges are not
* specified in this document.
*/
-class GetWhoIsJob : public BaseJob {
+class QUOTIENT_API GetWhoIsJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/administrative_contact.h b/lib/csapi/administrative_contact.h
index e436971d..e636b12a 100644
--- a/lib/csapi/administrative_contact.h
+++ b/lib/csapi/administrative_contact.h
@@ -24,7 +24,7 @@ namespace Quotient {
* Identifiers in this list may be used by the homeserver as, for example,
* identifiers that it will accept to reset the user's account password.
*/
-class GetAccount3PIDsJob : public BaseJob {
+class QUOTIENT_API GetAccount3PIDsJob : public BaseJob {
public:
// Inner data structures
@@ -102,7 +102,7 @@ struct JsonObjectConverter<GetAccount3PIDsJob::ThirdPartyIdentifier> {
* This results in this endpoint being an equivalent to `/3pid/bind` rather
* than dual-purpose.
*/
-class Post3PIDsJob : public BaseJob {
+class QUOTIENT_API Post3PIDsJob : public BaseJob {
public:
// Inner data structures
@@ -154,7 +154,7 @@ struct JsonObjectConverter<Post3PIDsJob::ThreePidCredentials> {
* Homeservers should prevent the caller from adding a 3PID to their account if
* it has already been added to another user's account on the homeserver.
*/
-class Add3PIDJob : public BaseJob {
+class QUOTIENT_API Add3PIDJob : public BaseJob {
public:
/*! \brief Adds contact information to the user's account.
*
@@ -182,7 +182,7 @@ public:
*
* Homeservers should track successful binds so they can be unbound later.
*/
-class Bind3PIDJob : public BaseJob {
+class QUOTIENT_API Bind3PIDJob : public BaseJob {
public:
/*! \brief Binds a 3PID to the user's account through an Identity Service.
*
@@ -211,7 +211,7 @@ public:
* parameter because the homeserver is expected to sign the request to the
* identity server instead.
*/
-class Delete3pidFromAccountJob : public BaseJob {
+class QUOTIENT_API Delete3pidFromAccountJob : public BaseJob {
public:
/*! \brief Deletes a third party identifier from the user's account
*
@@ -254,7 +254,7 @@ public:
* parameter because the homeserver is expected to sign the request to the
* identity server instead.
*/
-class Unbind3pidFromAccountJob : public BaseJob {
+class QUOTIENT_API Unbind3pidFromAccountJob : public BaseJob {
public:
/*! \brief Removes a user's third party identifier from an identity server.
*
@@ -300,7 +300,7 @@ public:
* the email itself, either by sending a validation email itself or by using
* a service it has control over.
*/
-class RequestTokenTo3PIDEmailJob : public BaseJob {
+class QUOTIENT_API RequestTokenTo3PIDEmailJob : public BaseJob {
public:
/*! \brief Begins the validation process for an email address for
* association with the user's account.
@@ -342,7 +342,7 @@ public:
* the phone number itself, either by sending a validation message itself or by
* using a service it has control over.
*/
-class RequestTokenTo3PIDMSISDNJob : public BaseJob {
+class QUOTIENT_API RequestTokenTo3PIDMSISDNJob : public BaseJob {
public:
/*! \brief Begins the validation process for a phone number for association
* with the user's account.
diff --git a/lib/csapi/appservice_room_directory.h b/lib/csapi/appservice_room_directory.h
index 56a69592..6b2801ca 100644
--- a/lib/csapi/appservice_room_directory.h
+++ b/lib/csapi/appservice_room_directory.h
@@ -21,7 +21,8 @@ namespace Quotient {
* instead of a typical client's access_token. This API cannot be invoked by
* users who are not identified as application services.
*/
-class UpdateAppserviceRoomDirectoryVisibilityJob : public BaseJob {
+class QUOTIENT_API UpdateAppserviceRoomDirectoryVisibilityJob
+ : public BaseJob {
public:
/*! \brief Updates a room's visibility in the application service's room
* directory.
diff --git a/lib/csapi/banning.h b/lib/csapi/banning.h
index 7a9697d3..e4c60ce3 100644
--- a/lib/csapi/banning.h
+++ b/lib/csapi/banning.h
@@ -18,7 +18,7 @@ namespace Quotient {
* The caller must have the required power level in order to perform this
* operation.
*/
-class BanJob : public BaseJob {
+class QUOTIENT_API BanJob : public BaseJob {
public:
/*! \brief Ban a user in the room.
*
@@ -46,7 +46,7 @@ public:
* The caller must have the required power level in order to perform this
* operation.
*/
-class UnbanJob : public BaseJob {
+class QUOTIENT_API UnbanJob : public BaseJob {
public:
/*! \brief Unban a user from the room.
*
diff --git a/lib/csapi/capabilities.h b/lib/csapi/capabilities.h
index da50c8c1..81b47cd4 100644
--- a/lib/csapi/capabilities.h
+++ b/lib/csapi/capabilities.h
@@ -13,7 +13,7 @@ namespace Quotient {
* Gets information about the server's supported feature set
* and other relevant capabilities.
*/
-class GetCapabilitiesJob : public BaseJob {
+class QUOTIENT_API GetCapabilitiesJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/content-repo.h b/lib/csapi/content-repo.h
index 28409f5c..511db985 100644
--- a/lib/csapi/content-repo.h
+++ b/lib/csapi/content-repo.h
@@ -14,7 +14,7 @@ namespace Quotient {
/*! \brief Upload some content to the content repository.
*
*/
-class UploadContentJob : public BaseJob {
+class QUOTIENT_API UploadContentJob : public BaseJob {
public:
/*! \brief Upload some content to the content repository.
*
@@ -40,7 +40,7 @@ public:
/*! \brief Download content from the content repository.
*
*/
-class GetContentJob : public BaseJob {
+class QUOTIENT_API GetContentJob : public BaseJob {
public:
/*! \brief Download content from the content repository.
*
@@ -87,7 +87,7 @@ public:
* the previous endpoint) but replace the target file name with the one
* provided by the caller.
*/
-class GetContentOverrideNameJob : public BaseJob {
+class QUOTIENT_API GetContentOverrideNameJob : public BaseJob {
public:
/*! \brief Download content from the content repository overriding the file
* name
@@ -142,7 +142,7 @@ public:
* See the [Thumbnails](/client-server-api/#thumbnails) section for more
* information.
*/
-class GetContentThumbnailJob : public BaseJob {
+class QUOTIENT_API GetContentThumbnailJob : public BaseJob {
public:
/*! \brief Download a thumbnail of content from the content repository
*
@@ -204,7 +204,7 @@ public:
* do not want to share with the homeserver, and this can mean that the URLs
* being shared should also not be shared with the homeserver.
*/
-class GetUrlPreviewJob : public BaseJob {
+class QUOTIENT_API GetUrlPreviewJob : public BaseJob {
public:
/*! \brief Get information about a URL for a client
*
@@ -252,7 +252,7 @@ public:
* content repository APIs, for example, proxies may enforce a lower upload size
* limit than is advertised by the server on this endpoint.
*/
-class GetConfigJob : public BaseJob {
+class QUOTIENT_API GetConfigJob : public BaseJob {
public:
/// Get the configuration for the content repository.
explicit GetConfigJob();
diff --git a/lib/csapi/create_room.h b/lib/csapi/create_room.h
index 81dfbffc..7d566057 100644
--- a/lib/csapi/create_room.h
+++ b/lib/csapi/create_room.h
@@ -53,7 +53,7 @@ namespace Quotient {
* requesting user as the creator, alongside other keys provided in the
* `creation_content`.
*/
-class CreateRoomJob : public BaseJob {
+class QUOTIENT_API CreateRoomJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/cross_signing.h b/lib/csapi/cross_signing.h
index 2ab65e06..617b61d1 100644
--- a/lib/csapi/cross_signing.h
+++ b/lib/csapi/cross_signing.h
@@ -17,7 +17,7 @@ namespace Quotient {
* This API endpoint uses the [User-Interactive Authentication
* API](/client-server-api/#user-interactive-authentication-api).
*/
-class UploadCrossSigningKeysJob : public BaseJob {
+class QUOTIENT_API UploadCrossSigningKeysJob : public BaseJob {
public:
/*! \brief Upload cross-signing keys.
*
@@ -47,7 +47,7 @@ public:
* Publishes cross-signing signatures for the user. The request body is a
* map from user ID to key ID to signed JSON object.
*/
-class UploadCrossSigningSignaturesJob : public BaseJob {
+class QUOTIENT_API UploadCrossSigningSignaturesJob : public BaseJob {
public:
/*! \brief Upload cross-signing signatures.
*
diff --git a/lib/csapi/device_management.h b/lib/csapi/device_management.h
index 7fb69873..430d2132 100644
--- a/lib/csapi/device_management.h
+++ b/lib/csapi/device_management.h
@@ -15,7 +15,7 @@ namespace Quotient {
*
* Gets information about all devices for the current user.
*/
-class GetDevicesJob : public BaseJob {
+class QUOTIENT_API GetDevicesJob : public BaseJob {
public:
/// List registered devices for the current user
explicit GetDevicesJob();
@@ -40,7 +40,7 @@ public:
*
* Gets information on a single device, by device id.
*/
-class GetDeviceJob : public BaseJob {
+class QUOTIENT_API GetDeviceJob : public BaseJob {
public:
/*! \brief Get a single device
*
@@ -66,7 +66,7 @@ public:
*
* Updates the metadata on the given device.
*/
-class UpdateDeviceJob : public BaseJob {
+class QUOTIENT_API UpdateDeviceJob : public BaseJob {
public:
/*! \brief Update a device
*
@@ -88,7 +88,7 @@ public:
*
* Deletes the given device, and invalidates any access token associated with it.
*/
-class DeleteDeviceJob : public BaseJob {
+class QUOTIENT_API DeleteDeviceJob : public BaseJob {
public:
/*! \brief Delete a device
*
@@ -111,7 +111,7 @@ public:
* Deletes the given devices, and invalidates any access token associated with
* them.
*/
-class DeleteDevicesJob : public BaseJob {
+class QUOTIENT_API DeleteDevicesJob : public BaseJob {
public:
/*! \brief Bulk deletion of devices
*
diff --git a/lib/csapi/directory.h b/lib/csapi/directory.h
index 93a31595..0bd13a76 100644
--- a/lib/csapi/directory.h
+++ b/lib/csapi/directory.h
@@ -11,7 +11,7 @@ namespace Quotient {
/*! \brief Create a new mapping from room alias to room ID.
*
*/
-class SetRoomAliasJob : public BaseJob {
+class QUOTIENT_API SetRoomAliasJob : public BaseJob {
public:
/*! \brief Create a new mapping from room alias to room ID.
*
@@ -32,7 +32,7 @@ public:
* domain part of the alias does not correspond to the server's own
* domain.
*/
-class GetRoomIdByAliasJob : public BaseJob {
+class QUOTIENT_API GetRoomIdByAliasJob : public BaseJob {
public:
/*! \brief Get the room ID corresponding to this room alias.
*
@@ -76,7 +76,7 @@ public:
* return a successful response even if the user does not have permission to
* update the `m.room.canonical_alias` event.
*/
-class DeleteRoomAliasJob : public BaseJob {
+class QUOTIENT_API DeleteRoomAliasJob : public BaseJob {
public:
/*! \brief Remove a mapping of room alias to room ID.
*
@@ -112,7 +112,7 @@ public:
* as they are not curated, unlike those listed in the `m.room.canonical_alias`
* state event.
*/
-class GetLocalAliasesJob : public BaseJob {
+class QUOTIENT_API GetLocalAliasesJob : public BaseJob {
public:
/*! \brief Get a list of local aliases on a given room.
*
diff --git a/lib/csapi/event_context.h b/lib/csapi/event_context.h
index 4e50edf3..662b976b 100644
--- a/lib/csapi/event_context.h
+++ b/lib/csapi/event_context.h
@@ -19,7 +19,7 @@ namespace Quotient {
* [Lazy-loading room members](/client-server-api/#lazy-loading-room-members)
* for more information.
*/
-class GetEventContextJob : public BaseJob {
+class QUOTIENT_API GetEventContextJob : public BaseJob {
public:
/*! \brief Get events and state around the specified event.
*
diff --git a/lib/csapi/filter.h b/lib/csapi/filter.h
index 01bec36b..9518a461 100644
--- a/lib/csapi/filter.h
+++ b/lib/csapi/filter.h
@@ -16,7 +16,7 @@ namespace Quotient {
* Returns a filter ID that may be used in future requests to
* restrict which events are returned to the client.
*/
-class DefineFilterJob : public BaseJob {
+class QUOTIENT_API DefineFilterJob : public BaseJob {
public:
/*! \brief Upload a new filter.
*
@@ -41,7 +41,7 @@ public:
/*! \brief Download a filter
*
*/
-class GetFilterJob : public BaseJob {
+class QUOTIENT_API GetFilterJob : public BaseJob {
public:
/*! \brief Download a filter
*
diff --git a/lib/csapi/inviting.h b/lib/csapi/inviting.h
index eb13cc95..21e6cb74 100644
--- a/lib/csapi/inviting.h
+++ b/lib/csapi/inviting.h
@@ -26,7 +26,7 @@ namespace Quotient {
* If the user was invited to the room, the homeserver will append a
* `m.room.member` event to the room.
*/
-class InviteUserJob : public BaseJob {
+class QUOTIENT_API InviteUserJob : public BaseJob {
public:
/*! \brief Invite a user to participate in a particular room.
*
diff --git a/lib/csapi/joining.h b/lib/csapi/joining.h
index d0199b11..f64152f7 100644
--- a/lib/csapi/joining.h
+++ b/lib/csapi/joining.h
@@ -25,7 +25,7 @@ namespace Quotient {
* [`/initialSync`](/client-server-api/#get_matrixclientr0initialsync) and
* [`/sync`](/client-server-api/#get_matrixclientr0sync) APIs.
*/
-class JoinRoomByIdJob : public BaseJob {
+class QUOTIENT_API JoinRoomByIdJob : public BaseJob {
public:
/*! \brief Start the requesting user participating in a particular room.
*
@@ -67,7 +67,7 @@ public:
* [`/initialSync`](/client-server-api/#get_matrixclientr0initialsync) and
* [`/sync`](/client-server-api/#get_matrixclientr0sync) APIs.
*/
-class JoinRoomJob : public BaseJob {
+class QUOTIENT_API JoinRoomJob : public BaseJob {
public:
/*! \brief Start the requesting user participating in a particular room.
*
diff --git a/lib/csapi/keys.h b/lib/csapi/keys.h
index 7db09e8d..ce1ca9ed 100644
--- a/lib/csapi/keys.h
+++ b/lib/csapi/keys.h
@@ -15,7 +15,7 @@ namespace Quotient {
*
* Publishes end-to-end encryption keys for the device.
*/
-class UploadKeysJob : public BaseJob {
+class QUOTIENT_API UploadKeysJob : public BaseJob {
public:
/*! \brief Upload end-to-end encryption keys.
*
@@ -48,7 +48,7 @@ public:
*
* Returns the current devices and identity keys for the given users.
*/
-class QueryKeysJob : public BaseJob {
+class QUOTIENT_API QueryKeysJob : public BaseJob {
public:
// Inner data structures
@@ -172,7 +172,7 @@ struct JsonObjectConverter<QueryKeysJob::DeviceInformation> {
*
* Claims one-time keys for use in pre-key messages.
*/
-class ClaimKeysJob : public BaseJob {
+class QUOTIENT_API ClaimKeysJob : public BaseJob {
public:
/*! \brief Claim one-time encryption keys.
*
@@ -226,7 +226,7 @@ public:
* * added new device identity keys or removed an existing device with
* identity keys, between `from` and `to`.
*/
-class GetKeysChangesJob : public BaseJob {
+class QUOTIENT_API GetKeysChangesJob : public BaseJob {
public:
/*! \brief Query users with recent device key updates.
*
diff --git a/lib/csapi/kicking.h b/lib/csapi/kicking.h
index 11018368..6ac106e2 100644
--- a/lib/csapi/kicking.h
+++ b/lib/csapi/kicking.h
@@ -20,7 +20,7 @@ namespace Quotient {
* directly adjust the target member's state by making a request to
* `/rooms/<room id>/state/m.room.member/<user id>`.
*/
-class KickJob : public BaseJob {
+class QUOTIENT_API KickJob : public BaseJob {
public:
/*! \brief Kick a user from the room.
*
diff --git a/lib/csapi/knocking.h b/lib/csapi/knocking.h
index 1108cb64..e3645b59 100644
--- a/lib/csapi/knocking.h
+++ b/lib/csapi/knocking.h
@@ -27,7 +27,7 @@ namespace Quotient {
* The knock will appear as an entry in the response of the
* [`/sync`](/client-server-api/#get_matrixclientr0sync) API.
*/
-class KnockRoomJob : public BaseJob {
+class QUOTIENT_API KnockRoomJob : public BaseJob {
public:
/*! \brief Knock on a room, requesting permission to join.
*
diff --git a/lib/csapi/leaving.h b/lib/csapi/leaving.h
index 2e402d16..19cac3f0 100644
--- a/lib/csapi/leaving.h
+++ b/lib/csapi/leaving.h
@@ -22,7 +22,7 @@ namespace Quotient {
* The user will still be allowed to retrieve history from the room which
* they were previously allowed to see.
*/
-class LeaveRoomJob : public BaseJob {
+class QUOTIENT_API LeaveRoomJob : public BaseJob {
public:
/*! \brief Stop the requesting user participating in a particular room.
*
@@ -48,7 +48,7 @@ public:
* If the user is currently joined to the room, they must leave the room
* before calling this API.
*/
-class ForgetRoomJob : public BaseJob {
+class QUOTIENT_API ForgetRoomJob : public BaseJob {
public:
/*! \brief Stop the requesting user remembering about a particular room.
*
diff --git a/lib/csapi/list_joined_rooms.h b/lib/csapi/list_joined_rooms.h
index 59a24a49..aea68afd 100644
--- a/lib/csapi/list_joined_rooms.h
+++ b/lib/csapi/list_joined_rooms.h
@@ -12,7 +12,7 @@ namespace Quotient {
*
* This API returns a list of the user's current rooms.
*/
-class GetJoinedRoomsJob : public BaseJob {
+class QUOTIENT_API GetJoinedRoomsJob : public BaseJob {
public:
/// Lists the user's current rooms.
explicit GetJoinedRoomsJob();
diff --git a/lib/csapi/list_public_rooms.h b/lib/csapi/list_public_rooms.h
index 963c8b56..e1f03db7 100644
--- a/lib/csapi/list_public_rooms.h
+++ b/lib/csapi/list_public_rooms.h
@@ -14,7 +14,7 @@ namespace Quotient {
*
* Gets the visibility of a given room on the server's public room directory.
*/
-class GetRoomVisibilityOnDirectoryJob : public BaseJob {
+class QUOTIENT_API GetRoomVisibilityOnDirectoryJob : public BaseJob {
public:
/*! \brief Gets the visibility of a room in the directory
*
@@ -48,7 +48,7 @@ public:
* here, for instance that room visibility can only be changed by
* the room creator or a server administrator.
*/
-class SetRoomVisibilityOnDirectoryJob : public BaseJob {
+class QUOTIENT_API SetRoomVisibilityOnDirectoryJob : public BaseJob {
public:
/*! \brief Sets the visibility of a room in the room directory
*
@@ -70,7 +70,7 @@ public:
* This API returns paginated responses. The rooms are ordered by the number
* of joined members, with the largest rooms first.
*/
-class GetPublicRoomsJob : public BaseJob {
+class QUOTIENT_API GetPublicRoomsJob : public BaseJob {
public:
/*! \brief Lists the public rooms on the server.
*
@@ -133,7 +133,7 @@ public:
* This API returns paginated responses. The rooms are ordered by the number
* of joined members, with the largest rooms first.
*/
-class QueryPublicRoomsJob : public BaseJob {
+class QUOTIENT_API QueryPublicRoomsJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/login.h b/lib/csapi/login.h
index b35db1eb..ce6951eb 100644
--- a/lib/csapi/login.h
+++ b/lib/csapi/login.h
@@ -16,7 +16,7 @@ namespace Quotient {
* Gets the homeserver's supported login types to authenticate users. Clients
* should pick one of these and supply it as the `type` when logging in.
*/
-class GetLoginFlowsJob : public BaseJob {
+class QUOTIENT_API GetLoginFlowsJob : public BaseJob {
public:
// Inner data structures
@@ -73,7 +73,7 @@ struct JsonObjectConverter<GetLoginFlowsJob::LoginFlow> {
* [Relationship between access tokens and
* devices](/client-server-api/#relationship-between-access-tokens-and-devices).
*/
-class LoginJob : public BaseJob {
+class QUOTIENT_API LoginJob : public BaseJob {
public:
/*! \brief Authenticates the user.
*
diff --git a/lib/csapi/logout.h b/lib/csapi/logout.h
index 2e4c2692..3f1ac7fa 100644
--- a/lib/csapi/logout.h
+++ b/lib/csapi/logout.h
@@ -15,7 +15,7 @@ namespace Quotient {
* [Device keys](/client-server-api/#device-keys) for the device are deleted
* alongside the device.
*/
-class LogoutJob : public BaseJob {
+class QUOTIENT_API LogoutJob : public BaseJob {
public:
/// Invalidates a user access token
explicit LogoutJob();
@@ -44,7 +44,7 @@ public:
* used in the request, and therefore the attacker is unable to take over the
* account in this way.
*/
-class LogoutAllJob : public BaseJob {
+class QUOTIENT_API LogoutAllJob : public BaseJob {
public:
/// Invalidates all access tokens for a user
explicit LogoutAllJob();
diff --git a/lib/csapi/message_pagination.h b/lib/csapi/message_pagination.h
index 363e4d99..8c18f104 100644
--- a/lib/csapi/message_pagination.h
+++ b/lib/csapi/message_pagination.h
@@ -18,7 +18,7 @@ namespace Quotient {
* [Lazy-loading room members](/client-server-api/#lazy-loading-room-members)
* for more information.
*/
-class GetRoomEventsJob : public BaseJob {
+class QUOTIENT_API GetRoomEventsJob : public BaseJob {
public:
/*! \brief Get a list of events for this room
*
diff --git a/lib/csapi/notifications.h b/lib/csapi/notifications.h
index 0c38fe6b..23211758 100644
--- a/lib/csapi/notifications.h
+++ b/lib/csapi/notifications.h
@@ -14,7 +14,7 @@ namespace Quotient {
* This API is used to paginate through the list of events that the
* user has been, or would have been notified about.
*/
-class GetNotificationsJob : public BaseJob {
+class QUOTIENT_API GetNotificationsJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/openid.h b/lib/csapi/openid.h
index 0be39c8c..773b6011 100644
--- a/lib/csapi/openid.h
+++ b/lib/csapi/openid.h
@@ -21,7 +21,7 @@ namespace Quotient {
* be used to request another OpenID access token or call `/sync`, for
* example.
*/
-class RequestOpenIdTokenJob : public BaseJob {
+class QUOTIENT_API RequestOpenIdTokenJob : public BaseJob {
public:
/*! \brief Get an OpenID token object to verify the requester's identity.
*
diff --git a/lib/csapi/peeking_events.h b/lib/csapi/peeking_events.h
index 885ff340..14cb6f0b 100644
--- a/lib/csapi/peeking_events.h
+++ b/lib/csapi/peeking_events.h
@@ -22,7 +22,7 @@ namespace Quotient {
* API will also be deprecated at some point, but its replacement is not
* yet known.
*/
-class PeekEventsJob : public BaseJob {
+class QUOTIENT_API PeekEventsJob : public BaseJob {
public:
/*! \brief Listen on the event stream.
*
diff --git a/lib/csapi/presence.h b/lib/csapi/presence.h
index 4ab50e25..52445205 100644
--- a/lib/csapi/presence.h
+++ b/lib/csapi/presence.h
@@ -15,7 +15,7 @@ namespace Quotient {
* not need to specify the `last_active_ago` field. You cannot set the
* presence state of another user.
*/
-class SetPresenceJob : public BaseJob {
+class QUOTIENT_API SetPresenceJob : public BaseJob {
public:
/*! \brief Update this user's presence state.
*
@@ -36,7 +36,7 @@ public:
*
* Get the given user's presence state.
*/
-class GetPresenceJob : public BaseJob {
+class QUOTIENT_API GetPresenceJob : public BaseJob {
public:
/*! \brief Get this user's presence state.
*
diff --git a/lib/csapi/profile.h b/lib/csapi/profile.h
index 7f9c9e95..b00c944b 100644
--- a/lib/csapi/profile.h
+++ b/lib/csapi/profile.h
@@ -13,7 +13,7 @@ namespace Quotient {
* This API sets the given user's display name. You must have permission to
* set this user's display name, e.g. you need to have their `access_token`.
*/
-class SetDisplayNameJob : public BaseJob {
+class QUOTIENT_API SetDisplayNameJob : public BaseJob {
public:
/*! \brief Set the user's display name.
*
@@ -33,7 +33,7 @@ public:
* own displayname or to query the name of other users; either locally or
* on remote homeservers.
*/
-class GetDisplayNameJob : public BaseJob {
+class QUOTIENT_API GetDisplayNameJob : public BaseJob {
public:
/*! \brief Get the user's display name.
*
@@ -63,7 +63,7 @@ public:
* This API sets the given user's avatar URL. You must have permission to
* set this user's avatar URL, e.g. you need to have their `access_token`.
*/
-class SetAvatarUrlJob : public BaseJob {
+class QUOTIENT_API SetAvatarUrlJob : public BaseJob {
public:
/*! \brief Set the user's avatar URL.
*
@@ -82,7 +82,7 @@ public:
* own avatar URL or to query the URL of other users; either locally or
* on remote homeservers.
*/
-class GetAvatarUrlJob : public BaseJob {
+class QUOTIENT_API GetAvatarUrlJob : public BaseJob {
public:
/*! \brief Get the user's avatar URL.
*
@@ -111,7 +111,7 @@ public:
* locally or on remote homeservers. This API may return keys which are not
* limited to `displayname` or `avatar_url`.
*/
-class GetUserProfileJob : public BaseJob {
+class QUOTIENT_API GetUserProfileJob : public BaseJob {
public:
/*! \brief Get this user's profile information.
*
diff --git a/lib/csapi/pusher.h b/lib/csapi/pusher.h
index 622b0df6..d859ffc4 100644
--- a/lib/csapi/pusher.h
+++ b/lib/csapi/pusher.h
@@ -12,7 +12,7 @@ namespace Quotient {
*
* Gets all currently active pushers for the authenticated user.
*/
-class GetPushersJob : public BaseJob {
+class QUOTIENT_API GetPushersJob : public BaseJob {
public:
// Inner data structures
@@ -108,7 +108,7 @@ struct JsonObjectConverter<GetPushersJob::Pusher> {
* [pushers](/client-server-api/#push-notifications) for this user ID. The
* behaviour of this endpoint varies depending on the values in the JSON body.
*/
-class PostPusherJob : public BaseJob {
+class QUOTIENT_API PostPusherJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/pushrules.h b/lib/csapi/pushrules.h
index a5eb48f0..d6c57efd 100644
--- a/lib/csapi/pushrules.h
+++ b/lib/csapi/pushrules.h
@@ -19,7 +19,7 @@ namespace Quotient {
* `/pushrules/global/`. This will return a subset of this data under the
* specified key e.g. the `global` key.
*/
-class GetPushRulesJob : public BaseJob {
+class QUOTIENT_API GetPushRulesJob : public BaseJob {
public:
/// Retrieve all push rulesets.
explicit GetPushRulesJob();
@@ -44,7 +44,7 @@ public:
*
* Retrieve a single specified push rule.
*/
-class GetPushRuleJob : public BaseJob {
+class QUOTIENT_API GetPushRuleJob : public BaseJob {
public:
/*! \brief Retrieve a push rule.
*
@@ -79,7 +79,7 @@ public:
*
* This endpoint removes the push rule defined in the path.
*/
-class DeletePushRuleJob : public BaseJob {
+class QUOTIENT_API DeletePushRuleJob : public BaseJob {
public:
/*! \brief Delete a push rule.
*
@@ -112,7 +112,7 @@ public:
*
* When creating push rules, they MUST be enabled by default.
*/
-class SetPushRuleJob : public BaseJob {
+class QUOTIENT_API SetPushRuleJob : public BaseJob {
public:
/*! \brief Add or change a push rule.
*
@@ -160,7 +160,7 @@ public:
*
* This endpoint gets whether the specified push rule is enabled.
*/
-class IsPushRuleEnabledJob : public BaseJob {
+class QUOTIENT_API IsPushRuleEnabledJob : public BaseJob {
public:
/*! \brief Get whether a push rule is enabled
*
@@ -195,7 +195,7 @@ public:
*
* This endpoint allows clients to enable or disable the specified push rule.
*/
-class SetPushRuleEnabledJob : public BaseJob {
+class QUOTIENT_API SetPushRuleEnabledJob : public BaseJob {
public:
/*! \brief Enable or disable a push rule.
*
@@ -219,7 +219,7 @@ public:
*
* This endpoint get the actions for the specified push rule.
*/
-class GetPushRuleActionsJob : public BaseJob {
+class QUOTIENT_API GetPushRuleActionsJob : public BaseJob {
public:
/*! \brief The actions for a push rule
*
@@ -258,7 +258,7 @@ public:
* This endpoint allows clients to change the actions of a push rule.
* This can be used to change the actions of builtin rules.
*/
-class SetPushRuleActionsJob : public BaseJob {
+class QUOTIENT_API SetPushRuleActionsJob : public BaseJob {
public:
/*! \brief Set the actions for a push rule.
*
diff --git a/lib/csapi/read_markers.h b/lib/csapi/read_markers.h
index 00a2aa0d..d13fa4fc 100644
--- a/lib/csapi/read_markers.h
+++ b/lib/csapi/read_markers.h
@@ -13,7 +13,7 @@ namespace Quotient {
* Sets the position of the read marker for a given room, and optionally
* the read receipt's location.
*/
-class SetReadMarkerJob : public BaseJob {
+class QUOTIENT_API SetReadMarkerJob : public BaseJob {
public:
/*! \brief Set the position of the read marker for a room.
*
diff --git a/lib/csapi/receipts.h b/lib/csapi/receipts.h
index 7ac093cd..e29e7b29 100644
--- a/lib/csapi/receipts.h
+++ b/lib/csapi/receipts.h
@@ -13,7 +13,7 @@ namespace Quotient {
* This API updates the marker for the given receipt type to the event ID
* specified.
*/
-class PostReceiptJob : public BaseJob {
+class QUOTIENT_API PostReceiptJob : public BaseJob {
public:
/*! \brief Send a receipt for the given event ID.
*
diff --git a/lib/csapi/redaction.h b/lib/csapi/redaction.h
index f0db9f9f..29d9c5d5 100644
--- a/lib/csapi/redaction.h
+++ b/lib/csapi/redaction.h
@@ -22,7 +22,7 @@ namespace Quotient {
*
* Server administrators may redact events sent by users on their server.
*/
-class RedactEventJob : public BaseJob {
+class QUOTIENT_API RedactEventJob : public BaseJob {
public:
/*! \brief Strips all non-integrity-critical information out of an event.
*
diff --git a/lib/csapi/registration.h b/lib/csapi/registration.h
index c1614f20..10375971 100644
--- a/lib/csapi/registration.h
+++ b/lib/csapi/registration.h
@@ -59,7 +59,7 @@ namespace Quotient {
* Any user ID returned by this API must conform to the grammar given in the
* [Matrix specification](/appendices/#user-identifiers).
*/
-class RegisterJob : public BaseJob {
+class QUOTIENT_API RegisterJob : public BaseJob {
public:
/*! \brief Register for an account on this homeserver.
*
@@ -143,7 +143,7 @@ public:
* should validate the email itself, either by sending a validation email
* itself or by using a service it has control over.
*/
-class RequestTokenToRegisterEmailJob : public BaseJob {
+class QUOTIENT_API RequestTokenToRegisterEmailJob : public BaseJob {
public:
/*! \brief Begins the validation process for an email to be used during
* registration.
@@ -175,7 +175,7 @@ public:
* should validate the phone number itself, either by sending a validation
* message itself or by using a service it has control over.
*/
-class RequestTokenToRegisterMSISDNJob : public BaseJob {
+class QUOTIENT_API RequestTokenToRegisterMSISDNJob : public BaseJob {
public:
/*! \brief Requests a validation token be sent to the given phone number for
* the purpose of registering an account
@@ -215,7 +215,7 @@ public:
* access token provided in the request. Whether other access tokens for
* the user are revoked depends on the request parameters.
*/
-class ChangePasswordJob : public BaseJob {
+class QUOTIENT_API ChangePasswordJob : public BaseJob {
public:
/*! \brief Changes a user's password.
*
@@ -257,7 +257,7 @@ public:
* The homeserver should validate the email itself, either by sending a
* validation email itself or by using a service it has control over.
*/
-class RequestTokenToResetPasswordEmailJob : public BaseJob {
+class QUOTIENT_API RequestTokenToResetPasswordEmailJob : public BaseJob {
public:
/*! \brief Requests a validation token be sent to the given email address
* for the purpose of resetting a user's password
@@ -309,7 +309,7 @@ public:
* The homeserver should validate the phone number itself, either by sending a
* validation message itself or by using a service it has control over.
*/
-class RequestTokenToResetPasswordMSISDNJob : public BaseJob {
+class QUOTIENT_API RequestTokenToResetPasswordMSISDNJob : public BaseJob {
public:
/*! \brief Requests a validation token be sent to the given phone number for
* the purpose of resetting a user's password.
@@ -361,7 +361,7 @@ public:
* parameter because the homeserver is expected to sign the request to the
* identity server instead.
*/
-class DeactivateAccountJob : public BaseJob {
+class QUOTIENT_API DeactivateAccountJob : public BaseJob {
public:
/*! \brief Deactivate a user's account.
*
@@ -411,7 +411,7 @@ public:
* reserve the username. This can mean that the username becomes unavailable
* between checking its availability and attempting to register it.
*/
-class CheckUsernameAvailabilityJob : public BaseJob {
+class QUOTIENT_API CheckUsernameAvailabilityJob : public BaseJob {
public:
/*! \brief Checks to see if a username is available on the server.
*
diff --git a/lib/csapi/report_content.h b/lib/csapi/report_content.h
index e401c2e1..8c533c19 100644
--- a/lib/csapi/report_content.h
+++ b/lib/csapi/report_content.h
@@ -13,7 +13,7 @@ namespace Quotient {
* Reports an event as inappropriate to the server, which may then notify
* the appropriate people.
*/
-class ReportContentJob : public BaseJob {
+class QUOTIENT_API ReportContentJob : public BaseJob {
public:
/*! \brief Reports an event as inappropriate.
*
diff --git a/lib/csapi/room_send.h b/lib/csapi/room_send.h
index 96f5beca..fea3d59d 100644
--- a/lib/csapi/room_send.h
+++ b/lib/csapi/room_send.h
@@ -18,7 +18,7 @@ namespace Quotient {
* fields in this object will vary depending on the type of event. See
* [Room Events](/client-server-api/#room-events) for the m. event specification.
*/
-class SendMessageJob : public BaseJob {
+class QUOTIENT_API SendMessageJob : public BaseJob {
public:
/*! \brief Send a message event to the given room.
*
diff --git a/lib/csapi/room_state.h b/lib/csapi/room_state.h
index f95af223..a00b0947 100644
--- a/lib/csapi/room_state.h
+++ b/lib/csapi/room_state.h
@@ -29,7 +29,7 @@ namespace Quotient {
* state event is to be sent. Servers do not validate aliases which are
* being removed or are already present in the state event.
*/
-class SetRoomStateWithKeyJob : public BaseJob {
+class QUOTIENT_API SetRoomStateWithKeyJob : public BaseJob {
public:
/*! \brief Send a state event to the given room.
*
diff --git a/lib/csapi/room_upgrades.h b/lib/csapi/room_upgrades.h
index 58327587..0432f667 100644
--- a/lib/csapi/room_upgrades.h
+++ b/lib/csapi/room_upgrades.h
@@ -12,7 +12,7 @@ namespace Quotient {
*
* Upgrades the given room to a particular room version.
*/
-class UpgradeRoomJob : public BaseJob {
+class QUOTIENT_API UpgradeRoomJob : public BaseJob {
public:
/*! \brief Upgrades a room to a new room version.
*
diff --git a/lib/csapi/rooms.h b/lib/csapi/rooms.h
index 2620582b..f0815109 100644
--- a/lib/csapi/rooms.h
+++ b/lib/csapi/rooms.h
@@ -15,7 +15,7 @@ namespace Quotient {
* Get a single event based on `roomId/eventId`. You must have permission to
* retrieve this event e.g. by being a member in the room for this event.
*/
-class GetOneRoomEventJob : public BaseJob {
+class QUOTIENT_API GetOneRoomEventJob : public BaseJob {
public:
/*! \brief Get a single event by event ID.
*
@@ -48,7 +48,7 @@ public:
* state of the room. If the user has left the room then the state is
* taken from the state of the room when they left.
*/
-class GetRoomStateWithKeyJob : public BaseJob {
+class QUOTIENT_API GetRoomStateWithKeyJob : public BaseJob {
public:
/*! \brief Get the state identified by the type and key.
*
@@ -80,7 +80,7 @@ public:
*
* Get the state events for the current state of a room.
*/
-class GetRoomStateJob : public BaseJob {
+class QUOTIENT_API GetRoomStateJob : public BaseJob {
public:
/*! \brief Get all state events in the current state of a room.
*
@@ -106,7 +106,7 @@ public:
*
* Get the list of members for this room.
*/
-class GetMembersByRoomJob : public BaseJob {
+class QUOTIENT_API GetMembersByRoomJob : public BaseJob {
public:
/*! \brief Get the m.room.member events for the room.
*
@@ -161,7 +161,7 @@ public:
* respond than `/members` as it can be implemented more efficiently on the
* server.
*/
-class GetJoinedMembersByRoomJob : public BaseJob {
+class QUOTIENT_API GetJoinedMembersByRoomJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/search.h b/lib/csapi/search.h
index 3d02752a..8683413d 100644
--- a/lib/csapi/search.h
+++ b/lib/csapi/search.h
@@ -15,7 +15,7 @@ namespace Quotient {
*
* Performs a full text search across different categories.
*/
-class SearchJob : public BaseJob {
+class QUOTIENT_API SearchJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/sso_login_redirect.h b/lib/csapi/sso_login_redirect.h
index ade1eb7d..f4f81c1e 100644
--- a/lib/csapi/sso_login_redirect.h
+++ b/lib/csapi/sso_login_redirect.h
@@ -17,7 +17,7 @@ namespace Quotient {
* or present a page which lets the user select an IdP to continue
* with in the event multiple are supported by the server.
*/
-class RedirectToSSOJob : public BaseJob {
+class QUOTIENT_API RedirectToSSOJob : public BaseJob {
public:
/*! \brief Redirect the user's browser to the SSO interface.
*
@@ -44,7 +44,7 @@ public:
* The server MUST respond with an HTTP redirect to the SSO interface
* for that IdP.
*/
-class RedirectToIdPJob : public BaseJob {
+class QUOTIENT_API RedirectToIdPJob : public BaseJob {
public:
/*! \brief Redirect the user's browser to the SSO interface for an IdP.
*
diff --git a/lib/csapi/tags.h b/lib/csapi/tags.h
index a854531a..f4250674 100644
--- a/lib/csapi/tags.h
+++ b/lib/csapi/tags.h
@@ -12,7 +12,7 @@ namespace Quotient {
*
* List the tags set by a user on a room.
*/
-class GetRoomTagsJob : public BaseJob {
+class QUOTIENT_API GetRoomTagsJob : public BaseJob {
public:
// Inner data structures
@@ -68,7 +68,7 @@ struct JsonObjectConverter<GetRoomTagsJob::Tag> {
*
* Add a tag to the room.
*/
-class SetRoomTagJob : public BaseJob {
+class QUOTIENT_API SetRoomTagJob : public BaseJob {
public:
/*! \brief Add a tag to a room.
*
@@ -98,7 +98,7 @@ public:
*
* Remove a tag from the room.
*/
-class DeleteRoomTagJob : public BaseJob {
+class QUOTIENT_API DeleteRoomTagJob : public BaseJob {
public:
/*! \brief Remove a tag from the room.
*
diff --git a/lib/csapi/third_party_lookup.h b/lib/csapi/third_party_lookup.h
index 969e767c..30c5346e 100644
--- a/lib/csapi/third_party_lookup.h
+++ b/lib/csapi/third_party_lookup.h
@@ -18,7 +18,7 @@ namespace Quotient {
* homeserver. Includes both the available protocols and all fields
* required for queries against each protocol.
*/
-class GetProtocolsJob : public BaseJob {
+class QUOTIENT_API GetProtocolsJob : public BaseJob {
public:
/// Retrieve metadata about all protocols that a homeserver supports.
explicit GetProtocolsJob();
@@ -45,7 +45,7 @@ public:
* Fetches the metadata from the homeserver about a particular third party
* protocol.
*/
-class GetProtocolMetadataJob : public BaseJob {
+class QUOTIENT_API GetProtocolMetadataJob : public BaseJob {
public:
/*! \brief Retrieve metadata about a specific protocol that the homeserver
* supports.
@@ -82,7 +82,7 @@ public:
* identifier. It should attempt to canonicalise the identifier as much
* as reasonably possible given the network type.
*/
-class QueryLocationByProtocolJob : public BaseJob {
+class QUOTIENT_API QueryLocationByProtocolJob : public BaseJob {
public:
/*! \brief Retrieve Matrix-side portals rooms leading to a third party
* location.
@@ -119,7 +119,7 @@ public:
* Retrieve a Matrix User ID linked to a user on the third party service, given
* a set of user parameters.
*/
-class QueryUserByProtocolJob : public BaseJob {
+class QUOTIENT_API QueryUserByProtocolJob : public BaseJob {
public:
/*! \brief Retrieve the Matrix User ID of a corresponding third party user.
*
@@ -155,7 +155,7 @@ public:
* Retrieve an array of third party network locations from a Matrix room
* alias.
*/
-class QueryLocationByAliasJob : public BaseJob {
+class QUOTIENT_API QueryLocationByAliasJob : public BaseJob {
public:
/*! \brief Reverse-lookup third party locations given a Matrix room alias.
*
@@ -184,7 +184,7 @@ public:
*
* Retrieve an array of third party users from a Matrix User ID.
*/
-class QueryUserByIDJob : public BaseJob {
+class QUOTIENT_API QueryUserByIDJob : public BaseJob {
public:
/*! \brief Reverse-lookup third party users given a Matrix User ID.
*
diff --git a/lib/csapi/third_party_membership.h b/lib/csapi/third_party_membership.h
index a424678f..1edb969e 100644
--- a/lib/csapi/third_party_membership.h
+++ b/lib/csapi/third_party_membership.h
@@ -52,7 +52,7 @@ namespace Quotient {
* If a token is requested from the identity server, the homeserver will
* append a `m.room.third_party_invite` event to the room.
*/
-class InviteBy3PIDJob : public BaseJob {
+class QUOTIENT_API InviteBy3PIDJob : public BaseJob {
public:
/*! \brief Invite a user to participate in a particular room.
*
diff --git a/lib/csapi/to_device.h b/lib/csapi/to_device.h
index 7a237195..5b6e0bfb 100644
--- a/lib/csapi/to_device.h
+++ b/lib/csapi/to_device.h
@@ -13,7 +13,7 @@ namespace Quotient {
* This endpoint is used to send send-to-device events to a set of
* client devices.
*/
-class SendToDeviceJob : public BaseJob {
+class QUOTIENT_API SendToDeviceJob : public BaseJob {
public:
/*! \brief Send an event to a given set of devices.
*
diff --git a/lib/csapi/typing.h b/lib/csapi/typing.h
index 64a310d0..234e91b0 100644
--- a/lib/csapi/typing.h
+++ b/lib/csapi/typing.h
@@ -15,7 +15,7 @@ namespace Quotient {
* Alternatively, if `typing` is `false`, it tells the server that the
* user has stopped typing.
*/
-class SetTypingJob : public BaseJob {
+class QUOTIENT_API SetTypingJob : public BaseJob {
public:
/*! \brief Informs the server that the user has started or stopped typing.
*
diff --git a/lib/csapi/users.h b/lib/csapi/users.h
index ec186592..3c99758b 100644
--- a/lib/csapi/users.h
+++ b/lib/csapi/users.h
@@ -21,7 +21,7 @@ namespace Quotient {
* names preferably using a collation determined based upon the
* `Accept-Language` header provided in the request, if present.
*/
-class SearchUserDirectoryJob : public BaseJob {
+class QUOTIENT_API SearchUserDirectoryJob : public BaseJob {
public:
// Inner data structures
diff --git a/lib/csapi/versions.h b/lib/csapi/versions.h
index 896e2ea9..4445dbd2 100644
--- a/lib/csapi/versions.h
+++ b/lib/csapi/versions.h
@@ -31,7 +31,7 @@ namespace Quotient {
* upgrade appropriately. Additionally, clients should avoid using unstable
* features in their stable releases.
*/
-class GetVersionsJob : public BaseJob {
+class QUOTIENT_API GetVersionsJob : public BaseJob {
public:
/// Gets the versions of the specification supported by the server.
explicit GetVersionsJob();
diff --git a/lib/csapi/voip.h b/lib/csapi/voip.h
index 087ebbbd..38904f60 100644
--- a/lib/csapi/voip.h
+++ b/lib/csapi/voip.h
@@ -13,7 +13,7 @@ namespace Quotient {
* This API provides credentials for the client to use when initiating
* calls.
*/
-class GetTurnServerJob : public BaseJob {
+class QUOTIENT_API GetTurnServerJob : public BaseJob {
public:
/// Obtain TURN server credentials.
explicit GetTurnServerJob();
diff --git a/lib/csapi/wellknown.h b/lib/csapi/wellknown.h
index c707d232..8615191c 100644
--- a/lib/csapi/wellknown.h
+++ b/lib/csapi/wellknown.h
@@ -21,7 +21,7 @@ namespace Quotient {
* Note that this endpoint is not necessarily handled by the homeserver,
* but by another webserver, to be used for discovering the homeserver URL.
*/
-class GetWellknownJob : public BaseJob {
+class QUOTIENT_API GetWellknownJob : public BaseJob {
public:
/// Gets Matrix server discovery information about the domain.
explicit GetWellknownJob();
diff --git a/lib/csapi/whoami.h b/lib/csapi/whoami.h
index 319f82c5..fba099f6 100644
--- a/lib/csapi/whoami.h
+++ b/lib/csapi/whoami.h
@@ -19,7 +19,7 @@ namespace Quotient {
* is registered by the appservice, and return it in the response
* body.
*/
-class GetTokenOwnerJob : public BaseJob {
+class QUOTIENT_API GetTokenOwnerJob : public BaseJob {
public:
/// Gets information about the owner of an access token.
explicit GetTokenOwnerJob();
diff --git a/lib/eventitem.cpp b/lib/eventitem.cpp
index 4f1595bc..a2d65d8d 100644
--- a/lib/eventitem.cpp
+++ b/lib/eventitem.cpp
@@ -25,3 +25,7 @@ void PendingEventItem::setFileUploaded(const QUrl& remoteUrl)
}
setStatus(EventStatus::FileUploaded);
}
+
+// Not exactly sure why but this helps with the linker not finding
+// Quotient::EventStatus::staticMetaObject when building Quaternion
+#include "moc_eventitem.cpp"
diff --git a/lib/eventitem.h b/lib/eventitem.h
index 0ab1a01d..f04ef323 100644
--- a/lib/eventitem.h
+++ b/lib/eventitem.h
@@ -4,6 +4,7 @@
#pragma once
#include "events/stateevent.h"
+#include "quotient_common.h"
#include <any>
#include <utility>
@@ -11,7 +12,7 @@
namespace Quotient {
namespace EventStatus {
- Q_NAMESPACE
+ QUO_NAMESPACE
/** Special marks an event can assume
*
@@ -33,7 +34,7 @@ namespace EventStatus {
Q_ENUM_NS(Code)
} // namespace EventStatus
-class EventItemBase {
+class QUOTIENT_API EventItemBase {
public:
explicit EventItemBase(RoomEventPtr&& e) : evt(std::move(e))
{
@@ -57,7 +58,7 @@ public:
}
/// Store arbitrary data with the event item
- void setUserData(std::any userData) { data = userData; }
+ void setUserData(std::any userData) { data = std::move(userData); }
/// Obtain custom data previously stored with the event item
const std::any& userdata() const { return data; }
std::any& userData() { return data; }
@@ -74,7 +75,7 @@ private:
std::any data;
};
-class TimelineItem : public EventItemBase {
+class QUOTIENT_API TimelineItem : public EventItemBase {
public:
// For compatibility with Qt containers, even though we use
// a std:: container now for the room timeline
@@ -103,7 +104,7 @@ inline const CallEventBase* EventItemBase::viewAs<CallEventBase>() const
return evt->isCallEvent() ? weakPtrCast<const CallEventBase>(evt) : nullptr;
}
-class PendingEventItem : public EventItemBase {
+class QUOTIENT_API PendingEventItem : public EventItemBase {
public:
using EventItemBase::EventItemBase;
diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h
index e5101d20..12f1f00b 100644
--- a/lib/events/accountdataevents.h
+++ b/lib/events/accountdataevents.h
@@ -4,28 +4,24 @@
#pragma once
#include "event.h"
-#include "eventcontent.h"
+#include "util.h"
namespace Quotient {
-constexpr const char* FavouriteTag = "m.favourite";
-constexpr const char* LowPriorityTag = "m.lowpriority";
-constexpr const char* ServerNoticeTag = "m.server_notice";
+constexpr auto FavouriteTag [[maybe_unused]] = "m.favourite"_ls;
+constexpr auto LowPriorityTag [[maybe_unused]] = "m.lowpriority"_ls;
+constexpr auto ServerNoticeTag [[maybe_unused]] = "m.server_notice"_ls;
struct TagRecord {
- using order_type = Omittable<float>;
-
- order_type order;
-
- TagRecord(order_type order = none) : order(std::move(order)) {}
-
- bool operator<(const TagRecord& other) const
- {
- // Per The Spec, rooms with no order should be after those with order,
- // against optional<>::operator<() convention.
- return order && (!other.order || *order < *other.order);
- }
+ Omittable<float> order = none;
};
+inline bool operator<(TagRecord lhs, TagRecord rhs)
+{
+ // Per The Spec, rooms with no order should be after those with order,
+ // against std::optional<>::operator<() convention.
+ return lhs.order && (!rhs.order || *lhs.order < *rhs.order);
+}
+
template <>
struct JsonObjectConverter<TagRecord> {
static void fillFrom(const QJsonObject& jo, TagRecord& rec)
@@ -42,7 +38,7 @@ struct JsonObjectConverter<TagRecord> {
rec.order = none;
}
}
- static void dumpTo(QJsonObject& jo, const TagRecord& rec)
+ static void dumpTo(QJsonObject& jo, TagRecord rec)
{
addParam<IfNotEmpty>(jo, QStringLiteral("order"), rec.order);
}
@@ -51,7 +47,7 @@ struct JsonObjectConverter<TagRecord> {
using TagsMap = QHash<QString, TagRecord>;
#define DEFINE_SIMPLE_EVENT(_Name, _TypeId, _ContentType, _ContentKey) \
- class _Name : public Event { \
+ class QUOTIENT_API _Name : public Event { \
public: \
using content_type = _ContentType; \
DEFINE_EVENT_TYPEID(_TypeId, _Name) \
diff --git a/lib/events/callanswerevent.h b/lib/events/callanswerevent.h
index 4c01c941..8ffe60f2 100644
--- a/lib/events/callanswerevent.h
+++ b/lib/events/callanswerevent.h
@@ -7,7 +7,7 @@
#include "roomevent.h"
namespace Quotient {
-class CallAnswerEvent : public CallEventBase {
+class QUOTIENT_API CallAnswerEvent : public CallEventBase {
public:
DEFINE_EVENT_TYPEID("m.call.answer", CallAnswerEvent)
@@ -26,6 +26,5 @@ public:
return contentPart<QJsonObject>("answer"_ls).value("sdp"_ls).toString();
}
};
-
REGISTER_EVENT_TYPE(CallAnswerEvent)
} // namespace Quotient
diff --git a/lib/events/callcandidatesevent.cpp b/lib/events/callcandidatesevent.cpp
deleted file mode 100644
index b87c8e9b..00000000
--- a/lib/events/callcandidatesevent.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-FileCopyrightText: 2017 Marius Gripsgard <marius@ubports.com>
-// SPDX-FileCopyrightText: 2018 Josip Delic <delijati@googlemail.com>
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-#include "callcandidatesevent.h"
-
-/*
-m.call.candidates
-{
- "age": 242352,
- "content": {
- "call_id": "12345",
- "candidates": [
- {
- "candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156
-43670 typ host generation 0", "sdpMLineIndex": 0, "sdpMid": "audio"
- }
- ],
- "version": 0
- },
- "event_id": "$WLGTSEFSEF:localhost",
- "origin_server_ts": 1431961217939,
- "room_id": "!Cuyf34gef24t:localhost",
- "sender": "@example:localhost",
- "type": "m.call.candidates"
-}
-*/
diff --git a/lib/events/callhangupevent.cpp b/lib/events/callhangupevent.cpp
deleted file mode 100644
index 43bc4db0..00000000
--- a/lib/events/callhangupevent.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/******************************************************************************
- * SPDX-FileCopyrightText: 2017 Marius Gripsgard <marius@ubports.com>
- * SPDX-FileCopyrightText: 2018 Josip Delic <delijati@googlemail.com>
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "callhangupevent.h"
-
-/*
-m.call.hangup
-{
- "age": 242352,
- "content": {
- "call_id": "12345",
- "version": 0
- },
- "event_id": "$WLGTSEFSEF:localhost",
- "origin_server_ts": 1431961217939,
- "room_id": "!Cuyf34gef24t:localhost",
- "sender": "@example:localhost",
- "type": "m.call.hangup"
-}
-*/
-
-using namespace Quotient;
-
-CallHangupEvent::CallHangupEvent(const QJsonObject& obj)
- : CallEventBase(typeId(), obj)
-{
- qCDebug(EVENTS) << "Call Hangup event";
-}
-
-CallHangupEvent::CallHangupEvent(const QString& callId)
- : CallEventBase(typeId(), matrixTypeId(), callId, 0)
-{}
diff --git a/lib/events/callhangupevent.h b/lib/events/callhangupevent.h
index 24382ac2..b0017c59 100644
--- a/lib/events/callhangupevent.h
+++ b/lib/events/callhangupevent.h
@@ -7,12 +7,16 @@
#include "roomevent.h"
namespace Quotient {
-class CallHangupEvent : public CallEventBase {
+class QUOTIENT_API CallHangupEvent : public CallEventBase {
public:
DEFINE_EVENT_TYPEID("m.call.hangup", CallHangupEvent)
- explicit CallHangupEvent(const QJsonObject& obj);
- explicit CallHangupEvent(const QString& callId);
+ explicit CallHangupEvent(const QJsonObject& obj)
+ : CallEventBase(typeId(), obj)
+ {}
+ explicit CallHangupEvent(const QString& callId)
+ : CallEventBase(typeId(), matrixTypeId(), callId, 0)
+ {}
};
REGISTER_EVENT_TYPE(CallHangupEvent)
diff --git a/lib/events/callinviteevent.h b/lib/events/callinviteevent.h
index 80b7d651..47362b5c 100644
--- a/lib/events/callinviteevent.h
+++ b/lib/events/callinviteevent.h
@@ -7,7 +7,7 @@
#include "roomevent.h"
namespace Quotient {
-class CallInviteEvent : public CallEventBase {
+class QUOTIENT_API CallInviteEvent : public CallEventBase {
public:
DEFINE_EVENT_TYPEID("m.call.invite", CallInviteEvent)
diff --git a/lib/events/directchatevent.h b/lib/events/directchatevent.h
index e2143779..2018d3d6 100644
--- a/lib/events/directchatevent.h
+++ b/lib/events/directchatevent.h
@@ -6,7 +6,7 @@
#include "event.h"
namespace Quotient {
-class DirectChatEvent : public Event {
+class QUOTIENT_API DirectChatEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.direct", DirectChatEvent)
diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h
index 4cc3bf8e..c838bbd8 100644
--- a/lib/events/encryptedevent.h
+++ b/lib/events/encryptedevent.h
@@ -25,7 +25,7 @@ namespace Quotient {
* in general. It's possible, because RoomEvent interface is similar to Event's
* one and doesn't add new restrictions, just provides additional features.
*/
-class EncryptedEvent : public RoomEvent {
+class QUOTIENT_API EncryptedEvent : public RoomEvent {
public:
DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent)
diff --git a/lib/events/encryptionevent.h b/lib/events/encryptionevent.h
index 14439fcc..124ced33 100644
--- a/lib/events/encryptionevent.h
+++ b/lib/events/encryptionevent.h
@@ -6,13 +6,18 @@
#include "eventcontent.h"
#include "stateevent.h"
+#include "quotient_common.h"
namespace Quotient {
-class EncryptionEventContent : public EventContent::Base {
+class QUOTIENT_API EncryptionEventContent : public EventContent::Base {
public:
enum EncryptionType : size_t { MegolmV1AesSha2 = 0, Undefined };
- explicit EncryptionEventContent(EncryptionType et = Undefined);
+ QUO_IMPLICIT EncryptionEventContent(EncryptionType et);
+ [[deprecated("This constructor will require explicit EncryptionType soon")]] //
+ explicit EncryptionEventContent()
+ : EncryptionEventContent(Undefined)
+ {}
explicit EncryptionEventContent(const QJsonObject& json);
EncryptionType encryption;
@@ -26,7 +31,7 @@ protected:
using EncryptionType = EncryptionEventContent::EncryptionType;
-class EncryptionEvent : public StateEvent<EncryptionEventContent> {
+class QUOTIENT_API EncryptionEvent : public StateEvent<EncryptionEventContent> {
Q_GADGET
public:
DEFINE_EVENT_TYPEID("m.room.encryption", EncryptionEvent)
@@ -34,15 +39,15 @@ public:
using EncryptionType = EncryptionEventContent::EncryptionType;
Q_ENUM(EncryptionType)
- explicit EncryptionEvent(const QJsonObject& obj = {}) // TODO: apropriate
- // default value
+ explicit EncryptionEvent(const QJsonObject& obj)
: StateEvent(typeId(), obj)
{}
- EncryptionEvent(EncryptionEvent&&) = delete;
- template <typename... ArgTs>
- EncryptionEvent(ArgTs&&... contentArgs)
- : StateEvent(typeId(), matrixTypeId(), QString(),
- std::forward<ArgTs>(contentArgs)...)
+ [[deprecated("This constructor will require an explicit parameter soon")]] //
+// explicit EncryptionEvent()
+// : EncryptionEvent(QJsonObject())
+// {}
+ explicit EncryptionEvent(EncryptionEventContent&& content)
+ : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content))
{}
EncryptionType encryption() const { return content().encryption; }
@@ -51,6 +56,5 @@ public:
int rotationPeriodMs() const { return content().rotationPeriodMs; }
int rotationPeriodMsgs() const { return content().rotationPeriodMsgs; }
};
-
REGISTER_EVENT_TYPE(EncryptionEvent)
} // namespace Quotient
diff --git a/lib/events/event.cpp b/lib/events/event.cpp
index 96be717c..4c304a3c 100644
--- a/lib/events/event.cpp
+++ b/lib/events/event.cpp
@@ -9,22 +9,14 @@
using namespace Quotient;
-event_type_t EventTypeRegistry::initializeTypeId(event_mtype_t matrixTypeId)
-{
- const auto id = get().eventTypes.size();
- get().eventTypes.push_back(matrixTypeId);
- if (strncmp(matrixTypeId, "", 1) == 0)
- qDebug(EVENTS) << "Initialized unknown event type with id" << id;
- else
- qDebug(EVENTS) << "Initialized event type" << matrixTypeId << "with id"
- << id;
- return id;
-}
+QString EventTypeRegistry::getMatrixType(event_type_t typeId) { return typeId; }
-QString EventTypeRegistry::getMatrixType(event_type_t typeId)
+void _impl::EventFactoryBase::logAddingMethod(event_type_t TypeId,
+ size_t newSize)
{
- return typeId < get().eventTypes.size() ? get().eventTypes[typeId]
- : QString();
+ qDebug(EVENTS) << "Adding factory method for" << TypeId << "events;"
+ << newSize << "methods will be in the" << name
+ << "chain";
}
Event::Event(Type type, const QJsonObject& json) : _type(type), _json(json)
diff --git a/lib/events/event.h b/lib/events/event.h
index 998a386c..113fa3fa 100644
--- a/lib/events/event.h
+++ b/lib/events/event.h
@@ -5,6 +5,7 @@
#include "converters.h"
#include "logging.h"
+#include "function_traits.h"
namespace Quotient {
// === event_ptr_tt<> and type casting facilities ===
@@ -28,24 +29,24 @@ inline TargetEventT* weakPtrCast(const event_ptr_tt<EventT>& ptr)
// === Standard Matrix key names and basicEventJson() ===
-static const auto TypeKey = QStringLiteral("type");
-static const auto BodyKey = QStringLiteral("body");
-static const auto ContentKey = QStringLiteral("content");
-static const auto EventIdKey = QStringLiteral("event_id");
-static const auto SenderKey = QStringLiteral("sender");
-static const auto RoomIdKey = QStringLiteral("room_id");
-static const auto UnsignedKey = QStringLiteral("unsigned");
-static const auto StateKeyKey = QStringLiteral("state_key");
-static const auto TypeKeyL = "type"_ls;
-static const auto BodyKeyL = "body"_ls;
-static const auto ContentKeyL = "content"_ls;
-static const auto EventIdKeyL = "event_id"_ls;
-static const auto SenderKeyL = "sender"_ls;
-static const auto RoomIdKeyL = "room_id"_ls;
-static const auto UnsignedKeyL = "unsigned"_ls;
-static const auto RedactedCauseKeyL = "redacted_because"_ls;
-static const auto PrevContentKeyL = "prev_content"_ls;
-static const auto StateKeyKeyL = "state_key"_ls;
+constexpr auto TypeKeyL = "type"_ls;
+constexpr auto BodyKeyL = "body"_ls;
+constexpr auto ContentKeyL = "content"_ls;
+constexpr auto EventIdKeyL = "event_id"_ls;
+constexpr auto SenderKeyL = "sender"_ls;
+constexpr auto RoomIdKeyL = "room_id"_ls;
+constexpr auto UnsignedKeyL = "unsigned"_ls;
+constexpr auto RedactedCauseKeyL = "redacted_because"_ls;
+constexpr auto PrevContentKeyL = "prev_content"_ls;
+constexpr auto StateKeyKeyL = "state_key"_ls;
+const QString TypeKey { TypeKeyL };
+const QString BodyKey { BodyKeyL };
+const QString ContentKey { ContentKeyL };
+const QString EventIdKey { EventIdKeyL };
+const QString SenderKey { SenderKeyL };
+const QString RoomIdKey { RoomIdKeyL };
+const QString UnsignedKey { UnsignedKeyL };
+const QString StateKeyKey { StateKeyKeyL };
/// Make a minimal correct Matrix event JSON
inline QJsonObject basicEventJson(const QString& matrixType,
@@ -54,149 +55,135 @@ inline QJsonObject basicEventJson(const QString& matrixType,
return { { TypeKey, matrixType }, { ContentKey, content } };
}
-// === Event types and event types registry ===
+// === Event types ===
-using event_type_t = size_t;
+using event_type_t = QLatin1String;
using event_mtype_t = const char*;
-class EventTypeRegistry {
+class QUOTIENT_API EventTypeRegistry {
public:
~EventTypeRegistry() = default;
- static event_type_t initializeTypeId(event_mtype_t matrixTypeId);
-
- template <typename EventT>
- static inline event_type_t initializeTypeId()
- {
- return initializeTypeId(EventT::matrixTypeId());
- }
-
+ [[deprecated("event_type_t is a string now, use it directly instead")]]
static QString getMatrixType(event_type_t typeId);
private:
EventTypeRegistry() = default;
Q_DISABLE_COPY_MOVE(EventTypeRegistry)
-
- static EventTypeRegistry& get()
- {
- static EventTypeRegistry etr;
- return etr;
- }
-
- std::vector<event_mtype_t> eventTypes;
-};
-
-template <>
-inline event_type_t EventTypeRegistry::initializeTypeId<void>()
-{
- return initializeTypeId("");
-}
-
-template <typename EventT>
-struct EventTypeTraits {
- static event_type_t id()
- {
- static const auto id = EventTypeRegistry::initializeTypeId<EventT>();
- return id;
- }
};
template <typename EventT>
-inline event_type_t typeId()
+constexpr event_type_t typeId()
{
- return EventTypeTraits<std::decay_t<EventT>>::id();
+ return std::decay_t<EventT>::TypeId;
}
-inline event_type_t unknownEventTypeId() { return typeId<void>(); }
+constexpr event_type_t UnknownEventTypeId = "?"_ls;
+[[deprecated("Use UnknownEventTypeId")]]
+constexpr event_type_t unknownEventTypeId() { return UnknownEventTypeId; }
-// === EventFactory ===
+// === Event creation facilities ===
-/** Create an event of arbitrary type from its arguments */
+//! Create an event of arbitrary type from its arguments
template <typename EventT, typename... ArgTs>
inline event_ptr_tt<EventT> makeEvent(ArgTs&&... args)
{
return std::make_unique<EventT>(std::forward<ArgTs>(args)...);
}
+namespace _impl {
+ class QUOTIENT_API EventFactoryBase {
+ public:
+ EventFactoryBase(const EventFactoryBase&) = delete;
+
+ protected: // This class is only to inherit from
+ explicit EventFactoryBase(const char* name)
+ : name(name)
+ {}
+ void logAddingMethod(event_type_t TypeId, size_t newSize);
+
+ private:
+ const char* const name;
+ };
+} // namespace _impl
+
+//! \brief A family of event factories to create events from CS API responses
+//!
+//! Each of these factories, as instantiated by event base types (Event,
+//! RoomEvent etc.) is capable of producing an event object derived from
+//! \p BaseEventT, using the JSON payload and the event type passed to its
+//! make() method. Don't use these directly to make events; use loadEvent()
+//! overloads as the frontend for these. Never instantiate new factories
+//! outside of base event classes.
+//! \sa loadEvent, setupFactory, Event::factory, RoomEvent::factory,
+//! StateEventBase::factory
template <typename BaseEventT>
-class EventFactory {
-public:
- template <typename FnT>
- static auto addMethod(FnT&& method)
+class EventFactory : public _impl::EventFactoryBase {
+private:
+ using method_t = event_ptr_tt<BaseEventT> (*)(const QJsonObject&,
+ const QString&);
+ std::vector<method_t> methods {};
+
+ template <class EventT>
+ static event_ptr_tt<BaseEventT> makeIfMatches(const QJsonObject& json,
+ const QString& matrixType)
{
- factories().emplace_back(std::forward<FnT>(method));
- return 0;
+ // If your matrix event type is not all ASCII, it's your problem
+ // (see https://github.com/matrix-org/matrix-doc/pull/2758)
+ return EventT::TypeId == matrixType ? makeEvent<EventT>(json) : nullptr;
}
- /** Chain two type factories
- * Adds the factory class of EventT2 (EventT2::factory_t) to
- * the list in factory class of EventT1 (EventT1::factory_t) so
- * that when EventT1::factory_t::make() is invoked, types of
- * EventT2 factory are looked through as well. This is used
- * to include RoomEvent types into the more general Event factory,
- * and state event types into the RoomEvent factory.
- */
- template <typename EventT>
- static auto chainFactory()
+public:
+ explicit EventFactory(const char* fName)
+ : EventFactoryBase { fName }
+ {}
+
+ //! \brief Add a method to create events of a given type
+ //!
+ //! Adds a standard factory method (makeIfMatches) for \p EventT so that
+ //! event objects of this type can be created dynamically by loadEvent.
+ //! The caller is responsible for ensuring this method is called only
+ //! once per type.
+ //! \sa loadEvent, Quotient::loadEvent
+ template <class EventT>
+ const auto& addMethod()
{
- return addMethod(&EventT::factory_t::make);
+ const auto m = &makeIfMatches<EventT>;
+ const auto it = std::find(methods.cbegin(), methods.cend(), m);
+ if (it != methods.cend())
+ return *it;
+ logAddingMethod(EventT::TypeId, methods.size() + 1);
+ return methods.emplace_back(m);
}
- static event_ptr_tt<BaseEventT> make(const QJsonObject& json,
- const QString& matrixType)
+ auto loadEvent(const QJsonObject& json, const QString& matrixType)
{
- for (const auto& f : factories())
+ for (const auto& f : methods)
if (auto e = f(json, matrixType))
return e;
- return nullptr;
- }
-
-private:
- static auto& factories()
- {
- using inner_factory_tt = std::function<event_ptr_tt<BaseEventT>(
- const QJsonObject&, const QString&)>;
- static std::vector<inner_factory_tt> _factories {};
- return _factories;
+ return makeEvent<BaseEventT>(UnknownEventTypeId, json);
}
};
-/** Add a type to its default factory
- * Adds a standard factory method (via makeEvent<>) for a given
- * type to EventT::factory_t factory class so that it can be
- * created dynamically from loadEvent<>().
- *
- * \tparam EventT the type to enable dynamic creation of
- * \return the registered type id
- * \sa loadEvent, Event::type
- */
-template <typename EventT>
-inline auto setupFactory()
-{
- qDebug(EVENTS) << "Adding factory method for" << EventT::matrixTypeId();
- return EventT::factory_t::addMethod([](const QJsonObject& json,
- const QString& jsonMatrixType) {
- return EventT::matrixTypeId() == jsonMatrixType ? makeEvent<EventT>(json)
- : nullptr;
- });
-}
-
-template <typename EventT>
-inline auto registerEventType()
+//! \brief Point of customisation to dynamically load events
+//!
+//! The default specialisation of this calls BaseEventT::factory.loadEvent()
+//! and if that fails (i.e. returns nullptr) creates an unknown event of
+//! BaseEventT. Other specialisations may reuse other factories, add validations
+//! common to BaseEventT events, and so on.
+template <class BaseEventT>
+event_ptr_tt<BaseEventT> doLoadEvent(const QJsonObject& json,
+ const QString& matrixType)
{
- // Initialise exactly once, even if this function is called twice for
- // the same type (for whatever reason - you never know the ways of
- // static initialisation is done).
- static const auto _ = setupFactory<EventT>();
- return _; // Only to facilitate usage in static initialisation
+ return BaseEventT::factory.loadEvent(json, matrixType);
}
// === Event ===
-class Event {
+class QUOTIENT_API Event {
public:
using Type = event_type_t;
- using factory_t = EventFactory<Event>;
+ static inline EventFactory<Event> factory { "Event" };
explicit Event(Type type, const QJsonObject& json);
explicit Event(Type type, event_mtype_t matrixType,
@@ -246,7 +233,7 @@ public:
return fromJson<T>(unsignedJson()[std::forward<KeyT>(key)]);
}
- friend QDebug operator<<(QDebug dbg, const Event& e)
+ friend QUOTIENT_API QDebug operator<<(QDebug dbg, const Event& e)
{
QDebugStateSaver _dss { dbg };
dbg.noquote().nospace() << e.matrixType() << '(' << e.type() << "): ";
@@ -271,26 +258,27 @@ template <typename EventT>
using EventsArray = std::vector<event_ptr_tt<EventT>>;
using Events = EventsArray<Event>;
-// === Macros used with event class definitions ===
+// === Facilities for event class definitions ===
// This macro should be used in a public section of an event class to
// provide matrixTypeId() and typeId().
-#define DEFINE_EVENT_TYPEID(_Id, _Type) \
- static constexpr event_mtype_t matrixTypeId() { return _Id; } \
- static auto typeId() { return Quotient::typeId<_Type>(); } \
+#define DEFINE_EVENT_TYPEID(Id_, Type_) \
+ static constexpr event_type_t TypeId = Id_##_ls; \
+ [[deprecated("Use " #Type_ "::TypeId directly instead")]] \
+ static constexpr event_mtype_t matrixTypeId() { return Id_; } \
+ [[deprecated("Use " #Type_ "::TypeId directly instead")]] \
+ static event_type_t typeId() { return TypeId; } \
// End of macro
// This macro should be put after an event class definition (in .h or .cpp)
// to enable its deserialisation from a /sync and other
// polymorphic event arrays
-#define REGISTER_EVENT_TYPE(_Type) \
- namespace { \
- [[maybe_unused]] static const auto _factoryAdded##_Type = \
- registerEventType<_Type>(); \
- } \
+#define REGISTER_EVENT_TYPE(Type_) \
+ [[maybe_unused]] inline const auto& factoryMethodFor##Type_ = \
+ Type_::factory.addMethod<Type_>(); \
// End of macro
-// === is<>(), eventCast<>() and visit<>() ===
+// === is<>(), eventCast<>() and switchOnType<>() ===
template <class EventT>
inline bool is(const Event& e)
@@ -300,7 +288,7 @@ inline bool is(const Event& e)
inline bool isUnknown(const Event& e)
{
- return e.type() == unknownEventTypeId();
+ return e.type() == UnknownEventTypeId;
}
template <class EventT, typename BasePtrT>
@@ -312,68 +300,75 @@ inline auto eventCast(const BasePtrT& eptr)
: nullptr;
}
-// A single generic catch-all visitor
+// A trivial generic catch-all "switch"
template <class BaseEventT, typename FnT>
-inline auto visit(const BaseEventT& event, FnT&& visitor)
- -> decltype(visitor(event))
+inline auto switchOnType(const BaseEventT& event, FnT&& fn)
+ -> decltype(fn(event))
{
- return visitor(event);
+ return fn(event);
}
namespace _impl {
// Using bool instead of auto below because auto apparently upsets MSVC
template <class BaseT, typename FnT>
- inline constexpr bool needs_downcast =
+ constexpr bool needs_downcast =
std::is_base_of_v<BaseT, std::decay_t<fn_arg_t<FnT>>>
&& !std::is_same_v<BaseT, std::decay_t<fn_arg_t<FnT>>>;
}
-// A single type-specific void visitor
+// A trivial type-specific "switch" for a void function
template <class BaseT, typename FnT>
-inline auto visit(const BaseT& event, FnT&& visitor)
+inline auto switchOnType(const BaseT& event, FnT&& fn)
-> std::enable_if_t<_impl::needs_downcast<BaseT, FnT>
&& std::is_void_v<fn_return_t<FnT>>>
{
using event_type = fn_arg_t<FnT>;
if (is<std::decay_t<event_type>>(event))
- visitor(static_cast<event_type>(event));
+ fn(static_cast<event_type>(event));
}
-// A single type-specific non-void visitor with an optional default value
-// non-voidness is guarded by defaultValue type
+// A trivial type-specific "switch" for non-void functions with an optional
+// default value; non-voidness is guarded by defaultValue type
template <class BaseT, typename FnT>
-inline auto visit(const BaseT& event, FnT&& visitor,
- fn_return_t<FnT>&& defaultValue = {})
+inline auto switchOnType(const BaseT& event, FnT&& fn,
+ fn_return_t<FnT>&& defaultValue = {})
-> std::enable_if_t<_impl::needs_downcast<BaseT, FnT>, fn_return_t<FnT>>
{
using event_type = fn_arg_t<FnT>;
if (is<std::decay_t<event_type>>(event))
- return visitor(static_cast<event_type>(event));
- return std::forward<fn_return_t<FnT>>(defaultValue);
+ return fn(static_cast<event_type>(event));
+ return std::move(defaultValue);
}
-// A chain of 2 or more visitors
+// A switch for a chain of 2 or more functions
template <class BaseT, typename FnT1, typename FnT2, typename... FnTs>
-inline std::common_type_t<fn_return_t<FnT1>, fn_return_t<FnT2>> visit(
- const BaseT& event, FnT1&& visitor1, FnT2&& visitor2,
- FnTs&&... visitors)
+inline std::common_type_t<fn_return_t<FnT1>, fn_return_t<FnT2>>
+switchOnType(const BaseT& event, FnT1&& fn1, FnT2&& fn2, FnTs&&... fns)
{
using event_type1 = fn_arg_t<FnT1>;
if (is<std::decay_t<event_type1>>(event))
- return visitor1(static_cast<event_type1&>(event));
- return visit(event, std::forward<FnT2>(visitor2),
- std::forward<FnTs>(visitors)...);
+ return fn1(static_cast<event_type1&>(event));
+ return switchOnType(event, std::forward<FnT2>(fn2),
+ std::forward<FnTs>(fns)...);
+}
+
+template <class BaseT, typename... FnTs>
+[[deprecated("The new name for visit() is switchOnType()")]] //
+inline auto visit(const BaseT& event, FnTs&&... fns)
+{
+ return switchOnType(event, std::forward<FnTs>(fns)...);
}
-// A facility overload that calls void-returning visit() on each event
+ // A facility overload that calls void-returning switchOnType() on each event
// over a range of event pointers
+// TODO: replace with ranges::for_each once all standard libraries have it
template <typename RangeT, typename... FnTs>
-inline auto visitEach(RangeT&& events, FnTs&&... visitors)
+inline auto visitEach(RangeT&& events, FnTs&&... fns)
-> std::enable_if_t<std::is_void_v<
- decltype(visit(**begin(events), std::forward<FnTs>(visitors)...))>>
+ decltype(switchOnType(**begin(events), std::forward<FnTs>(fns)...))>>
{
for (auto&& evtPtr: events)
- visit(*evtPtr, std::forward<FnTs>(visitors)...);
+ switchOnType(*evtPtr, std::forward<FnTs>(fns)...);
}
} // namespace Quotient
Q_DECLARE_METATYPE(Quotient::Event*)
diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp
index d4cb43ff..9d7edf20 100644
--- a/lib/events/eventcontent.cpp
+++ b/lib/events/eventcontent.cpp
@@ -4,7 +4,6 @@
#include "eventcontent.h"
#include "converters.h"
-#include "util.h"
#include "logging.h"
#include <QtCore/QMimeDatabase>
diff --git a/lib/events/eventcontent.h b/lib/events/eventcontent.h
index f609a603..de9a792b 100644
--- a/lib/events/eventcontent.h
+++ b/lib/events/eventcontent.h
@@ -6,14 +6,15 @@
// This file contains generic event content definitions, applicable to room
// message events as well as other events (e.g., avatars).
+#include "encryptedfile.h"
+#include "quotient_export.h"
+
#include <QtCore/QJsonObject>
#include <QtCore/QMimeType>
#include <QtCore/QSize>
#include <QtCore/QUrl>
#include <QtCore/QMetaType>
-#include "encryptedfile.h"
-
class QFileInfo;
namespace Quotient {
@@ -28,7 +29,7 @@ namespace EventContent {
* assumed but not required that a content object can also be created
* from plain data.
*/
- class Base {
+ class QUOTIENT_API Base {
public:
explicit Base(QJsonObject o = {}) : originalJson(std::move(o)) {}
virtual ~Base() = default;
@@ -76,7 +77,7 @@ namespace EventContent {
*
* This class is not polymorphic.
*/
- class FileInfo {
+ class QUOTIENT_API FileInfo {
public:
FileInfo() = default;
explicit FileInfo(const QFileInfo& fi);
@@ -105,7 +106,7 @@ namespace EventContent {
QJsonObject originalInfoJson;
QMimeType mimeType;
QUrl url;
- qint64 payloadSize;
+ qint64 payloadSize = 0;
QString originalName;
Omittable<EncryptedFile> file = none;
};
@@ -121,7 +122,7 @@ namespace EventContent {
/**
* A content info class for image content types: image, thumbnail, video
*/
- class ImageInfo : public FileInfo {
+ class QUOTIENT_API ImageInfo : public FileInfo {
public:
ImageInfo() = default;
explicit ImageInfo(const QFileInfo& fi, QSize imageSize = {});
@@ -146,12 +147,10 @@ namespace EventContent {
* the JSON representation of event content; namely,
* "info/thumbnail_url" and "info/thumbnail_info" fields are used.
*/
- class Thumbnail : public ImageInfo {
+ class QUOTIENT_API Thumbnail : public ImageInfo {
public:
- Thumbnail() = default; // Allow empty thumbnails
- Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file = none);
- Thumbnail(const ImageInfo& info) : ImageInfo(info) {}
using ImageInfo::ImageInfo;
+ Thumbnail(const QJsonObject& infoJson, const Omittable<EncryptedFile> &file = none);
/**
* Writes thumbnail information to "thumbnail_info" subobject
@@ -160,7 +159,7 @@ namespace EventContent {
void fillInfoJson(QJsonObject* infoJson) const;
};
- class TypedBase : public Base {
+ class QUOTIENT_API TypedBase : public Base {
public:
virtual QMimeType type() const = 0;
virtual const FileInfo* fileInfo() const { return nullptr; }
@@ -183,7 +182,7 @@ namespace EventContent {
* \tparam InfoT base info class
*/
template <class InfoT>
- class UrlBasedContent : public TypedBase, public InfoT {
+ class QUOTIENT_API UrlBasedContent : public TypedBase, public InfoT {
public:
using InfoT::InfoT;
explicit UrlBasedContent(const QJsonObject& json)
@@ -215,7 +214,7 @@ namespace EventContent {
};
template <typename InfoT>
- class UrlWithThumbnailContent : public UrlBasedContent<InfoT> {
+ class QUOTIENT_API UrlWithThumbnailContent : public UrlBasedContent<InfoT> {
public:
// NB: when using inherited constructors, thumbnail has to be
// initialised separately
diff --git a/lib/events/eventloader.h b/lib/events/eventloader.h
index 978668f2..fe624d70 100644
--- a/lib/events/eventloader.h
+++ b/lib/events/eventloader.h
@@ -6,16 +6,6 @@
#include "stateevent.h"
namespace Quotient {
-namespace _impl {
- template <typename BaseEventT>
- static inline auto loadEvent(const QJsonObject& json,
- const QString& matrixType)
- {
- if (auto e = EventFactory<BaseEventT>::make(json, matrixType))
- return e;
- return makeEvent<BaseEventT>(unknownEventTypeId(), json);
- }
-} // namespace _impl
/*! Create an event with proper type from a JSON object
*
@@ -26,7 +16,7 @@ namespace _impl {
template <typename BaseEventT>
inline event_ptr_tt<BaseEventT> loadEvent(const QJsonObject& fullJson)
{
- return _impl::loadEvent<BaseEventT>(fullJson, fullJson[TypeKeyL].toString());
+ return doLoadEvent<BaseEventT>(fullJson, fullJson[TypeKeyL].toString());
}
/*! Create an event from a type string and content JSON
@@ -39,8 +29,8 @@ template <typename BaseEventT>
inline event_ptr_tt<BaseEventT> loadEvent(const QString& matrixType,
const QJsonObject& content)
{
- return _impl::loadEvent<BaseEventT>(basicEventJson(matrixType, content),
- matrixType);
+ return doLoadEvent<BaseEventT>(basicEventJson(matrixType, content),
+ matrixType);
}
/*! Create a state event from a type string, content JSON and state key
@@ -53,7 +43,7 @@ inline StateEventPtr loadStateEvent(const QString& matrixType,
const QJsonObject& content,
const QString& stateKey = {})
{
- return _impl::loadEvent<StateEventBase>(
+ return doLoadEvent<StateEventBase>(
basicStateEventJson(matrixType, content, stateKey), matrixType);
}
diff --git a/lib/events/eventrelation.cpp b/lib/events/eventrelation.cpp
new file mode 100644
index 00000000..04972f45
--- /dev/null
+++ b/lib/events/eventrelation.cpp
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: 2022 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "eventrelation.h"
+
+#include "../logging.h"
+#include "event.h"
+
+using namespace Quotient;
+
+void JsonObjectConverter<EventRelation>::dumpTo(QJsonObject& jo,
+ const EventRelation& pod)
+{
+ if (pod.type.isEmpty()) {
+ qCWarning(MAIN) << "Empty relation type; won't dump to JSON";
+ return;
+ }
+ jo.insert(RelTypeKey, pod.type);
+ jo.insert(EventIdKey, pod.eventId);
+ if (pod.type == EventRelation::AnnotationType)
+ jo.insert(QStringLiteral("key"), pod.key);
+}
+
+void JsonObjectConverter<EventRelation>::fillFrom(const QJsonObject& jo,
+ EventRelation& pod)
+{
+ if (const auto replyJson = jo.value(EventRelation::ReplyType).toObject();
+ !replyJson.isEmpty()) {
+ pod.type = EventRelation::ReplyType;
+ fromJson(replyJson[EventIdKeyL], pod.eventId);
+ } else {
+ // The experimental logic for generic relationships (MSC1849)
+ fromJson(jo[RelTypeKey], pod.type);
+ fromJson(jo[EventIdKeyL], pod.eventId);
+ if (pod.type == EventRelation::AnnotationType)
+ fromJson(jo["key"_ls], pod.key);
+ }
+}
diff --git a/lib/events/eventrelation.h b/lib/events/eventrelation.h
new file mode 100644
index 00000000..e445ee42
--- /dev/null
+++ b/lib/events/eventrelation.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2022 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include "converters.h"
+
+namespace Quotient {
+
+[[maybe_unused]] constexpr auto RelatesToKey = "m.relates_to"_ls;
+constexpr auto RelTypeKey = "rel_type"_ls;
+
+struct QUOTIENT_API EventRelation {
+ using reltypeid_t = QLatin1String;
+
+ QString type;
+ QString eventId;
+ QString key = {}; // Only used for m.annotation for now
+
+ static constexpr auto ReplyType = "m.in_reply_to"_ls;
+ static constexpr auto AnnotationType = "m.annotation"_ls;
+ static constexpr auto ReplacementType = "m.replace"_ls;
+
+ static EventRelation replyTo(QString eventId)
+ {
+ return { ReplyType, std::move(eventId) };
+ }
+ static EventRelation annotate(QString eventId, QString key)
+ {
+ return { AnnotationType, std::move(eventId), std::move(key) };
+ }
+ static EventRelation replace(QString eventId)
+ {
+ return { ReplacementType, std::move(eventId) };
+ }
+
+ [[deprecated("Use ReplyRelation variable instead")]]
+ static constexpr auto Reply() { return ReplyType; }
+ [[deprecated("Use AnnotationRelation variable instead")]] //
+ static constexpr auto Annotation() { return AnnotationType; }
+ [[deprecated("Use ReplacementRelation variable instead")]] //
+ static constexpr auto Replacement() { return ReplacementType; }
+};
+
+template <>
+struct QUOTIENT_API JsonObjectConverter<EventRelation> {
+ static void dumpTo(QJsonObject& jo, const EventRelation& pod);
+ static void fillFrom(const QJsonObject& jo, EventRelation& pod);
+};
+
+}
+
diff --git a/lib/events/reactionevent.cpp b/lib/events/reactionevent.cpp
deleted file mode 100644
index b53fffd6..00000000
--- a/lib/events/reactionevent.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net>
-// SPDX-License-Identifier: LGPL-2.1-or-later
-
-#include "reactionevent.h"
-
-using namespace Quotient;
-
-void JsonObjectConverter<EventRelation>::dumpTo(
- QJsonObject& jo, const EventRelation& pod)
-{
- if (pod.type.isEmpty()) {
- qCWarning(MAIN) << "Empty relation type; won't dump to JSON";
- return;
- }
- jo.insert(QStringLiteral("rel_type"), pod.type);
- jo.insert(EventIdKey, pod.eventId);
- if (pod.type == EventRelation::Annotation())
- jo.insert(QStringLiteral("key"), pod.key);
-}
-
-void JsonObjectConverter<EventRelation>::fillFrom(
- const QJsonObject& jo, EventRelation& pod)
-{
- // The experimental logic for generic relationships (MSC1849)
- fromJson(jo["rel_type"_ls], pod.type);
- fromJson(jo[EventIdKeyL], pod.eventId);
- if (pod.type == EventRelation::Annotation())
- fromJson(jo["key"_ls], pod.key);
-}
diff --git a/lib/events/reactionevent.h b/lib/events/reactionevent.h
index 5a2b98c4..b3cb3ca7 100644
--- a/lib/events/reactionevent.h
+++ b/lib/events/reactionevent.h
@@ -4,39 +4,11 @@
#pragma once
#include "roomevent.h"
+#include "eventrelation.h"
namespace Quotient {
-struct EventRelation {
- using reltypeid_t = const char*;
- static constexpr reltypeid_t Reply() { return "m.in_reply_to"; }
- static constexpr reltypeid_t Annotation() { return "m.annotation"; }
- static constexpr reltypeid_t Replacement() { return "m.replace"; }
-
- QString type;
- QString eventId;
- QString key = {}; // Only used for m.annotation for now
-
- static EventRelation replyTo(QString eventId)
- {
- return { Reply(), std::move(eventId) };
- }
- static EventRelation annotate(QString eventId, QString key)
- {
- return { Annotation(), std::move(eventId), std::move(key) };
- }
- static EventRelation replace(QString eventId)
- {
- return { Replacement(), std::move(eventId) };
- }
-};
-template <>
-struct JsonObjectConverter<EventRelation> {
- static void dumpTo(QJsonObject& jo, const EventRelation& pod);
- static void fillFrom(const QJsonObject& jo, EventRelation& pod);
-};
-
-class ReactionEvent : public RoomEvent {
+class QUOTIENT_API ReactionEvent : public RoomEvent {
public:
DEFINE_EVENT_TYPEID("m.reaction", ReactionEvent)
@@ -47,7 +19,7 @@ public:
explicit ReactionEvent(const QJsonObject& obj) : RoomEvent(typeId(), obj) {}
EventRelation relation() const
{
- return contentPart<EventRelation>("m.relates_to"_ls);
+ return contentPart<EventRelation>(RelatesToKey);
}
};
REGISTER_EVENT_TYPE(ReactionEvent)
diff --git a/lib/events/receiptevent.cpp b/lib/events/receiptevent.cpp
index 72dbf2e3..7f06d99f 100644
--- a/lib/events/receiptevent.cpp
+++ b/lib/events/receiptevent.cpp
@@ -20,7 +20,6 @@ Example of a Receipt Event:
#include "receiptevent.h"
-#include "converters.h"
#include "logging.h"
using namespace Quotient;
diff --git a/lib/events/receiptevent.h b/lib/events/receiptevent.h
index 9683deef..5e077e47 100644
--- a/lib/events/receiptevent.h
+++ b/lib/events/receiptevent.h
@@ -19,7 +19,7 @@ struct ReceiptsForEvent {
};
using EventsWithReceipts = QVector<ReceiptsForEvent>;
-class ReceiptEvent : public Event {
+class QUOTIENT_API ReceiptEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.receipt", ReceiptEvent)
explicit ReceiptEvent(const EventsWithReceipts& ewrs);
diff --git a/lib/events/roomavatarevent.h b/lib/events/roomavatarevent.h
index 8618ba31..c54b5801 100644
--- a/lib/events/roomavatarevent.h
+++ b/lib/events/roomavatarevent.h
@@ -7,7 +7,8 @@
#include "stateevent.h"
namespace Quotient {
-class RoomAvatarEvent : public StateEvent<EventContent::ImageContent> {
+class QUOTIENT_API RoomAvatarEvent
+ : public StateEvent<EventContent::ImageContent> {
// It's a bit of an overkill to use a full-fledged ImageContent
// because in reality m.room.avatar usually only has a single URL,
// without a thumbnail. But The Spec says there be thumbnails, and
diff --git a/lib/events/roomcreateevent.h b/lib/events/roomcreateevent.h
index b3ad287c..989030ac 100644
--- a/lib/events/roomcreateevent.h
+++ b/lib/events/roomcreateevent.h
@@ -7,11 +7,10 @@
#include "quotient_common.h"
namespace Quotient {
-class RoomCreateEvent : public StateEventBase {
+class QUOTIENT_API RoomCreateEvent : public StateEventBase {
public:
DEFINE_EVENT_TYPEID("m.room.create", RoomCreateEvent)
- explicit RoomCreateEvent() : StateEventBase(typeId(), matrixTypeId()) {}
explicit RoomCreateEvent(const QJsonObject& obj)
: StateEventBase(typeId(), obj)
{}
diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp
index eb5d0485..2f482871 100644
--- a/lib/events/roomevent.cpp
+++ b/lib/events/roomevent.cpp
@@ -3,15 +3,11 @@
#include "roomevent.h"
-#include "converters.h"
#include "logging.h"
#include "redactionevent.h"
using namespace Quotient;
-[[maybe_unused]] static auto roomEventTypeInitialised =
- Event::factory_t::chainFactory<RoomEvent>();
-
RoomEvent::RoomEvent(Type type, event_mtype_t matrixType,
const QJsonObject& contentJson)
: Event(type, matrixType, contentJson)
diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h
index 3d46bf9b..c4b0131a 100644
--- a/lib/events/roomevent.h
+++ b/lib/events/roomevent.h
@@ -11,9 +11,9 @@ namespace Quotient {
class RedactionEvent;
/** This class corresponds to m.room.* events */
-class RoomEvent : public Event {
+class QUOTIENT_API RoomEvent : public Event {
public:
- using factory_t = EventFactory<RoomEvent>;
+ static inline EventFactory<RoomEvent> factory { "RoomEvent" };
// RedactionEvent is an incomplete type here so we cannot inline
// constructors and destructors and we cannot use 'using'.
@@ -80,7 +80,7 @@ using RoomEventPtr = event_ptr_tt<RoomEvent>;
using RoomEvents = EventsArray<RoomEvent>;
using RoomEventsRange = Range<RoomEvents>;
-class CallEventBase : public RoomEvent {
+class QUOTIENT_API CallEventBase : public RoomEvent {
public:
CallEventBase(Type type, event_mtype_t matrixType, const QString& callId,
int version, const QJsonObject& contentJson = {});
diff --git a/lib/events/roomkeyevent.h b/lib/events/roomkeyevent.h
index d021fbec..c4df7936 100644
--- a/lib/events/roomkeyevent.h
+++ b/lib/events/roomkeyevent.h
@@ -6,7 +6,7 @@
#include "event.h"
namespace Quotient {
-class RoomKeyEvent : public Event
+class QUOTIENT_API RoomKeyEvent : public Event
{
public:
DEFINE_EVENT_TYPEID("m.room_key", RoomKeyEvent)
diff --git a/lib/events/roommemberevent.cpp b/lib/events/roommemberevent.cpp
index b0bc7bcb..b4770224 100644
--- a/lib/events/roommemberevent.cpp
+++ b/lib/events/roommemberevent.cpp
@@ -4,7 +4,6 @@
#include "roommemberevent.h"
-#include "converters.h"
#include "logging.h"
#include <QtCore/QtAlgorithms>
@@ -48,11 +47,9 @@ void MemberEventContent::fillJson(QJsonObject* o) const
{
Q_ASSERT(o);
if (membership != Membership::Invalid)
- o->insert(
- QStringLiteral("membership"),
- MembershipStrings[qCountTrailingZeroBits(
- std::underlying_type_t<Membership>(membership))
- + 1]);
+ o->insert(QStringLiteral("membership"),
+ MembershipStrings[qCountTrailingZeroBits(
+ std::underlying_type_t<Membership>(membership))]);
if (displayName)
o->insert(QStringLiteral("displayname"), *displayName);
if (avatarUrl && avatarUrl->isValid())
diff --git a/lib/events/roommemberevent.h b/lib/events/roommemberevent.h
index f3047159..ceb7826b 100644
--- a/lib/events/roommemberevent.h
+++ b/lib/events/roommemberevent.h
@@ -10,14 +10,12 @@
#include "quotient_common.h"
namespace Quotient {
-class MemberEventContent : public EventContent::Base {
+class QUOTIENT_API MemberEventContent : public EventContent::Base {
public:
using MembershipType
[[deprecated("Use Quotient::Membership instead")]] = Membership;
- explicit MemberEventContent(Membership ms = Membership::Join)
- : membership(ms)
- {}
+ QUO_IMPLICIT MemberEventContent(Membership ms) : membership(ms) {}
explicit MemberEventContent(const QJsonObject& json);
Membership membership;
@@ -33,7 +31,7 @@ protected:
using MembershipType [[deprecated("Use Membership instead")]] = Membership;
-class RoomMemberEvent : public StateEvent<MemberEventContent> {
+class QUOTIENT_API RoomMemberEvent : public StateEvent<MemberEventContent> {
Q_GADGET
public:
DEFINE_EVENT_TYPEID("m.room.member", RoomMemberEvent)
@@ -43,22 +41,19 @@ public:
explicit RoomMemberEvent(const QJsonObject& obj) : StateEvent(typeId(), obj)
{}
- template <typename... ArgTs>
- RoomMemberEvent(const QString& userId, ArgTs&&... contentArgs)
- : StateEvent(typeId(), matrixTypeId(), userId,
- std::forward<ArgTs>(contentArgs)...)
+ RoomMemberEvent(const QString& userId, MemberEventContent&& content)
+ : StateEvent(typeId(), matrixTypeId(), userId, std::move(content))
{}
- /// A special constructor to create unknown RoomMemberEvents
- /**
- * This is needed in order to use RoomMemberEvent as a "base event
- * class" in cases like GetMembersByRoomJob when RoomMemberEvents
- * (rather than RoomEvents or StateEvents) are resolved from JSON.
- * For such cases loadEvent<> requires an underlying class to be
- * constructible with unknownTypeId() instead of its genuine id.
- * Don't use it directly.
- * \sa GetMembersByRoomJob, loadEvent, unknownTypeId
- */
+ //! \brief A special constructor to create unknown RoomMemberEvents
+ //!
+ //! This is needed in order to use RoomMemberEvent as a "base event class"
+ //! in cases like GetMembersByRoomJob when RoomMemberEvents (rather than
+ //! RoomEvents or StateEvents) are resolved from JSON. For such cases
+ //! loadEvent\<> requires an underlying class to have a specialisation of
+ //! EventFactory\<> and be constructible with unknownTypeId() instead of
+ //! its genuine id. Don't use directly.
+ //! \sa EventFactory, loadEvent, GetMembersByRoomJob
RoomMemberEvent(Type type, const QJsonObject& fullJson)
: StateEvent(type, fullJson)
{}
@@ -89,14 +84,12 @@ public:
};
template <>
-class EventFactory<RoomMemberEvent> {
-public:
- static event_ptr_tt<RoomMemberEvent> make(const QJsonObject& json,
- const QString&)
- {
+inline event_ptr_tt<RoomMemberEvent>
+doLoadEvent<RoomMemberEvent>(const QJsonObject& json, const QString& matrixType)
+{
+ if (matrixType == QLatin1String(RoomMemberEvent::matrixTypeId()))
return makeEvent<RoomMemberEvent>(json);
- }
-};
-
+ return makeEvent<RoomMemberEvent>(unknownEventTypeId(), json);
+}
REGISTER_EVENT_TYPE(RoomMemberEvent)
} // namespace Quotient
diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp
index 2b7b4166..d63352cb 100644
--- a/lib/events/roommessageevent.cpp
+++ b/lib/events/roommessageevent.cpp
@@ -6,6 +6,7 @@
#include "roommessageevent.h"
#include "logging.h"
+#include "events/eventrelation.h"
#include <QtCore/QFileInfo>
#include <QtCore/QMimeDatabase>
@@ -19,15 +20,14 @@ using namespace EventContent;
using MsgType = RoomMessageEvent::MsgType;
-static const auto RelatesToKeyL = "m.relates_to"_ls;
-static const auto MsgTypeKeyL = "msgtype"_ls;
-static const auto FormattedBodyKeyL = "formatted_body"_ls;
-
-static const auto TextTypeKey = "m.text";
-static const auto EmoteTypeKey = "m.emote";
-static const auto NoticeTypeKey = "m.notice";
-
-static const auto HtmlContentTypeId = QStringLiteral("org.matrix.custom.html");
+namespace { // Supporting internal definitions
+constexpr auto RelatesToKey = "m.relates_to"_ls;
+constexpr auto MsgTypeKey = "msgtype"_ls;
+constexpr auto FormattedBodyKey = "formatted_body"_ls;
+constexpr auto TextTypeKey = "m.text"_ls;
+constexpr auto EmoteTypeKey = "m.emote"_ls;
+constexpr auto NoticeTypeKey = "m.notice"_ls;
+constexpr auto HtmlContentTypeId = "org.matrix.custom.html"_ls;
template <typename ContentT>
TypedBase* make(const QJsonObject& json)
@@ -38,13 +38,13 @@ TypedBase* make(const QJsonObject& json)
template <>
TypedBase* make<TextContent>(const QJsonObject& json)
{
- return json.contains(FormattedBodyKeyL) || json.contains(RelatesToKeyL)
+ return json.contains(FormattedBodyKey) || json.contains(RelatesToKey)
? new TextContent(json)
: nullptr;
}
struct MsgTypeDesc {
- QString matrixType;
+ QLatin1String matrixType;
MsgType enumType;
TypedBase* (*maker)(const QJsonObject&);
};
@@ -53,11 +53,11 @@ const std::vector<MsgTypeDesc> msgTypes = {
{ TextTypeKey, MsgType::Text, make<TextContent> },
{ EmoteTypeKey, MsgType::Emote, make<TextContent> },
{ NoticeTypeKey, MsgType::Notice, make<TextContent> },
- { QStringLiteral("m.image"), MsgType::Image, make<ImageContent> },
- { QStringLiteral("m.file"), MsgType::File, make<FileContent> },
- { QStringLiteral("m.location"), MsgType::Location, make<LocationContent> },
- { QStringLiteral("m.video"), MsgType::Video, make<VideoContent> },
- { QStringLiteral("m.audio"), MsgType::Audio, make<AudioContent> }
+ { "m.image"_ls, MsgType::Image, make<ImageContent> },
+ { "m.file"_ls, MsgType::File, make<FileContent> },
+ { "m.location"_ls, MsgType::Location, make<LocationContent> },
+ { "m.video"_ls, MsgType::Video, make<VideoContent> },
+ { "m.audio"_ls, MsgType::Audio, make<AudioContent> }
};
QString msgTypeToJson(MsgType enumType)
@@ -84,41 +84,44 @@ MsgType jsonToMsgType(const QString& matrixType)
return MsgType::Unknown;
}
-inline bool isReplacement(const Omittable<RelatesTo>& rel)
+inline bool isReplacement(const Omittable<EventRelation>& rel)
{
- return rel && rel->type == RelatesTo::ReplacementTypeId();
+ return rel && rel->type == EventRelation::ReplacementType;
}
+} // anonymous namespace
+
QJsonObject RoomMessageEvent::assembleContentJson(const QString& plainBody,
const QString& jsonMsgType,
TypedBase* content)
{
- auto json = content ? content->toJson() : QJsonObject();
- if (json.contains(RelatesToKeyL)) {
+ QJsonObject json;
+ if (content) {
+ // TODO: replace with content->fillJson(json) when it starts working
+ json = content->toJson();
if (jsonMsgType != TextTypeKey && jsonMsgType != NoticeTypeKey
&& jsonMsgType != EmoteTypeKey) {
- json.remove(RelatesToKeyL);
- qCWarning(EVENTS)
- << RelatesToKeyL << "cannot be used in" << jsonMsgType
- << "messages; the relation has been stripped off";
- } else {
- // After the above, we know for sure that the content is TextContent
- // and that its RelatesTo structure is not omitted
- auto* textContent = static_cast<const TextContent*>(content);
- Q_ASSERT(textContent && textContent->relatesTo.has_value());
- if (textContent->relatesTo->type == RelatesTo::ReplacementTypeId()) {
- auto newContentJson = json.take("m.new_content"_ls).toObject();
- newContentJson.insert(BodyKey, plainBody);
- newContentJson.insert(MsgTypeKeyL, jsonMsgType);
- json.insert(QStringLiteral("m.new_content"), newContentJson);
- json[MsgTypeKeyL] = jsonMsgType;
- json[BodyKeyL] = "* " + plainBody;
- return json;
+ if (json.contains(RelatesToKey)) {
+ json.remove(RelatesToKey);
+ qCWarning(EVENTS)
+ << RelatesToKey << "cannot be used in" << jsonMsgType
+ << "messages; the relation has been stripped off";
}
+ } else if (auto* textContent = static_cast<const TextContent*>(content);
+ textContent->relatesTo
+ && textContent->relatesTo->type
+ == EventRelation::ReplacementType) {
+ auto newContentJson = json.take("m.new_content"_ls).toObject();
+ newContentJson.insert(BodyKey, plainBody);
+ newContentJson.insert(MsgTypeKey, jsonMsgType);
+ json.insert(QStringLiteral("m.new_content"), newContentJson);
+ json[MsgTypeKey] = jsonMsgType;
+ json[BodyKeyL] = "* " + plainBody;
+ return json;
}
}
- json.insert(QStringLiteral("msgtype"), jsonMsgType);
- json.insert(QStringLiteral("body"), plainBody);
+ json.insert(MsgTypeKey, jsonMsgType);
+ json.insert(BodyKey, plainBody);
return json;
}
@@ -177,8 +180,8 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj)
if (isRedacted())
return;
const QJsonObject content = contentJson();
- if (content.contains(MsgTypeKeyL) && content.contains(BodyKeyL)) {
- auto msgtype = content[MsgTypeKeyL].toString();
+ if (content.contains(MsgTypeKey) && content.contains(BodyKeyL)) {
+ auto msgtype = content[MsgTypeKey].toString();
bool msgTypeFound = false;
for (const auto& mt : msgTypes)
if (mt.matrixType == msgtype) {
@@ -204,7 +207,7 @@ RoomMessageEvent::MsgType RoomMessageEvent::msgtype() const
QString RoomMessageEvent::rawMsgtype() const
{
- return contentPart<QString>(MsgTypeKeyL);
+ return contentPart<QString>(MsgTypeKey);
}
QString RoomMessageEvent::plainBody() const
@@ -267,7 +270,7 @@ QString RoomMessageEvent::rawMsgTypeForFile(const QFileInfo& fi)
}
TextContent::TextContent(QString text, const QString& contentType,
- Omittable<RelatesTo> relatesTo)
+ Omittable<EventRelation> relatesTo)
: mimeType(QMimeDatabase().mimeTypeForName(contentType))
, body(std::move(text))
, relatesTo(std::move(relatesTo))
@@ -276,26 +279,8 @@ TextContent::TextContent(QString text, const QString& contentType,
mimeType = QMimeDatabase().mimeTypeForName("text/html");
}
-namespace Quotient {
-// Overload the default fromJson<> logic that defined in converters.h
-// as we want
-template <>
-Omittable<RelatesTo> fromJson(const QJsonValue& jv)
-{
- const auto jo = jv.toObject();
- if (jo.isEmpty())
- return none;
- const auto replyJson = jo.value(RelatesTo::ReplyTypeId()).toObject();
- if (!replyJson.isEmpty())
- return replyTo(fromJson<QString>(replyJson[EventIdKeyL]));
-
- return RelatesTo { jo.value("rel_type"_ls).toString(),
- jo.value(EventIdKeyL).toString() };
-}
-} // namespace Quotient
-
TextContent::TextContent(const QJsonObject& json)
- : relatesTo(fromJson<Omittable<RelatesTo>>(json[RelatesToKeyL]))
+ : relatesTo(fromJson<Omittable<EventRelation>>(json[RelatesToKey]))
{
QMimeDatabase db;
static const auto PlainTextMimeType = db.mimeTypeForName("text/plain");
@@ -308,7 +293,7 @@ TextContent::TextContent(const QJsonObject& json)
// of sending HTML messages.
if (actualJson["format"_ls].toString() == HtmlContentTypeId) {
mimeType = HtmlMimeType;
- body = actualJson[FormattedBodyKeyL].toString();
+ body = actualJson[FormattedBodyKey].toString();
} else {
// Falling back to plain text, as there's no standard way to describe
// rich text in messages.
@@ -320,7 +305,6 @@ TextContent::TextContent(const QJsonObject& json)
void TextContent::fillJson(QJsonObject* json) const
{
static const auto FormatKey = QStringLiteral("format");
- static const auto FormattedBodyKey = QStringLiteral("formatted_body");
Q_ASSERT(json);
if (mimeType.inherits("text/html")) {
@@ -328,12 +312,15 @@ void TextContent::fillJson(QJsonObject* json) const
json->insert(FormattedBodyKey, body);
}
if (relatesTo) {
- json->insert(QStringLiteral("m.relates_to"),
- relatesTo->type == RelatesTo::ReplyTypeId() ?
- QJsonObject { { relatesTo->type, QJsonObject{ { EventIdKey, relatesTo->eventId } } } } :
- QJsonObject { { "rel_type", relatesTo->type }, { EventIdKey, relatesTo->eventId } }
- );
- if (relatesTo->type == RelatesTo::ReplacementTypeId()) {
+ json->insert(
+ QStringLiteral("m.relates_to"),
+ relatesTo->type == EventRelation::ReplyType
+ ? QJsonObject { { relatesTo->type,
+ QJsonObject {
+ { EventIdKey, relatesTo->eventId } } } }
+ : QJsonObject { { RelTypeKey, relatesTo->type },
+ { EventIdKey, relatesTo->eventId } });
+ if (relatesTo->type == EventRelation::ReplacementType) {
QJsonObject newContentJson;
if (mimeType.inherits("text/html")) {
newContentJson.insert(FormatKey, HtmlContentTypeId);
diff --git a/lib/events/roommessageevent.h b/lib/events/roommessageevent.h
index 56597ddc..03a51328 100644
--- a/lib/events/roommessageevent.h
+++ b/lib/events/roommessageevent.h
@@ -6,6 +6,7 @@
#pragma once
#include "eventcontent.h"
+#include "eventrelation.h"
#include "roomevent.h"
class QFileInfo;
@@ -16,7 +17,7 @@ namespace MessageEventContent = EventContent; // Back-compatibility
/**
* The event class corresponding to m.room.message events
*/
-class RoomMessageEvent : public RoomEvent {
+class QUOTIENT_API RoomMessageEvent : public RoomEvent {
Q_GADGET
public:
DEFINE_EVENT_TYPEID("m.room.message", RoomMessageEvent)
@@ -97,40 +98,42 @@ REGISTER_EVENT_TYPE(RoomMessageEvent)
using MessageEventType = RoomMessageEvent::MsgType;
namespace EventContent {
- // Additional event content types
- struct RelatesTo {
- static constexpr const char* ReplyTypeId() { return "m.in_reply_to"; }
- static constexpr const char* ReplacementTypeId() { return "m.replace"; }
- QString type; // The only supported relation so far
- QString eventId;
+ struct [[deprecated("Use Quotient::EventRelation instead")]] RelatesTo
+ : EventRelation {
+ static constexpr auto ReplyTypeId() { return ReplyType; }
+ static constexpr auto ReplacementTypeId() { return ReplacementType; }
};
- inline RelatesTo replyTo(QString eventId)
+ [[deprecated("Use EventRelation::replyTo() instead")]]
+ inline auto replyTo(QString eventId)
{
- return { RelatesTo::ReplyTypeId(), std::move(eventId) };
+ return EventRelation::replyTo(std::move(eventId));
}
- inline RelatesTo replacementOf(QString eventId)
+ [[deprecated("Use EventRelation::replace() instead")]]
+ inline auto replacementOf(QString eventId)
{
- return { RelatesTo::ReplacementTypeId(), std::move(eventId) };
+ return EventRelation::replace(std::move(eventId));
}
+ // Additional event content types
+
/**
* Rich text content for m.text, m.emote, m.notice
*
* Available fields: mimeType, body. The body can be either rich text
* or plain text, depending on what mimeType specifies.
*/
- class TextContent : public TypedBase {
+ class QUOTIENT_API TextContent : public TypedBase {
public:
TextContent(QString text, const QString& contentType,
- Omittable<RelatesTo> relatesTo = none);
+ Omittable<EventRelation> relatesTo = none);
explicit TextContent(const QJsonObject& json);
QMimeType type() const override { return mimeType; }
QMimeType mimeType;
QString body;
- Omittable<RelatesTo> relatesTo;
+ Omittable<EventRelation> relatesTo;
protected:
void fillJson(QJsonObject* json) const override;
@@ -149,7 +152,7 @@ namespace EventContent {
* - thumbnail.mimeType
* - thumbnail.imageSize
*/
- class LocationContent : public TypedBase {
+ class QUOTIENT_API LocationContent : public TypedBase {
public:
LocationContent(const QString& geoUri, const Thumbnail& thumbnail = {});
explicit LocationContent(const QJsonObject& json);
@@ -168,7 +171,7 @@ namespace EventContent {
* A base class for info types that include duration: audio and video
*/
template <typename ContentT>
- class PlayableContent : public ContentT {
+ class QUOTIENT_API PlayableContent : public ContentT {
public:
using ContentT::ContentT;
PlayableContent(const QJsonObject& json)
diff --git a/lib/events/roompowerlevelsevent.h b/lib/events/roompowerlevelsevent.h
index 0346fc0d..415cc814 100644
--- a/lib/events/roompowerlevelsevent.h
+++ b/lib/events/roompowerlevelsevent.h
@@ -7,7 +7,7 @@
#include "stateevent.h"
namespace Quotient {
-class PowerLevelsEventContent : public EventContent::Base {
+class QUOTIENT_API PowerLevelsEventContent : public EventContent::Base {
public:
struct Notifications {
int room;
@@ -34,11 +34,14 @@ protected:
void fillJson(QJsonObject* o) const override;
};
-class RoomPowerLevelsEvent : public StateEvent<PowerLevelsEventContent> {
- Q_GADGET
+class QUOTIENT_API RoomPowerLevelsEvent
+ : public StateEvent<PowerLevelsEventContent> {
public:
DEFINE_EVENT_TYPEID("m.room.power_levels", RoomPowerLevelsEvent)
+ explicit RoomPowerLevelsEvent(PowerLevelsEventContent&& content)
+ : StateEvent(typeId(), matrixTypeId(), QString(), std::move(content))
+ {}
explicit RoomPowerLevelsEvent(const QJsonObject& obj)
: StateEvent(typeId(), obj)
{}
@@ -61,9 +64,6 @@ public:
int powerLevelForEvent(const QString& eventId) const;
int powerLevelForState(const QString& eventId) const;
int powerLevelForUser(const QString& userId) const;
-
-private:
};
-
REGISTER_EVENT_TYPE(RoomPowerLevelsEvent)
} // namespace Quotient
diff --git a/lib/events/roomtombstoneevent.h b/lib/events/roomtombstoneevent.h
index 30e53738..15d26923 100644
--- a/lib/events/roomtombstoneevent.h
+++ b/lib/events/roomtombstoneevent.h
@@ -6,11 +6,10 @@
#include "stateevent.h"
namespace Quotient {
-class RoomTombstoneEvent : public StateEventBase {
+class QUOTIENT_API RoomTombstoneEvent : public StateEventBase {
public:
DEFINE_EVENT_TYPEID("m.room.tombstone", RoomTombstoneEvent)
- explicit RoomTombstoneEvent() : StateEventBase(typeId(), matrixTypeId()) {}
explicit RoomTombstoneEvent(const QJsonObject& obj)
: StateEventBase(typeId(), obj)
{}
diff --git a/lib/events/simplestateevents.h b/lib/events/simplestateevents.h
index cf1bfbba..9610574b 100644
--- a/lib/events/simplestateevents.h
+++ b/lib/events/simplestateevents.h
@@ -8,8 +8,7 @@
namespace Quotient {
namespace EventContent {
template <typename T>
- class SimpleContent {
- public:
+ struct SimpleContent {
using value_type = T;
// The constructor is templated to enable perfect forwarding
@@ -25,20 +24,17 @@ namespace EventContent {
return { { key, Quotient::toJson(value) } };
}
- public:
T value;
-
- protected:
- QString key;
+ const QString key;
};
} // namespace EventContent
#define DEFINE_SIMPLE_STATE_EVENT(_Name, _TypeId, _ValueType, _ContentKey) \
- class _Name : public StateEvent<EventContent::SimpleContent<_ValueType>> { \
+ class QUOTIENT_API _Name \
+ : public StateEvent<EventContent::SimpleContent<_ValueType>> { \
public: \
using value_type = content_type::value_type; \
DEFINE_EVENT_TYPEID(_TypeId, _Name) \
- explicit _Name() : _Name(value_type()) {} \
template <typename T> \
explicit _Name(T&& value) \
: StateEvent(typeId(), matrixTypeId(), QString(), \
@@ -55,6 +51,7 @@ namespace EventContent {
DEFINE_SIMPLE_STATE_EVENT(RoomNameEvent, "m.room.name", QString, name)
DEFINE_SIMPLE_STATE_EVENT(RoomTopicEvent, "m.room.topic", QString, topic)
+DEFINE_SIMPLE_STATE_EVENT(RoomPinnedEvent, "m.room.pinned_messages", QStringList, pinnedEvents)
class [[deprecated(
"m.room.aliases events are deprecated by the Matrix spec; use"
@@ -65,10 +62,6 @@ public:
explicit RoomAliasesEvent(const QJsonObject& obj)
: StateEvent(typeId(), obj, QStringLiteral("aliases"))
{}
- RoomAliasesEvent(const QString& server, const QStringList& aliases)
- : StateEvent(typeId(), matrixTypeId(), server,
- QStringLiteral("aliases"), aliases)
- {}
QString server() const { return stateKey(); }
QStringList aliases() const { return content().value; }
};
diff --git a/lib/events/stateevent.cpp b/lib/events/stateevent.cpp
index efe011a0..e53d47d4 100644
--- a/lib/events/stateevent.cpp
+++ b/lib/events/stateevent.cpp
@@ -5,20 +5,13 @@
using namespace Quotient;
-// Aside from the normal factory to instantiate StateEventBase inheritors
-// StateEventBase itself can be instantiated if there's a state_key JSON key
-// but the event type is unknown.
-[[maybe_unused]] static auto stateEventTypeInitialised =
- RoomEvent::factory_t::addMethod(
- [](const QJsonObject& json, const QString& matrixType) -> StateEventPtr {
- if (!json.contains(StateKeyKeyL))
- return nullptr;
-
- if (auto e = StateEventBase::factory_t::make(json, matrixType))
- return e;
-
- return makeEvent<StateEventBase>(unknownEventTypeId(), json);
- });
+StateEventBase::StateEventBase(Type type, const QJsonObject& json)
+ : RoomEvent(json.contains(StateKeyKeyL) ? type : unknownEventTypeId(), json)
+{
+ if (Event::type() == unknownEventTypeId() && !json.contains(StateKeyKeyL))
+ qWarning(EVENTS) << "Attempt to create a state event with no stateKey -"
+ "forcing the event type to unknown to avoid damage";
+}
StateEventBase::StateEventBase(Event::Type type, event_mtype_t matrixType,
const QString& stateKey,
diff --git a/lib/events/stateevent.h b/lib/events/stateevent.h
index b0aa9907..88da68f8 100644
--- a/lib/events/stateevent.h
+++ b/lib/events/stateevent.h
@@ -17,12 +17,11 @@ inline QJsonObject basicStateEventJson(const QString& matrixTypeId,
{ ContentKey, content } };
}
-class StateEventBase : public RoomEvent {
+class QUOTIENT_API StateEventBase : public RoomEvent {
public:
- using factory_t = EventFactory<StateEventBase>;
+ static inline EventFactory<StateEventBase> factory { "StateEvent" };
- StateEventBase(Type type, const QJsonObject& json) : RoomEvent(type, json)
- {}
+ StateEventBase(Type type, const QJsonObject& json);
StateEventBase(Type type, event_mtype_t matrixType,
const QString& stateKey = {},
const QJsonObject& contentJson = {});
@@ -37,6 +36,22 @@ public:
using StateEventPtr = event_ptr_tt<StateEventBase>;
using StateEvents = EventsArray<StateEventBase>;
+//! \brief Override RoomEvent factory with that from StateEventBase if JSON has
+//! stateKey
+//!
+//! This means in particular that an event with a type known to RoomEvent but
+//! having stateKey set (even to an empty value) will be treated as a state
+//! event and most likely end up as unknown (consider, e.g., m.room.message
+//! that has stateKey set).
+template <>
+inline RoomEventPtr doLoadEvent(const QJsonObject& json,
+ const QString& matrixType)
+{
+ if (json.contains(StateKeyKeyL))
+ return StateEventBase::factory.loadEvent(json, matrixType);
+ return RoomEvent::factory.loadEvent(json, matrixType);
+}
+
template <>
inline bool is<StateEventBase>(const Event& e)
{
diff --git a/lib/events/stickerevent.h b/lib/events/stickerevent.h
index 93671086..0957dca3 100644
--- a/lib/events/stickerevent.h
+++ b/lib/events/stickerevent.h
@@ -11,7 +11,7 @@ namespace Quotient {
/// Sticker messages are specialised image messages that are displayed without
/// controls (e.g. no "download" link, or light-box view on click, as would be
/// displayed for for m.image events).
-class StickerEvent : public RoomEvent
+class QUOTIENT_API StickerEvent : public RoomEvent
{
public:
DEFINE_EVENT_TYPEID("m.sticker", StickerEvent)
diff --git a/lib/events/typingevent.h b/lib/events/typingevent.h
index 7456100a..522f7e42 100644
--- a/lib/events/typingevent.h
+++ b/lib/events/typingevent.h
@@ -6,7 +6,7 @@
#include "event.h"
namespace Quotient {
-class TypingEvent : public Event {
+class QUOTIENT_API TypingEvent : public Event {
public:
DEFINE_EVENT_TYPEID("m.typing", TypingEvent)
diff --git a/lib/eventstats.h b/lib/eventstats.h
index 77c661a7..a10c81fb 100644
--- a/lib/eventstats.h
+++ b/lib/eventstats.h
@@ -16,7 +16,7 @@ namespace Quotient {
//! \note It's just a simple grouping of counters and is not automatically
//! updated from the room as subsequent syncs arrive.
//! \sa Room::unreadStats, Room::partiallyReadStats, Room::isEventNotable
-struct EventStats {
+struct QUOTIENT_API EventStats {
Q_GADGET
Q_PROPERTY(qsizetype notableCount MEMBER notableCount CONSTANT)
Q_PROPERTY(qsizetype highlightCount MEMBER highlightCount CONSTANT)
@@ -109,6 +109,6 @@ public:
bool isValidFor(const Room* room, const marker_t& marker) const;
};
-QDebug operator<<(QDebug dbg, const EventStats& es);
+QUOTIENT_API QDebug operator<<(QDebug dbg, const EventStats& es);
}
diff --git a/lib/function_traits.cpp b/lib/function_traits.cpp
new file mode 100644
index 00000000..6542101a
--- /dev/null
+++ b/lib/function_traits.cpp
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "function_traits.h"
+
+// Tests for function_traits<>
+
+using namespace Quotient;
+
+int f_();
+static_assert(std::is_same_v<fn_return_t<decltype(f_)>, int>,
+ "Test fn_return_t<>");
+
+void f1_(int, float);
+static_assert(std::is_same_v<fn_arg_t<decltype(f1_), 1>, float>,
+ "Test fn_arg_t<>");
+
+struct Fo {
+ int operator()();
+ static constexpr auto l = [] { return 0.0f; };
+ bool memFn();
+ void constMemFn() const&;
+ double field;
+ const double field2;
+};
+static_assert(std::is_same_v<fn_return_t<Fo>, int>,
+ "Test return type of function object");
+static_assert(std::is_same_v<fn_return_t<decltype(Fo::l)>, float>,
+ "Test return type of lambda");
+static_assert(std::is_same_v<fn_arg_t<decltype(&Fo::memFn)>, Fo>,
+ "Test first argument type of member function");
+static_assert(std::is_same_v<fn_return_t<decltype(&Fo::memFn)>, bool>,
+ "Test return type of member function");
+static_assert(std::is_same_v<fn_arg_t<decltype(&Fo::constMemFn)>, const Fo&>,
+ "Test first argument type of const member function");
+static_assert(std::is_void_v<fn_return_t<decltype(&Fo::constMemFn)>>,
+ "Test return type of const member function");
+static_assert(std::is_same_v<fn_return_t<decltype(&Fo::field)>, double&>,
+ "Test return type of a class member");
+static_assert(std::is_same_v<fn_return_t<decltype(&Fo::field2)>, const double&>,
+ "Test return type of a const class member");
+
+struct Fo1 {
+ void operator()(int);
+};
+static_assert(std::is_same_v<fn_arg_t<Fo1>, int>,
+ "Test fn_arg_t defaulting to first argument");
+
+template <typename T>
+[[maybe_unused]] static void ft(const std::vector<T>&);
+static_assert(
+ std::is_same<fn_arg_t<decltype(ft<double>)>, const std::vector<double>&>(),
+ "Test function templates");
diff --git a/lib/function_traits.h b/lib/function_traits.h
new file mode 100644
index 00000000..83b8e425
--- /dev/null
+++ b/lib/function_traits.h
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <functional>
+
+namespace Quotient {
+
+namespace _impl {
+ template <typename AlwaysVoid, typename>
+ struct fn_traits {};
+}
+
+/// Determine traits of an arbitrary function/lambda/functor
+/*!
+ * Doesn't work with generic lambdas and function objects that have
+ * operator() overloaded.
+ * \sa
+ * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765
+ */
+template <typename T>
+struct function_traits
+ : public _impl::fn_traits<void, std::remove_reference_t<T>> {};
+
+// Specialisation for a function
+template <typename ReturnT, typename... ArgTs>
+struct function_traits<ReturnT(ArgTs...)> {
+ using return_type = ReturnT;
+ using arg_types = std::tuple<ArgTs...>;
+ // See also the comment for wrap_in_function() in qt_connection_util.h
+ using function_type = std::function<ReturnT(ArgTs...)>;
+};
+
+namespace _impl {
+ template <typename AlwaysVoid, typename>
+ struct fn_object_traits;
+
+ // Specialisation for a lambda function
+ template <typename ReturnT, typename ClassT, typename... ArgTs>
+ struct fn_object_traits<void, ReturnT (ClassT::*)(ArgTs...)>
+ : function_traits<ReturnT(ArgTs...)> {};
+
+ // Specialisation for a const lambda function
+ template <typename ReturnT, typename ClassT, typename... ArgTs>
+ struct fn_object_traits<void, ReturnT (ClassT::*)(ArgTs...) const>
+ : function_traits<ReturnT(ArgTs...)> {};
+
+ // Specialisation for function objects with (non-overloaded) operator()
+ // (this includes non-generic lambdas)
+ template <typename T>
+ struct fn_traits<decltype(void(&T::operator())), T>
+ : public fn_object_traits<void, decltype(&T::operator())> {};
+
+ // Specialisation for a member function in a non-functor class
+ template <typename ReturnT, typename ClassT, typename... ArgTs>
+ struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...)>
+ : function_traits<ReturnT(ClassT, ArgTs...)> {};
+
+ // Specialisation for a const member function
+ template <typename ReturnT, typename ClassT, typename... ArgTs>
+ struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...) const>
+ : function_traits<ReturnT(const ClassT&, ArgTs...)> {};
+
+ // Specialisation for a constref member function
+ template <typename ReturnT, typename ClassT, typename... ArgTs>
+ struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...) const&>
+ : function_traits<ReturnT(const ClassT&, ArgTs...)> {};
+
+ // Specialisation for a prvalue member function
+ template <typename ReturnT, typename ClassT, typename... ArgTs>
+ struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...) &&>
+ : function_traits<ReturnT(ClassT&&, ArgTs...)> {};
+
+ // Specialisation for a pointer-to-member
+ template <typename ReturnT, typename ClassT>
+ struct fn_traits<void, ReturnT ClassT::*>
+ : function_traits<ReturnT&(ClassT)> {};
+
+ // Specialisation for a const pointer-to-member
+ template <typename ReturnT, typename ClassT>
+ struct fn_traits<void, const ReturnT ClassT::*>
+ : function_traits<const ReturnT&(ClassT)> {};
+} // namespace _impl
+
+template <typename FnT>
+using fn_return_t = typename function_traits<FnT>::return_type;
+
+template <typename FnT, int ArgN = 0>
+using fn_arg_t =
+ std::tuple_element_t<ArgN, typename function_traits<FnT>::arg_types>;
+
+} // namespace Quotient
diff --git a/lib/jobs/basejob.cpp b/lib/jobs/basejob.cpp
index 971fea7b..b6858b5a 100644
--- a/lib/jobs/basejob.cpp
+++ b/lib/jobs/basejob.cpp
@@ -5,11 +5,9 @@
#include "basejob.h"
#include "connectiondata.h"
-#include "quotient_common.h"
#include <QtCore/QRegularExpression>
#include <QtCore/QTimer>
-#include <QtCore/QStringBuilder>
#include <QtCore/QMetaEnum>
#include <QtCore/QPointer>
#include <QtNetwork/QNetworkAccessManager>
@@ -194,8 +192,8 @@ BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint,
BaseJob::BaseJob(HttpVerb verb, const QString& name, QByteArray endpoint,
const QUrlQuery& query, RequestData&& data, bool needsToken)
- : d(new Private(verb, std::move(endpoint), query, std::move(data),
- needsToken))
+ : d(makeImpl<Private>(verb, std::move(endpoint), query, std::move(data),
+ needsToken))
{
setObjectName(name);
connect(&d->timer, &QTimer::timeout, this, &BaseJob::timeout);
diff --git a/lib/jobs/basejob.h b/lib/jobs/basejob.h
index ddf243ed..555c602b 100644
--- a/lib/jobs/basejob.h
+++ b/lib/jobs/basejob.h
@@ -5,9 +5,9 @@
#pragma once
#include "requestdata.h"
-#include "../logging.h"
-#include "../converters.h"
-#include "../quotient_common.h"
+#include "logging.h"
+#include "converters.h" // Common for csapi/ headers even though not used here
+#include "quotient_common.h" // For DECL_DEPRECATED_ENUMERATOR
#include <QtCore/QObject>
#include <QtCore/QStringBuilder>
@@ -20,7 +20,7 @@ class ConnectionData;
enum class HttpVerb { Get, Put, Post, Delete };
-class BaseJob : public QObject {
+class QUOTIENT_API BaseJob : public QObject {
Q_OBJECT
Q_PROPERTY(QUrl requestUrl READ requestUrl CONSTANT)
Q_PROPERTY(int maxRetries READ maxRetries WRITE setMaxRetries)
@@ -28,7 +28,7 @@ class BaseJob : public QObject {
static QByteArray encodeIfParam(const QString& paramPart);
template <int N>
- static inline auto encodeIfParam(const char (&constPart)[N])
+ static auto encodeIfParam(const char (&constPart)[N])
{
return constPart;
}
@@ -248,7 +248,7 @@ public:
}
public Q_SLOTS:
- void initiate(ConnectionData* connData, bool inBackground);
+ void initiate(Quotient::ConnectionData* connData, bool inBackground);
/**
* Abandons the result of this job, arrived or unarrived.
@@ -467,10 +467,10 @@ private:
void finishJob();
class Private;
- QScopedPointer<Private> d;
+ ImplPtr<Private> d;
};
-inline bool isJobPending(BaseJob* job)
+inline bool QUOTIENT_API isJobPending(BaseJob* job)
{
return job && job->error() == BaseJob::Pending;
}
diff --git a/lib/jobs/downloadfilejob.cpp b/lib/jobs/downloadfilejob.cpp
index c5280770..634e5fb9 100644
--- a/lib/jobs/downloadfilejob.cpp
+++ b/lib/jobs/downloadfilejob.cpp
@@ -40,7 +40,8 @@ DownloadFileJob::DownloadFileJob(const QString& serverName,
const QString& mediaId,
const QString& localFilename)
: GetContentJob(serverName, mediaId)
- , d(localFilename.isEmpty() ? new Private : new Private(localFilename))
+ , d(localFilename.isEmpty() ? makeImpl<Private>()
+ : makeImpl<Private>(localFilename))
{
setObjectName(QStringLiteral("DownloadFileJob"));
}
@@ -51,7 +52,8 @@ DownloadFileJob::DownloadFileJob(const QString& serverName,
const EncryptedFile& file,
const QString& localFilename)
: GetContentJob(serverName, mediaId)
- , d(localFilename.isEmpty() ? new Private : new Private(localFilename))
+ , d(localFilename.isEmpty() ? makeImpl<Private>()
+ : makeImpl<Private>(localFilename))
{
setObjectName(QStringLiteral("DownloadFileJob"));
d->encryptedFile = file;
diff --git a/lib/jobs/downloadfilejob.h b/lib/jobs/downloadfilejob.h
index 90d80478..ffa3d055 100644
--- a/lib/jobs/downloadfilejob.h
+++ b/lib/jobs/downloadfilejob.h
@@ -7,7 +7,7 @@
#include "events/encryptedfile.h"
namespace Quotient {
-class DownloadFileJob : public GetContentJob {
+class QUOTIENT_API DownloadFileJob : public GetContentJob {
public:
using GetContentJob::makeRequestUrl;
static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri);
@@ -22,7 +22,7 @@ public:
private:
class Private;
- QScopedPointer<Private> d;
+ ImplPtr<Private> d;
void doPrepare() override;
void onSentRequest(QNetworkReply* reply) override;
diff --git a/lib/jobs/mediathumbnailjob.h b/lib/jobs/mediathumbnailjob.h
index 3183feb1..c9f6da35 100644
--- a/lib/jobs/mediathumbnailjob.h
+++ b/lib/jobs/mediathumbnailjob.h
@@ -8,7 +8,7 @@
#include <QtGui/QPixmap>
namespace Quotient {
-class MediaThumbnailJob : public GetContentThumbnailJob {
+class QUOTIENT_API MediaThumbnailJob : public GetContentThumbnailJob {
public:
using GetContentThumbnailJob::makeRequestUrl;
static QUrl makeRequestUrl(QUrl baseUrl, const QUrl& mxcUri,
diff --git a/lib/jobs/requestdata.h b/lib/jobs/requestdata.h
index 4f05e5ff..41ad833a 100644
--- a/lib/jobs/requestdata.h
+++ b/lib/jobs/requestdata.h
@@ -3,6 +3,8 @@
#pragma once
+#include "quotient_export.h"
+
#include <QtCore/QByteArray>
#include <memory>
@@ -19,7 +21,7 @@ namespace Quotient {
* as well as JSON (and possibly other structures in the future) to
* a QByteArray consumed by QNetworkAccessManager request methods.
*/
-class RequestData {
+class QUOTIENT_API RequestData {
public:
RequestData(const QByteArray& a = {});
RequestData(const QJsonObject& jo);
diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp
index 9b1b46f0..f5c632bf 100644
--- a/lib/jobs/syncjob.cpp
+++ b/lib/jobs/syncjob.cpp
@@ -37,10 +37,12 @@ SyncJob::SyncJob(const QString& since, const Filter& filter, int timeout,
BaseJob::Status SyncJob::prepareResult()
{
d.parseJson(jsonData());
- if (d.unresolvedRooms().isEmpty())
+ if (Q_LIKELY(d.unresolvedRooms().isEmpty()))
return Success;
- qCCritical(MAIN).noquote() << "Incomplete sync response, missing rooms:"
+ Q_ASSERT(d.unresolvedRooms().isEmpty());
+ qCCritical(MAIN).noquote() << "Rooms missing after processing sync "
+ "response, possibly a bug in SyncData: "
<< d.unresolvedRooms().join(',');
return IncorrectResponse;
}
diff --git a/lib/mxcreply.cpp b/lib/mxcreply.cpp
index c666cce3..1d40c5e1 100644
--- a/lib/mxcreply.cpp
+++ b/lib/mxcreply.cpp
@@ -26,7 +26,7 @@ public:
};
MxcReply::MxcReply(QNetworkReply* reply)
- : d(std::make_unique<Private>(reply))
+ : d(makeImpl<Private>(reply))
{
d->m_device = d->m_reply;
reply->setParent(this);
@@ -38,7 +38,7 @@ MxcReply::MxcReply(QNetworkReply* reply)
}
MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId)
- : d(std::make_unique<Private>(reply))
+ : d(makeImpl<Private>(reply))
{
reply->setParent(this);
connect(d->m_reply, &QNetworkReply::finished, this, [this]() {
@@ -77,6 +77,7 @@ MxcReply::MxcReply(QNetworkReply* reply, Room* room, const QString &eventId)
#endif
MxcReply::MxcReply()
+ : d(ZeroImpl<Private>())
{
static const auto BadRequestPhrase = tr("Bad Request");
QMetaObject::invokeMethod(this, [this]() {
diff --git a/lib/mxcreply.h b/lib/mxcreply.h
index efaf01c6..f6c4a34d 100644
--- a/lib/mxcreply.h
+++ b/lib/mxcreply.h
@@ -3,14 +3,16 @@
#pragma once
+#include "util.h"
+
#include <QtNetwork/QNetworkReply>
-#include <memory>
namespace Quotient {
class Room;
-class MxcReply : public QNetworkReply
+class QUOTIENT_API MxcReply : public QNetworkReply
{
+ Q_OBJECT
public:
explicit MxcReply();
explicit MxcReply(QNetworkReply *reply);
@@ -24,6 +26,6 @@ protected:
private:
class Private;
- std::unique_ptr<Private> d;
+ ImplPtr<Private> d;
};
}
diff --git a/lib/networkaccessmanager.cpp b/lib/networkaccessmanager.cpp
index d0380cec..f4e7b1af 100644
--- a/lib/networkaccessmanager.cpp
+++ b/lib/networkaccessmanager.cpp
@@ -39,7 +39,7 @@ public:
};
NetworkAccessManager::NetworkAccessManager(QObject* parent)
- : QNetworkAccessManager(parent), d(std::make_unique<Private>(this))
+ : QNetworkAccessManager(parent), d(makeImpl<Private>(this))
{}
QList<QSslError> NetworkAccessManager::ignoredSslErrors() const
@@ -90,8 +90,6 @@ NetworkAccessManager* NetworkAccessManager::instance()
return storage.localData();
}
-NetworkAccessManager::~NetworkAccessManager() = default;
-
QNetworkReply* NetworkAccessManager::createRequest(
Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
{
@@ -110,7 +108,7 @@ QNetworkReply* NetworkAccessManager::createRequest(
// TODO: Make the best effort with a direct unauthenticated request
// to the media server
} else {
- auto* const connection = AccountRegistry::instance().get(accountId);
+ auto* const connection = Accounts.get(accountId);
if (!connection) {
qCWarning(NETWORK) << "Connection" << accountId << "not found";
return new MxcReply();
diff --git a/lib/networkaccessmanager.h b/lib/networkaccessmanager.h
index efa41994..01b0599d 100644
--- a/lib/networkaccessmanager.h
+++ b/lib/networkaccessmanager.h
@@ -3,18 +3,16 @@
#pragma once
-#include <QtNetwork/QNetworkAccessManager>
+#include "util.h"
-#include <memory>
+#include <QtNetwork/QNetworkAccessManager>
namespace Quotient {
-class Room;
-class NetworkAccessManager : public QNetworkAccessManager {
+class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
public:
NetworkAccessManager(QObject* parent = nullptr);
- ~NetworkAccessManager() override;
QList<QSslError> ignoredSslErrors() const;
void addIgnoredSslError(const QSslError& error);
@@ -32,6 +30,6 @@ private:
QIODevice* outgoingData = Q_NULLPTR) override;
class Private;
- std::unique_ptr<Private> d;
+ ImplPtr<Private> d;
};
} // namespace Quotient
diff --git a/lib/networksettings.cpp b/lib/networksettings.cpp
index ce46ce5f..06b1fdf9 100644
--- a/lib/networksettings.cpp
+++ b/lib/networksettings.cpp
@@ -11,9 +11,9 @@ void NetworkSettings::setupApplicationProxy() const
{ proxyType(), proxyHostName(), proxyPort() });
}
-QTNT_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType,
+QUO_DEFINE_SETTING(NetworkSettings, QNetworkProxy::ProxyType, proxyType,
"proxy_type", QNetworkProxy::DefaultProxy, setProxyType)
-QTNT_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname",
+QUO_DEFINE_SETTING(NetworkSettings, QString, proxyHostName, "proxy_hostname",
{}, setProxyHostName)
-QTNT_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1,
+QUO_DEFINE_SETTING(NetworkSettings, quint16, proxyPort, "proxy_port", -1,
setProxyPort)
diff --git a/lib/networksettings.h b/lib/networksettings.h
index df11a9c8..44247e59 100644
--- a/lib/networksettings.h
+++ b/lib/networksettings.h
@@ -10,11 +10,11 @@
Q_DECLARE_METATYPE(QNetworkProxy::ProxyType)
namespace Quotient {
-class NetworkSettings : public SettingsGroup {
+class QUOTIENT_API NetworkSettings : public SettingsGroup {
Q_OBJECT
- QTNT_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType)
- QTNT_DECLARE_SETTING(QString, proxyHostName, setProxyHostName)
- QTNT_DECLARE_SETTING(quint16, proxyPort, setProxyPort)
+ QUO_DECLARE_SETTING(QNetworkProxy::ProxyType, proxyType, setProxyType)
+ QUO_DECLARE_SETTING(QString, proxyHostName, setProxyHostName)
+ QUO_DECLARE_SETTING(quint16, proxyPort, setProxyPort)
Q_PROPERTY(QString proxyHost READ proxyHostName WRITE setProxyHostName)
public:
template <typename... ArgTs>
diff --git a/lib/omittable.h b/lib/omittable.h
new file mode 100644
index 00000000..b5efecf5
--- /dev/null
+++ b/lib/omittable.h
@@ -0,0 +1,223 @@
+// SPDX-FileCopyrightText: 2018 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <optional>
+#include <functional>
+
+namespace Quotient {
+
+template <typename T>
+class Omittable;
+
+constexpr auto none = std::nullopt;
+
+//! \brief Lift an operation into dereferenceable types (Omittables or pointers)
+//!
+//! This is a more generic version of Omittable::then() that extends to
+//! an arbitrary number of arguments of any type that is dereferenceable (unary
+//! operator*() can be applied to it) and (explicitly or implicitly) convertible
+//! to bool. This allows to streamline checking for nullptr/none before applying
+//! the operation on the underlying types. \p fn is only invoked if all \p args
+//! are "truthy" (i.e. <tt>(... && bool(args)) == true</tt>).
+//! \param fn A callable that should accept the types stored inside
+//! Omittables/pointers passed in \p args
+//! \return Always an Omittable: if \p fn returns another type, lift() wraps
+//! it in an Omittable; if \p fn returns an Omittable, that return value
+//! (or none) is returned as is.
+template <typename FnT, typename... MaybeTs>
+inline auto lift(FnT&& fn, MaybeTs&&... args)
+{
+ return (... && bool(args))
+ ? Omittable(std::invoke(std::forward<FnT>(fn), *args...))
+ : none;
+}
+
+/** `std::optional` with tweaks
+ *
+ * The tweaks are:
+ * - streamlined assignment (operator=)/emplace()ment of values that can be
+ * used to implicitly construct the underlying type, including
+ * direct-list-initialisation, e.g.:
+ * \code
+ * struct S { int a; char b; }
+ * Omittable<S> o;
+ * o = { 1, 'a' }; // std::optional would require o = S { 1, 'a' }
+ * \endcode
+ * - entirely deleted value(). The technical reason is that Xcode 10 doesn't
+ * have it; but besides that, value_or() or (after explicit checking)
+ * `operator*()`/`operator->()` are better alternatives within Quotient
+ * that doesn't practice throwing exceptions (as doesn't most of Qt).
+ * - disabled non-const lvalue operator*() and operator->(), as it's too easy
+ * to inadvertently cause a value change through them.
+ * - ensure() to provide a safe and explicit lvalue accessor instead of
+ * those above. Allows chained initialisation of nested Omittables:
+ * \code
+ * struct Inner { int member = 10; Omittable<int> innermost; };
+ * struct Outer { int anotherMember = 10; Omittable<Inner> inner; };
+ * Omittable<Outer> o; // = { 10, std::nullopt };
+ * o.ensure().inner.ensure().innermost.emplace(42);
+ * \endcode
+ * - merge() - a soft version of operator= that only overwrites its first
+ * operand with the second one if the second one is not empty.
+ * - then() and then_or() to streamline read-only interrogation in a "monadic"
+ * interface.
+ */
+template <typename T>
+class Omittable : public std::optional<T> {
+public:
+ using base_type = std::optional<T>;
+ using value_type = std::decay_t<T>;
+
+ using std::optional<T>::optional;
+
+ // Overload emplace() and operator=() to allow passing braced-init-lists
+ // (the standard emplace() does direct-initialisation but
+ // not direct-list-initialisation).
+ using base_type::operator=;
+ Omittable& operator=(const value_type& v)
+ {
+ base_type::operator=(v);
+ return *this;
+ }
+ Omittable& operator=(value_type&& v)
+ {
+ base_type::operator=(std::move(v));
+ return *this;
+ }
+
+ using base_type::emplace;
+ T& emplace(const T& val) { return base_type::emplace(val); }
+ T& emplace(T&& val) { return base_type::emplace(std::move(val)); }
+
+ // Use value_or() or check (with operator! or has_value) before accessing
+ // with operator-> or operator*
+ // The technical reason is that Xcode 10 has incomplete std::optional
+ // that has no value(); but using value() may also mean that you rely
+ // on the optional throwing an exception (which is not assumed practice
+ // throughout Quotient) or that you spend unnecessary CPU cycles on
+ // an extraneous has_value() check.
+ auto& value() = delete;
+ const auto& value() const = delete;
+
+ template <typename U>
+ value_type& ensure(U&& defaultValue = value_type {})
+ {
+ return this->has_value() ? this->operator*()
+ : this->emplace(std::forward<U>(defaultValue));
+ }
+ value_type& ensure(const value_type& defaultValue)
+ {
+ return ensure<>(defaultValue);
+ }
+ value_type& ensure(value_type&& defaultValue)
+ {
+ return ensure<>(std::move(defaultValue));
+ }
+
+ //! Merge the value from another Omittable
+ //! \return true if \p other is not omitted and the value of
+ //! the current Omittable was different (or omitted),
+ //! in other words, if the current Omittable has changed;
+ //! false otherwise
+ template <typename T1>
+ auto merge(const std::optional<T1>& other)
+ -> std::enable_if_t<std::is_convertible_v<T1, T>, bool>
+ {
+ if (!other || (this->has_value() && **this == *other))
+ return false;
+ this->emplace(*other);
+ return true;
+ }
+
+ // Hide non-const lvalue operator-> and operator* as these are
+ // a bit too surprising: value() & doesn't lazy-create an object;
+ // and it's too easy to inadvertently change the underlying value.
+
+ const value_type* operator->() const& { return base_type::operator->(); }
+ value_type* operator->() && { return base_type::operator->(); }
+ const value_type& operator*() const& { return base_type::operator*(); }
+ value_type& operator*() && { return base_type::operator*(); }
+
+ // The below is inspired by the proposed std::optional monadic operations
+ // (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0798r6.html).
+
+ //! \brief Lift a callable into the Omittable
+ //!
+ //! 'Lifting', as used in functional programming, means here invoking
+ //! a callable (e.g., a function) on the contents of the Omittable if it has
+ //! any and wrapping the returned value (that may be of a different type T2)
+ //! into a new Omittable\<T2>. If the current Omittable is empty,
+ //! the invocation is skipped altogether and Omittable\<T2>{none} is
+ //! returned instead.
+ //! \note if \p fn already returns an Omittable (i.e., it is a 'functor',
+ //! in functional programming terms), then() will not wrap another
+ //! Omittable around but will just return what \p fn returns. The
+ //! same doesn't hold for the parameter: if \p fn accepts an Omittable
+ //! you have to wrap it in another Omittable before calling then().
+ //! \return `none` if the current Omittable has `none`;
+ //! otherwise, the Omittable returned from a call to \p fn
+ //! \tparam FnT a callable with \p T (or <tt>const T&</tt>)
+ //! returning Omittable<T2>, T2 is any supported type
+ //! \sa then_or, transform
+ template <typename FnT>
+ auto then(FnT&& fn) const&
+ {
+ return lift(std::forward<FnT>(fn), *this);
+ }
+
+ //! \brief Lift a callable into the rvalue Omittable
+ //!
+ //! This is an rvalue overload for then().
+ template <typename FnT>
+ auto then(FnT&& fn) &&
+ {
+ return lift(std::forward<FnT>(fn), *this);
+ }
+
+ //! \brief Lift a callable into the const lvalue Omittable, with a fallback
+ //!
+ //! This effectively does the same what then() does, except that it returns
+ //! a value of type returned by the callable, or the provided fallback value
+ //! if the current Omittable is empty. This is a typesafe version to apply
+ //! an operation on an Omittable without having to deal with another
+ //! Omittable afterwards.
+ template <typename FnT, typename FallbackT>
+ auto then_or(FnT&& fn, FallbackT&& fallback) const&
+ {
+ return then(std::forward<FnT>(fn))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+
+ //! \brief Lift a callable into the rvalue Omittable, with a fallback
+ //!
+ //! This is an overload for functions that accept rvalue
+ template <typename FnT, typename FallbackT>
+ auto then_or(FnT&& fn, FallbackT&& fallback) &&
+ {
+ return then(std::forward<FnT>(fn))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+};
+
+template <typename T>
+Omittable(T&&) -> Omittable<T>;
+
+//! \brief Merge the value from an optional
+//! This is an adaptation of Omittable::merge() to the case when the value
+//! on the left hand side is not an Omittable.
+//! \return true if \p rhs is not omitted and the \p lhs value was different,
+//! in other words, if \p lhs has changed;
+//! false otherwise
+template <typename T1, typename T2>
+inline auto merge(T1& lhs, const std::optional<T2>& rhs)
+ -> std::enable_if_t<std::is_assignable_v<T1&, const T2&>, bool>
+{
+ if (!rhs || lhs == *rhs)
+ return false;
+ lhs = *rhs;
+ return true;
+}
+
+} // namespace Quotient
diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h
index 46294499..86593cc8 100644
--- a/lib/qt_connection_util.h
+++ b/lib/qt_connection_util.h
@@ -3,7 +3,7 @@
#pragma once
-#include "util.h"
+#include "function_traits.h"
#include <QtCore/QPointer>
diff --git a/lib/quotient_common.h b/lib/quotient_common.h
index 0e3e2a40..2b785a39 100644
--- a/lib/quotient_common.h
+++ b/lib/quotient_common.h
@@ -3,31 +3,69 @@
#pragma once
+#include "quotient_export.h"
+
#include <qobjectdefs.h>
#include <array>
-// See https://bugreports.qt.io/browse/QTBUG-82295 - despite the comment that
-// Q_FLAG[_NS] "should" be applied to the enum only, Qt doesn't allow to wrap
-// a flag type into a QVariant then. The macros below define Q_FLAG[_NS] and on
-// top of that add Q_ENUM[_NS]_IMPL which is a part of Q_ENUM() macro that
-// enables the metatype data but goes under the moc radar to avoid double
-// registration of the same data in the map defined in moc_*.cpp
+
+//! \brief Quotient replacement for the Q_FLAG/Q_DECLARE_FLAGS combination
+//!
+//! Although the comment in QTBUG-82295 says that Q_FLAG[_NS] "should" be
+//! applied to the enum type only, Qt then doesn't allow to wrap the
+//! corresponding flag type (defined with Q_DECLARE_FLAGS) into a QVariant.
+//! This macro defines Q_FLAG and on top of that adds Q_ENUM_IMPL which is
+//! a part of Q_ENUM() macro that enables the metatype data but goes under
+//! the moc radar to avoid double registration of the same data in the map
+//! defined in moc_*.cpp.
+//!
+//! Simply put, instead of using Q_FLAG/Q_DECLARE_FLAGS combo (and struggling
+//! to figure out what you should pass to Q_FLAG if you want to make it
+//! wrappable in a QVariant) use the macro below, and things will just work.
+//!
+//! \sa https://bugreports.qt.io/browse/QTBUG-82295
#define QUO_DECLARE_FLAGS(Flags, Enum) \
Q_DECLARE_FLAGS(Flags, Enum) \
Q_ENUM_IMPL(Enum) \
Q_FLAG(Flags)
+//! \brief Quotient replacement for the Q_FLAG_NS/Q_DECLARE_FLAGS combination
+//!
+//! This is the equivalent of QUO_DECLARE_FLAGS for enums declared at the
+//! namespace level (be sure to provide Q_NAMESPACE _in the same file_
+//! as the enum definition and this macro).
+//! \sa QUO_DECLARE_FLAGS
#define QUO_DECLARE_FLAGS_NS(Flags, Enum) \
Q_DECLARE_FLAGS(Flags, Enum) \
Q_ENUM_NS_IMPL(Enum) \
Q_FLAG_NS(Flags)
+// Apple Clang hasn't caught up with explicit(bool) yet
+#if __cpp_conditional_explicit >= 201806L
+#define QUO_IMPLICIT explicit(false)
+#else
+#define QUO_IMPLICIT
+#endif
+
#define DECL_DEPRECATED_ENUMERATOR(Deprecated, Recommended) \
Deprecated Q_DECL_ENUMERATOR_DEPRECATED_X("Use " #Recommended) = Recommended
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+// The first line forward-declares the namespace static metaobject with
+// QUOTIENT_API so that dynamically linked clients could serialise flag/enum
+// values from the namespace; Qt before 5.14 doesn't help with that. The second
+// line is needed for moc to do its job on the namespace.
+#define QUO_NAMESPACE \
+ extern QUOTIENT_API const QMetaObject staticMetaObject; \
+ Q_NAMESPACE
+#else
+// Since Qt 5.14.0, it's all packed in a single macro
+#define QUO_NAMESPACE Q_NAMESPACE_EXPORT(QUOTIENT_API)
+#endif
+
namespace Quotient {
-Q_NAMESPACE
+QUO_NAMESPACE
// std::array {} needs explicit template parameters on macOS because
// Apple stdlib doesn't have deduction guides for std::array. C++20 has
@@ -62,7 +100,7 @@ enum class Membership : unsigned int {
};
QUO_DECLARE_FLAGS_NS(MembershipMask, Membership)
-constexpr inline auto MembershipStrings = make_array(
+constexpr auto MembershipStrings = make_array(
// The order MUST be the same as the order in the original enum
"join", "leave", "invite", "knock", "ban");
@@ -80,7 +118,7 @@ enum class JoinState : std::underlying_type_t<Membership> {
};
QUO_DECLARE_FLAGS_NS(JoinStates, JoinState)
-constexpr inline auto JoinStateStrings = make_array(
+[[maybe_unused]] constexpr auto JoinStateStrings = make_array(
MembershipStrings[0], MembershipStrings[1], MembershipStrings[2],
MembershipStrings[3] /* same as MembershipStrings, sans "ban" */
);
@@ -110,9 +148,7 @@ enum RoomType {
};
Q_ENUM_NS(RoomType)
-constexpr inline auto RoomTypeStrings = make_array(
- "m.space"
-);
+[[maybe_unused]] constexpr auto RoomTypeStrings = make_array("m.space");
} // namespace Quotient
Q_DECLARE_OPERATORS_FOR_FLAGS(Quotient::MembershipMask)
diff --git a/lib/quotient_export.h b/lib/quotient_export.h
new file mode 100644
index 00000000..5a6edb0e
--- /dev/null
+++ b/lib/quotient_export.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#ifdef QUOTIENT_STATIC
+# define QUOTIENT_API
+# define QUOTIENT_HIDDEN
+#else
+# ifndef QUOTIENT_API
+# ifdef BUILDING_SHARED_QUOTIENT
+ /* We are building this library */
+# define QUOTIENT_API Q_DECL_EXPORT
+# else
+ /* We are using this library */
+# define QUOTIENT_API Q_DECL_IMPORT
+# endif
+# endif
+
+# ifndef QUOTIENT_HIDDEN
+# define QUOTIENT_HIDDEN Q_DECL_HIDDEN
+# endif
+#endif
diff --git a/lib/room.cpp b/lib/room.cpp
index 0fc7d23e..ea9915c3 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -15,6 +15,7 @@
#include "syncdata.h"
#include "user.h"
#include "eventstats.h"
+#include "roomstateview.h"
// NB: since Qt 6, moc_room.cpp needs User fully defined
#include "moc_room.cpp"
@@ -104,7 +105,7 @@ public:
static decltype(baseState) stubbedState;
/// The state of the room at syncEdge()
/// \sa syncEdge
- QHash<StateEventKey, const StateEventBase*> currentState;
+ RoomStateView currentState;
/// Servers with aliases for this room except the one of the local user
/// \sa Room::remoteAliases
QSet<QString> aliasServers;
@@ -220,6 +221,7 @@ public:
evtKey.second));
qCDebug(STATE) << "A new stub event created for key {"
<< evtKey.first << evtKey.second << "}";
+ qCDebug(STATE) << "Stubbed state size:" << stubbedState.size();
}
evt = stubbedState[evtKey].get();
Q_ASSERT(evt);
@@ -229,34 +231,6 @@ public:
return evt;
}
- QVector<const StateEventBase*> stateEventsOfType(const QString& evtType) const
- {
- auto vals = QVector<const StateEventBase*>();
- for (auto it = currentState.cbegin(); it != currentState.cend(); ++it)
- if (it.key().first == evtType)
- vals.append(it.value());
-
- return vals;
- }
-
- template <typename EventT>
- const EventT* getCurrentState(const QString& stateKey = {}) const
- {
- const auto* evt = getCurrentState({ EventT::matrixTypeId(), stateKey });
- Q_ASSERT(evt->type() == EventT::typeId()
- && evt->matrixType() == EventT::matrixTypeId());
- return static_cast<const EventT*>(evt);
- }
-
-// template <typename EventT>
-// const auto& getCurrentStateContent(const QString& stateKey = {}) const
-// {
-// if (const auto* evt =
-// currentState.value({ EventT::matrixTypeId(), stateKey }, nullptr))
-// return evt->content();
-// return EventT::content_type()
-// }
-
template <typename EventArrayT>
Changes updateStateFrom(EventArrayT&& events)
{
@@ -326,7 +300,9 @@ public:
QString doSendEvent(const RoomEvent* pEvent);
void onEventSendingFailure(const QString& txnId, BaseJob* call = nullptr);
- SetRoomStateWithKeyJob* requestSetState(const StateEventBase& event)
+ SetRoomStateWithKeyJob* requestSetState(const QString& evtType,
+ const QString& stateKey,
+ const QJsonObject& contentJson)
{
// if (event.roomId().isEmpty())
// event.setRoomId(id);
@@ -334,14 +310,8 @@ public:
// event.setSender(connection->userId());
// TODO: Queue up state events sending (see #133).
// TODO: Maybe addAsPending() as well, despite having no txnId
- return connection->callApi<SetRoomStateWithKeyJob>(
- id, event.matrixType(), event.stateKey(), event.contentJson());
- }
-
- template <typename EvT, typename... ArgTs>
- auto requestSetState(ArgTs&&... args)
- {
- return requestSetState(EvT(std::forward<ArgTs>(args)...));
+ return connection->callApi<SetRoomStateWithKeyJob>(id, evtType, stateKey,
+ contentJson);
}
/*! Apply redaction to the timeline
@@ -397,8 +367,7 @@ public:
const QString& eventId,
QDateTime timestamp)
{
- const auto senderSessionPairKey = make_pair(senderKey, sessionId);
- auto groupSessionIt = groupSessions.find(senderSessionPairKey);
+ auto groupSessionIt = groupSessions.find({ senderKey, sessionId });
if (groupSessionIt == groupSessions.end()) {
// qCWarning(E2EE) << "Unable to decrypt event" << eventId
// << "The sender's device has not sent us the keys for "
@@ -472,8 +441,8 @@ const QString& Room::id() const { return d->id; }
QString Room::version() const
{
- const auto v = d->getCurrentState<RoomCreateEvent>()->version();
- return v.isEmpty() ? QStringLiteral("1") : v;
+ const auto v = currentState().query(&RoomCreateEvent::version);
+ return v && !v->isEmpty() ? *v : QStringLiteral("1");
}
bool Room::isUnstable() const
@@ -484,7 +453,10 @@ bool Room::isUnstable() const
QString Room::predecessorId() const
{
- return d->getCurrentState<RoomCreateEvent>()->predecessor().roomId;
+ if (const auto* evt = currentState().get<RoomCreateEvent>())
+ return evt->predecessor().roomId;
+
+ return {};
}
Room* Room::predecessor(JoinStates statesFilter) const
@@ -499,7 +471,8 @@ Room* Room::predecessor(JoinStates statesFilter) const
QString Room::successorId() const
{
- return d->getCurrentState<RoomTombstoneEvent>()->successorRoomId();
+ return currentState().queryOr(&RoomTombstoneEvent::successorRoomId,
+ QString());
}
Room* Room::successor(JoinStates statesFilter) const
@@ -526,30 +499,47 @@ bool Room::allHistoryLoaded() const
QString Room::name() const
{
- return d->getCurrentState<RoomNameEvent>()->name();
+ return currentState().queryOr(&RoomNameEvent::name, QString());
}
QStringList Room::aliases() const
{
- const auto* evt = d->getCurrentState<RoomCanonicalAliasEvent>();
- auto result = evt->altAliases();
- if (!evt->alias().isEmpty())
- result << evt->alias();
- return result;
+ if (const auto* evt = currentState().get<RoomCanonicalAliasEvent>()) {
+ auto result = evt->altAliases();
+ if (!evt->alias().isEmpty())
+ result << evt->alias();
+ return result;
+ }
+ return {};
}
QStringList Room::altAliases() const
{
- return d->getCurrentState<RoomCanonicalAliasEvent>()->altAliases();
+ return currentState().queryOr(&RoomCanonicalAliasEvent::altAliases,
+ QStringList());
}
QString Room::canonicalAlias() const
{
- return d->getCurrentState<RoomCanonicalAliasEvent>()->alias();
+ return currentState().queryOr(&RoomCanonicalAliasEvent::alias, QString());
}
QString Room::displayName() const { return d->displayname; }
+QStringList Room::pinnedEventIds() const {
+ return currentState().queryOr(&RoomPinnedEvent::pinnedEvents, QStringList());
+}
+
+QVector<const Quotient::RoomEvent*> Quotient::Room::pinnedEvents() const
+{
+ QVector<const RoomEvent*> pinnedEvents;
+ for (const auto& evtId : pinnedEventIds())
+ if (const auto& it = findInTimeline(evtId); it != historyEdge())
+ pinnedEvents.append(it->event());
+
+ return pinnedEvents;
+}
+
QString Room::displayNameForHtml() const
{
return displayName().toHtmlEscaped();
@@ -559,7 +549,7 @@ void Room::refreshDisplayName() { d->updateDisplayname(); }
QString Room::topic() const
{
- return d->getCurrentState<RoomTopicEvent>()->topic();
+ return currentState().queryOr(&RoomTopicEvent::topic, QString());
}
QString Room::avatarMediaId() const { return d->avatar.mediaId(); }
@@ -598,7 +588,8 @@ JoinState Room::memberJoinState(User* user) const
Membership Room::memberState(const QString& userId) const
{
- return d->getCurrentState<RoomMemberEvent>(userId)->membership();
+ return currentState().queryOr(userId, &RoomMemberEvent::membership,
+ Membership::Leave);
}
bool Room::isMember(const QString& userId) const
@@ -876,8 +867,9 @@ bool Room::canSwitchVersions() const
if (!successorId().isEmpty())
return false; // No one can upgrade a room that's already upgraded
- if (const auto* plEvt = d->getCurrentState<RoomPowerLevelsEvent>()) {
- const auto currentUserLevel = plEvt->powerLevelForUser(localUser()->id());
+ if (const auto* plEvt = currentState().get<RoomPowerLevelsEvent>()) {
+ const auto currentUserLevel =
+ plEvt->powerLevelForUser(localUser()->id());
const auto tombstonePowerLevel =
plEvt->powerLevelForState("m.room.tombstone"_ls);
return currentUserLevel >= tombstonePowerLevel;
@@ -973,18 +965,28 @@ Room::findPendingEvent(const QString& txnId) const
});
}
-const Room::RelatedEvents Room::relatedEvents(const QString& evtId,
- const char* relType) const
+const Room::RelatedEvents Room::relatedEvents(
+ const QString& evtId, EventRelation::reltypeid_t relType) const
{
return d->relations.value({ evtId, relType });
}
-const Room::RelatedEvents Room::relatedEvents(const RoomEvent& evt,
- const char* relType) const
+const Room::RelatedEvents Room::relatedEvents(
+ const RoomEvent& evt, EventRelation::reltypeid_t relType) const
{
return relatedEvents(evt.id(), relType);
}
+const RoomCreateEvent* Room::creation() const
+{
+ return currentState().get<RoomCreateEvent>();
+}
+
+const RoomTombstoneEvent *Room::tombstone() const
+{
+ return currentState().get<RoomTombstoneEvent>();
+}
+
void Room::Private::getAllMembers()
{
// If already loaded or already loading, there's nothing to do here.
@@ -1449,7 +1451,9 @@ int Room::timelineSize() const { return int(d->timeline.size()); }
bool Room::usesEncryption() const
{
- return !d->getCurrentState<EncryptionEvent>()->algorithm().isEmpty();
+ return !currentState()
+ .queryOr(&EncryptionEvent::algorithm, QString())
+ .isEmpty();
}
const StateEventBase* Room::getCurrentState(const QString& evtType,
@@ -1458,13 +1462,7 @@ const StateEventBase* Room::getCurrentState(const QString& evtType,
return d->getCurrentState({ evtType, stateKey });
}
-const QVector<const StateEventBase*>
-Room::stateEventsOfType(const QString& evtType) const
-{
- return d->stateEventsOfType(evtType);
-}
-
-const QHash<StateEventKey, const StateEventBase*>& Room::currentState() const
+RoomStateView Room::currentState() const
{
return d->currentState;
}
@@ -1558,7 +1556,7 @@ Room::Changes Room::Private::setSummary(RoomSummary&& newSummary)
void Room::Private::insertMemberIntoMap(User* u)
{
const auto maybeUserName =
- getCurrentState<RoomMemberEvent>(u->id())->newDisplayName();
+ currentState.query(u->id(), &RoomMemberEvent::newDisplayName);
if (!maybeUserName)
qCWarning(MEMBERS) << "insertMemberIntoMap():" << u->id()
<< "has no name (even empty)";
@@ -1588,9 +1586,9 @@ void Room::Private::insertMemberIntoMap(User* u)
void Room::Private::removeMemberFromMap(User* u)
{
- const auto userName =
- getCurrentState<RoomMemberEvent>(
- u->id())->newDisplayName().value_or(QString());
+ const auto userName = currentState.queryOr(u->id(),
+ &RoomMemberEvent::newDisplayName,
+ QString());
qCDebug(MEMBERS) << "removeMemberFromMap(), username" << userName
<< "for user" << u->id();
@@ -1673,10 +1671,13 @@ Room::Private::moveEventsToTimeline(RoomEventsRange events,
QString Room::memberName(const QString& mxId) const
{
// See https://github.com/matrix-org/matrix-doc/issues/1375
- const auto rme = getCurrentState<RoomMemberEvent>(mxId);
- return rme->newDisplayName() ? *rme->newDisplayName()
- : rme->prevContent() ? rme->prevContent()->displayName.value_or(QString())
- : QString();
+ if (const auto rme = currentState().get<RoomMemberEvent>(mxId)) {
+ if (rme->newDisplayName())
+ return *rme->newDisplayName();
+ if (rme->prevContent() && rme->prevContent()->displayName)
+ return *rme->prevContent()->displayName;
+ }
+ return {};
}
QString Room::roomMembername(const User* u) const
@@ -1731,10 +1732,13 @@ QString Room::htmlSafeMemberName(const QString& userId) const
QUrl Room::memberAvatarUrl(const QString &mxId) const
{
// See https://github.com/matrix-org/matrix-doc/issues/1375
- const auto rme = getCurrentState<RoomMemberEvent>(mxId);
- return rme->newAvatarUrl() ? *rme->newAvatarUrl()
- : rme->prevContent() ? rme->prevContent()->avatarUrl.value_or(QUrl())
- : QUrl();
+ if (const auto rme = currentState().get<RoomMemberEvent>(mxId)) {
+ if (rme->newAvatarUrl())
+ return *rme->newAvatarUrl();
+ if (rme->prevContent() && rme->prevContent()->avatarUrl)
+ return *rme->prevContent()->avatarUrl;
+ }
+ return {};
}
Room::Changes Room::Private::updateStatsFromSyncData(const SyncRoomData& data,
@@ -2126,29 +2130,41 @@ QString Room::postJson(const QString& matrixType,
return d->sendEvent(loadEvent<RoomEvent>(matrixType, eventContent));
}
-SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt) const
+SetRoomStateWithKeyJob* Room::setState(const StateEventBase& evt)
+{
+ return d->requestSetState(evt.matrixType(), evt.stateKey(),
+ evt.contentJson());
+}
+
+SetRoomStateWithKeyJob* Room::setState(const QString& evtType,
+ const QString& stateKey,
+ const QJsonObject& contentJson)
{
- return d->requestSetState(evt);
+ return d->requestSetState(evtType, stateKey, contentJson);
}
void Room::setName(const QString& newName)
{
- d->requestSetState<RoomNameEvent>(newName);
+ setState<RoomNameEvent>(newName);
}
void Room::setCanonicalAlias(const QString& newAlias)
{
- d->requestSetState<RoomCanonicalAliasEvent>(newAlias, altAliases());
+ setState<RoomCanonicalAliasEvent>(newAlias, altAliases());
}
+void Room::setPinnedEvents(const QStringList& events)
+{
+ setState<RoomPinnedEvent>(events);
+}
void Room::setLocalAliases(const QStringList& aliases)
{
- d->requestSetState<RoomCanonicalAliasEvent>(canonicalAlias(), aliases);
+ setState<RoomCanonicalAliasEvent>(canonicalAlias(), aliases);
}
void Room::setTopic(const QString& newTopic)
{
- d->requestSetState<RoomTopicEvent>(newTopic);
+ setState<RoomTopicEvent>(newTopic);
}
bool isEchoEvent(const RoomEventPtr& le, const PendingEventItem& re)
@@ -2488,12 +2504,14 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction)
auto oldEvent = ti.replaceEvent(makeRedacted(*ti, redaction));
qCDebug(EVENTS) << "Redacted" << oldEvent->id() << "with" << redaction.id();
if (oldEvent->isStateEvent()) {
- const StateEventKey evtKey { oldEvent->matrixType(),
- oldEvent->stateKey() };
- Q_ASSERT(currentState.contains(evtKey));
- if (currentState.value(evtKey) == oldEvent.get()) {
- Q_ASSERT(ti.index() >= 0); // Historical states can't be in
- // currentState
+ // Check whether the old event was a part of current state; if it was,
+ // update the current state to the redacted event object.
+ const auto currentStateEvt =
+ currentState.get(oldEvent->matrixType(), oldEvent->stateKey());
+ Q_ASSERT(currentStateEvt);
+ if (currentStateEvt == oldEvent.get()) {
+ // Historical states can't be in currentState
+ Q_ASSERT(ti.index() >= 0);
qCDebug(STATE).nospace()
<< "Redacting state " << oldEvent->matrixType() << "/"
<< oldEvent->stateKey();
@@ -2505,8 +2523,7 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction)
}
if (const auto* reaction = eventCast<ReactionEvent>(oldEvent)) {
const auto& targetEvtId = reaction->relation().eventId;
- const auto lookupKey =
- qMakePair(targetEvtId, EventRelation::Annotation());
+ const QPair lookupKey { targetEvtId, EventRelation::AnnotationType };
if (relations.contains(lookupKey)) {
relations[lookupKey].removeOne(reaction);
emit q->updatedEvent(targetEvtId);
@@ -2514,6 +2531,7 @@ bool Room::Private::processRedaction(const RedactionEvent& redaction)
}
q->onRedaction(*oldEvent, *ti);
emit q->replacedEvent(ti.event(), rawPtr(oldEvent));
+ // By now, all references to oldEvent must have been updated to ti.event()
return true;
}
@@ -2784,7 +2802,7 @@ void Room::Private::addHistoricalMessageEvents(RoomEvents&& events)
for (const auto& eptr : events) {
const auto& e = *eptr;
if (e.isStateEvent()
- && !currentState.contains({ e.matrixType(), e.stateKey() })) {
+ && !currentState.contains(e.matrixType(), e.stateKey())) {
changes |= q->processStateEvent(e);
}
}
@@ -2821,13 +2839,11 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
return Change::None;
// Find a value (create an empty one if necessary) and get a reference
- // to it. Can't use getCurrentState<>() because it (creates and) returns
- // a stub if a value is not found, and what's needed here is a "real" event
- // or nullptr.
+ // to it, anticipating a change further in the function.
auto& curStateEvent = d->currentState[{ e.matrixType(), e.stateKey() }];
// Prepare for the state change
// clang-format off
- const bool proceed = visit(e
+ const bool proceed = switchOnType(e
, [this, curStateEvent](const RoomMemberEvent& rme) {
// clang-format on
auto* oldRme = static_cast<const RoomMemberEvent*>(curStateEvent);
@@ -2925,7 +2941,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
// Update internal structures as per the change and work out the return value
// clang-format off
- const auto result = visit(e
+ const auto result = switchOnType(e
, [] (const RoomNameEvent&) {
return Change::Name;
}
@@ -2933,7 +2949,7 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
// clang-format on
setObjectName(cae.alias().isEmpty() ? d->id : cae.alias());
const auto* oldCae =
- static_cast<const RoomCanonicalAliasEvent*>(oldStateEvent);
+ static_cast<const RoomCanonicalAliasEvent*>(oldStateEvent);
QStringList previousAltAliases {};
if (oldCae) {
previousAltAliases = oldCae->altAliases();
@@ -2945,10 +2961,15 @@ Room::Changes Room::processStateEvent(const RoomEvent& e)
if (!cae.alias().isEmpty())
newAliases.push_front(cae.alias());
- connection()->updateRoomAliases(id(), previousAltAliases, newAliases);
+ connection()->updateRoomAliases(id(), previousAltAliases,
+ newAliases);
return Change::Aliases;
// clang-format off
}
+ , [this] (const RoomPinnedEvent&) {
+ emit pinnedEventsChanged();
+ return Change::Other;
+ }
, [] (const RoomTopicEvent&) {
return Change::Topic;
}
diff --git a/lib/room.h b/lib/room.h
index 85c51a87..9f70d77a 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -10,6 +10,7 @@
#pragma once
#include "connection.h"
+#include "roomstateview.h"
#include "eventitem.h"
#include "quotient_common.h"
@@ -21,6 +22,7 @@
#include "events/roommessageevent.h"
#include "events/roomcreateevent.h"
#include "events/roomtombstoneevent.h"
+#include "events/eventrelation.h"
#include <QtCore/QJsonObject>
#include <QtGui/QImage>
@@ -45,7 +47,7 @@ class RedactEventJob;
* This is specifically tuned to work with QML exposing all traits as
* Q_PROPERTY values.
*/
-class FileTransferInfo {
+class QUOTIENT_API FileTransferInfo {
Q_GADGET
Q_PROPERTY(bool isUpload MEMBER isUpload CONSTANT)
Q_PROPERTY(bool active READ active CONSTANT)
@@ -73,7 +75,7 @@ public:
//! \brief Data structure for a room member's read receipt
//! \sa Room::lastReadReceipt
-class ReadReceipt {
+class QUOTIENT_API ReadReceipt {
Q_GADGET
Q_PROPERTY(QString eventId MEMBER eventId CONSTANT)
Q_PROPERTY(QDateTime timestamp MEMBER timestamp CONSTANT)
@@ -101,7 +103,7 @@ struct EventStats;
struct Notification
{
enum Type { None = 0, Basic, Highlight };
- Q_ENUM(Notification)
+ Q_ENUM(Type)
Type type = None;
@@ -110,7 +112,7 @@ private:
Q_PROPERTY(Type type MEMBER type CONSTANT)
};
-class Room : public QObject {
+class QUOTIENT_API Room : public QObject {
Q_OBJECT
Q_PROPERTY(Connection* connection READ connection CONSTANT)
Q_PROPERTY(User* localUser READ localUser CONSTANT)
@@ -124,6 +126,8 @@ class Room : public QObject {
Q_PROPERTY(QStringList altAliases READ altAliases NOTIFY namesChanged)
Q_PROPERTY(QString canonicalAlias READ canonicalAlias NOTIFY namesChanged)
Q_PROPERTY(QString displayName READ displayName NOTIFY displaynameChanged)
+ Q_PROPERTY(QStringList pinnedEventIds READ pinnedEventIds WRITE setPinnedEvents
+ NOTIFY pinnedEventsChanged)
Q_PROPERTY(QString displayNameForHtml READ displayNameForHtml NOTIFY displaynameChanged)
Q_PROPERTY(QString topic READ topic NOTIFY topicChanged)
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged
@@ -253,6 +257,9 @@ public:
//! Get a list of both canonical and alternative aliases
QStringList aliases() const;
QString displayName() const;
+ QStringList pinnedEventIds() const;
+ // Returns events available locally, use pinnedEventIds() for full list
+ QVector<const RoomEvent*> pinnedEvents() const;
QString displayNameForHtml() const;
QString topic() const;
QString avatarMediaId() const;
@@ -389,18 +396,12 @@ public:
PendingEvents::const_iterator findPendingEvent(const QString& txnId) const;
const RelatedEvents relatedEvents(const QString& evtId,
- const char* relType) const;
+ EventRelation::reltypeid_t relType) const;
const RelatedEvents relatedEvents(const RoomEvent& evt,
- const char* relType) const;
+ EventRelation::reltypeid_t relType) const;
- const RoomCreateEvent* creation() const
- {
- return getCurrentState<RoomCreateEvent>();
- }
- const RoomTombstoneEvent* tombstone() const
- {
- return getCurrentState<RoomTombstoneEvent>();
- }
+ const RoomCreateEvent* creation() const;
+ const RoomTombstoneEvent* tombstone() const;
bool displayed() const;
/// Mark the room as currently displayed to the user
@@ -754,47 +755,45 @@ public:
/*! This method returns a (potentially empty) state event corresponding
* to the pair of event type \p evtType and state key \p stateKey.
*/
- Q_INVOKABLE const Quotient::StateEventBase*
+ [[deprecated("Use currentState().get() instead; "
+ "make sure to check its result for nullptrs")]] //
+ const Quotient::StateEventBase*
getCurrentState(const QString& evtType, const QString& stateKey = {}) const;
- /// Get all state events in the room.
- /*! This method returns all known state events that have occured in
- * the room, as a mapping from the event type and state key to value.
- */
- const QHash<StateEventKey, const StateEventBase*>& currentState() const;
-
- /// Get all state events in the room of a certain type.
- /*! This method returns all known state events that have occured in
- * the room of the given type.
- */
- Q_INVOKABLE const QVector<const StateEventBase*>
- stateEventsOfType(const QString& evtType) const;
-
/// Get a state event with the given event type and state key
/*! This is a typesafe overload that accepts a C++ event type instead of
* its Matrix name.
*/
template <typename EvT>
+ [[deprecated("Use currentState().get() instead; "
+ "make sure to check its result for nullptrs")]] //
const EvT* getCurrentState(const QString& stateKey = {}) const
{
- const auto* evt =
- eventCast<const EvT>(getCurrentState(EvT::matrixTypeId(), stateKey));
+ QT_IGNORE_DEPRECATIONS(
+ const auto* evt = eventCast<const EvT>(
+ getCurrentState(EvT::matrixTypeId(), stateKey));)
Q_ASSERT(evt);
Q_ASSERT(evt->matrixTypeId() == EvT::matrixTypeId()
&& evt->stateKey() == stateKey);
return evt;
}
- /// Set a state event of the given type with the given arguments
- /*! This typesafe overload attempts to send a state event with the type
- * \p EvT and the content defined by \p args. Specifically, the function
- * creates a temporary object of type \p EvT passing \p args to
- * the constructor, and sends a request to the homeserver using
- * the Matrix event type defined by \p EvT and the event content produced
- * via EvT::contentJson().
- */
+ /// \brief Get the current room state
+ RoomStateView currentState() const;
+
+ //! Send a request to update the room state with the given event
+ SetRoomStateWithKeyJob* setState(const StateEventBase& evt);
+
+ //! \brief Set a state event of the given type with the given arguments
+ //!
+ //! This typesafe overload attempts to send a state event with the type
+ //! \p EvT and the content defined by \p args. Specifically, the function
+ //! creates a temporary object of type \p EvT passing \p args to
+ //! the constructor, and sends a request to the homeserver using
+ //! the Matrix event type defined by \p EvT and the event content produced
+ //! via EvT::contentJson().
template <typename EvT, typename... ArgTs>
- auto setState(ArgTs&&... args) const
+ auto setState(ArgTs&&... args)
{
return setState(EvT(std::forward<ArgTs>(args)...));
}
@@ -828,10 +827,13 @@ public Q_SLOTS:
QString retryMessage(const QString& txnId);
void discardMessage(const QString& txnId);
- /// Send a request to update the room state with the given event
- SetRoomStateWithKeyJob* setState(const StateEventBase& evt) const;
+ //! Send a request to update the room state based on freeform inputs
+ SetRoomStateWithKeyJob* setState(const QString& evtType,
+ const QString& stateKey,
+ const QJsonObject& contentJson);
void setName(const QString& newName);
void setCanonicalAlias(const QString& newAlias);
+ void setPinnedEvents(const QStringList& events);
/// Set room aliases on the user's current server
void setLocalAliases(const QStringList& aliases);
void setTopic(const QString& newTopic);
@@ -938,6 +940,7 @@ Q_SIGNALS:
void namesChanged(Quotient::Room* room);
void displaynameAboutToChange(Quotient::Room* room);
void displaynameChanged(Quotient::Room* room, QString oldName);
+ void pinnedEventsChanged();
void topicChanged();
void avatarChanged();
void userAdded(Quotient::User* user);
@@ -1037,7 +1040,7 @@ private:
void setJoinState(JoinState state);
};
-class MemberSorter {
+class QUOTIENT_API MemberSorter {
public:
explicit MemberSorter(const Room* r) : room(r) {}
diff --git a/lib/roomstateview.cpp b/lib/roomstateview.cpp
new file mode 100644
index 00000000..94c88eee
--- /dev/null
+++ b/lib/roomstateview.cpp
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "roomstateview.h"
+
+using namespace Quotient;
+
+const StateEventBase* RoomStateView::get(const QString& evtType,
+ const QString& stateKey) const
+{
+ return value({ evtType, stateKey });
+}
+
+bool RoomStateView::contains(const QString& evtType,
+ const QString& stateKey) const
+{
+ return contains({ evtType, stateKey });
+}
+
+QJsonObject RoomStateView::contentJson(const QString& evtType,
+ const QString& stateKey) const
+{
+ return queryOr(evtType, stateKey, &Event::contentJson, QJsonObject());
+}
+
+const QVector<const StateEventBase*>
+RoomStateView::eventsOfType(const QString& evtType) const
+{
+ auto vals = QVector<const StateEventBase*>();
+ for (auto it = cbegin(); it != cend(); ++it)
+ if (it.key().first == evtType)
+ vals.append(it.value());
+
+ return vals;
+}
diff --git a/lib/roomstateview.h b/lib/roomstateview.h
new file mode 100644
index 00000000..cab69ae3
--- /dev/null
+++ b/lib/roomstateview.h
@@ -0,0 +1,127 @@
+// SPDX-FileCopyrightText: 2021 Kitsune Ral <kitsune-ral@users.sf.net>
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include "events/stateevent.h"
+
+#include <QtCore/QHash>
+
+namespace Quotient {
+
+class Room;
+
+class RoomStateView : private QHash<StateEventKey, const StateEventBase*> {
+ Q_GADGET
+public:
+ const QHash<StateEventKey, const StateEventBase*>& events() const
+ {
+ return *this;
+ }
+
+ //! \brief Get a state event with the given event type and state key
+ //! \return A state event corresponding to the pair of event type
+ //! \p evtType and state key \p stateKey, or nullptr if there's
+ //! no such \p evtType / \p stateKey combination in the current
+ //! state.
+ //! \warning In libQuotient 0.7 the return type changed to an OmittableCref
+ //! which is effectively a nullable const reference wrapper. You
+ //! have to check that it has_value() before using. Alternatively
+ //! you can now use queryCurrentState() to access state safely.
+ //! \sa getCurrentStateContentJson
+ const StateEventBase* get(const QString& evtType,
+ const QString& stateKey = {}) const;
+
+ //! \brief Get a state event with the given event type and state key
+ //!
+ //! This is a typesafe overload that accepts a C++ event type instead of
+ //! its Matrix name.
+ //! \warning In libQuotient 0.7 the return type changed to an Omittable with
+ //! a reference wrapper inside - you have to check that it
+ //! has_value() before using. Alternatively you can now use
+ //! queryCurrentState() to access state safely.
+ template <typename EvT>
+ const EvT* get(const QString& stateKey = {}) const
+ {
+ static_assert(std::is_base_of_v<StateEventBase, EvT>);
+ if (const auto* evt = get(EvT::matrixTypeId(), stateKey)) {
+ Q_ASSERT(evt->matrixType() == EvT::matrixTypeId()
+ && evt->stateKey() == stateKey);
+ return eventCast<const EvT>(evt);
+ }
+ return nullptr;
+ }
+
+ using QHash::contains;
+
+ bool contains(const QString& evtType, const QString& stateKey = {}) const;
+
+ template <typename EvT>
+ bool contains(const QString& stateKey = {}) const
+ {
+ return contains(EvT::matrixTypeId(), stateKey);
+ }
+
+ //! \brief Get the content of the current state event with the given
+ //! event type and state key
+ //! \return An empty object if there's no event in the current state with
+ //! this event type and state key; the contents of the event
+ //! <tt>'content'</tt> object otherwise
+ Q_INVOKABLE QJsonObject contentJson(const QString& evtType,
+ const QString& stateKey = {}) const;
+
+ //! \brief Get all state events in the room of a certain type.
+ //!
+ //! This method returns all known state events that have occured in
+ //! the room of the given type.
+ const QVector<const StateEventBase*>
+ eventsOfType(const QString& evtType) const;
+
+ template <typename FnT>
+ auto query(const QString& evtType, const QString& stateKey, FnT&& fn) const
+ {
+ return lift(std::forward<FnT>(fn), get(evtType, stateKey));
+ }
+
+ template <typename FnT>
+ auto query(const QString& stateKey, FnT&& fn) const
+ {
+ using EventT = std::decay_t<fn_arg_t<FnT>>;
+ static_assert(std::is_base_of_v<StateEventBase, EventT>);
+ return lift(std::forward<FnT>(fn), get<EventT>(stateKey));
+ }
+
+ template <typename FnT, typename FallbackT>
+ auto queryOr(const QString& evtType, const QString& stateKey, FnT&& fn,
+ FallbackT&& fallback) const
+ {
+ return lift(std::forward<FnT>(fn), get(evtType, stateKey))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+
+ template <typename FnT>
+ auto query(FnT&& fn) const
+ {
+ return query({}, std::forward<FnT>(fn));
+ }
+
+ template <typename FnT, typename FallbackT>
+ auto queryOr(const QString& stateKey, FnT&& fn, FallbackT&& fallback) const
+ {
+ using EventT = std::decay_t<fn_arg_t<FnT>>;
+ static_assert(std::is_base_of_v<StateEventBase, EventT>);
+ return lift(std::forward<FnT>(fn), get<EventT>(stateKey))
+ .value_or(std::forward<FallbackT>(fallback));
+ }
+
+ template <typename FnT, typename FallbackT>
+ auto queryOr(FnT&& fn, FallbackT&& fallback) const
+ {
+ return queryOr({}, std::forward<FnT>(fn),
+ std::forward<FallbackT>(fallback));
+ }
+
+private:
+ friend class Room;
+};
+} // namespace Quotient
diff --git a/lib/settings.cpp b/lib/settings.cpp
index f9b4f471..510d253c 100644
--- a/lib/settings.cpp
+++ b/lib/settings.cpp
@@ -3,6 +3,7 @@
#include "settings.h"
+#include "util.h"
#include "logging.h"
#include <QtCore/QUrl>
@@ -102,17 +103,18 @@ void SettingsGroup::remove(const QString& key)
Settings::remove(fullKey);
}
-QTNT_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {},
+QUO_DEFINE_SETTING(AccountSettings, QString, deviceId, "device_id", {},
setDeviceId)
-QTNT_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {},
+QUO_DEFINE_SETTING(AccountSettings, QString, deviceName, "device_name", {},
setDeviceName)
-QTNT_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false,
+QUO_DEFINE_SETTING(AccountSettings, bool, keepLoggedIn, "keep_logged_in", false,
setKeepLoggedIn)
-static const auto HomeserverKey = QStringLiteral("homeserver");
-static const auto AccessTokenKey = QStringLiteral("access_token");
-static const auto EncryptionAccountPickleKey =
- QStringLiteral("encryption_account_pickle");
+namespace {
+constexpr auto HomeserverKey = "homeserver"_ls;
+constexpr auto AccessTokenKey = "access_token"_ls;
+constexpr auto EncryptionAccountPickleKey = "encryption_account_pickle"_ls;
+}
QUrl AccountSettings::homeserver() const
{
diff --git a/lib/settings.h b/lib/settings.h
index efd0d714..ff99d488 100644
--- a/lib/settings.h
+++ b/lib/settings.h
@@ -3,6 +3,8 @@
#pragma once
+#include "quotient_export.h"
+
#include <QtCore/QSettings>
#include <QtCore/QUrl>
#include <QtCore/QVector>
@@ -11,7 +13,7 @@ class QVariant;
namespace Quotient {
-class Settings : public QSettings {
+class QUOTIENT_API Settings : public QSettings {
Q_OBJECT
public:
/// Add a legacy organisation/application name to migrate settings from
@@ -76,7 +78,7 @@ protected:
QSettings legacySettings { legacyOrganizationName, legacyApplicationName };
};
-class SettingsGroup : public Settings {
+class QUOTIENT_API SettingsGroup : public Settings {
public:
explicit SettingsGroup(QString path, QObject* parent = nullptr)
: Settings(parent)
@@ -104,7 +106,7 @@ private:
QString groupPath;
};
-#define QTNT_DECLARE_SETTING(type, propname, setter) \
+#define QUO_DECLARE_SETTING(type, propname, setter) \
Q_PROPERTY(type propname READ propname WRITE setter) \
public: \
type propname() const; \
@@ -112,7 +114,7 @@ public: \
\
private:
-#define QTNT_DEFINE_SETTING(classname, type, propname, qsettingname, \
+#define QUO_DEFINE_SETTING(classname, type, propname, qsettingname, \
defaultValue, setter) \
type classname::propname() const \
{ \
@@ -124,12 +126,12 @@ private:
setValue(QStringLiteral(qsettingname), std::move(newValue)); \
}
-class AccountSettings : public SettingsGroup {
+class QUOTIENT_API AccountSettings : public SettingsGroup {
Q_OBJECT
Q_PROPERTY(QString userId READ userId CONSTANT)
- QTNT_DECLARE_SETTING(QString, deviceId, setDeviceId)
- QTNT_DECLARE_SETTING(QString, deviceName, setDeviceName)
- QTNT_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn)
+ QUO_DECLARE_SETTING(QString, deviceId, setDeviceId)
+ QUO_DECLARE_SETTING(QString, deviceName, setDeviceName)
+ QUO_DECLARE_SETTING(bool, keepLoggedIn, setKeepLoggedIn)
Q_PROPERTY(QByteArray encryptionAccountPickle READ encryptionAccountPickle
WRITE setEncryptionAccountPickle)
public:
diff --git a/lib/ssosession.cpp b/lib/ssosession.cpp
index a1d27504..93e252cc 100644
--- a/lib/ssosession.cpp
+++ b/lib/ssosession.cpp
@@ -15,10 +15,10 @@ using namespace Quotient;
class SsoSession::Private {
public:
- Private(SsoSession* q, const QString& initialDeviceName = {},
- const QString& deviceId = {}, Connection* connection = nullptr)
- : initialDeviceName(initialDeviceName)
- , deviceId(deviceId)
+ Private(SsoSession* q, QString initialDeviceName = {},
+ QString deviceId = {}, Connection* connection = nullptr)
+ : initialDeviceName(std::move(initialDeviceName))
+ , deviceId(std::move(deviceId))
, connection(connection)
{
auto* server = new QTcpServer(q);
@@ -29,7 +29,7 @@ public:
.arg(server->serverPort());
ssoUrl = connection->getUrlForApi<RedirectToSSOJob>(callbackUrl);
- QObject::connect(server, &QTcpServer::newConnection, q, [this, server] {
+ QObject::connect(server, &QTcpServer::newConnection, q, [this, q, server] {
qCDebug(MAIN) << "SSO callback initiated";
socket = server->nextPendingConnection();
server->close();
@@ -43,8 +43,14 @@ public:
});
QObject::connect(socket, &QTcpSocket::disconnected, socket,
&QTcpSocket::deleteLater);
+ QObject::connect(socket, &QObject::destroyed, q,
+ &QObject::deleteLater);
});
+ qCDebug(MAIN) << "SSO session constructed";
}
+ ~Private() { qCDebug(MAIN) << "SSO session deconstructed"; }
+ Q_DISABLE_COPY_MOVE(Private)
+
void processCallback();
void sendHttpResponse(const QByteArray& code, const QByteArray& msg);
void onError(const QByteArray& code, const QString& errorMsg);
@@ -61,15 +67,8 @@ public:
SsoSession::SsoSession(Connection* connection, const QString& initialDeviceName,
const QString& deviceId)
: QObject(connection)
- , d(std::make_unique<Private>(this, initialDeviceName, deviceId, connection))
-{
- qCDebug(MAIN) << "SSO session constructed";
-}
-
-SsoSession::~SsoSession()
-{
- qCDebug(MAIN) << "SSO session deconstructed";
-}
+ , d(makeImpl<Private>(this, initialDeviceName, deviceId, connection))
+{}
QUrl SsoSession::ssoUrl() const { return d->ssoUrl; }
@@ -82,29 +81,29 @@ void SsoSession::Private::processCallback()
// (see at https://github.com/clementine-player/Clementine/)
const auto& requestParts = requestData.split(' ');
if (requestParts.size() < 2 || requestParts[1].isEmpty()) {
- onError("400 Bad Request", tr("No login token in SSO callback"));
+ onError("400 Bad Request", tr("Malformed single sign-on callback"));
return;
}
const auto& QueryItemName = QStringLiteral("loginToken");
QUrlQuery query { QUrl(requestParts[1]).query() };
if (!query.hasQueryItem(QueryItemName)) {
- onError("400 Bad Request", tr("Malformed single sign-on callback"));
+ onError("400 Bad Request", tr("No login token in SSO callback"));
+ return;
}
qCDebug(MAIN) << "Found the token in SSO callback, logging in";
connection->loginWithToken(query.queryItemValue(QueryItemName).toLatin1(),
initialDeviceName, deviceId);
connect(connection, &Connection::connected, socket, [this] {
- const QString msg =
- "The application '" % QCoreApplication::applicationName()
- % "' has successfully logged in as a user " % connection->userId()
- % " with device id " % connection->deviceId()
- % ". This window can be closed. Thank you.\r\n";
+ const auto msg =
+ tr("The application '%1' has successfully logged in as a user %2 "
+ "with device id %3. This window can be closed. Thank you.\r\n")
+ .arg(QCoreApplication::applicationName(), connection->userId(),
+ connection->deviceId());
sendHttpResponse("200 OK", msg.toHtmlEscaped().toUtf8());
socket->disconnectFromHost();
});
connect(connection, &Connection::loginError, socket, [this] {
onError("401 Unauthorised", tr("Login failed"));
- socket->disconnectFromHost();
});
}
@@ -128,4 +127,5 @@ void SsoSession::Private::onError(const QByteArray& code,
// [kitsune] Yeah, I know, dirty. Maybe the "right" way would be to have
// an intermediate signal but that seems just a fight for purity.
emit connection->loginError(errorMsg, requestData);
+ socket->disconnectFromHost();
}
diff --git a/lib/ssosession.h b/lib/ssosession.h
index 72dd60c4..e6a3f8fb 100644
--- a/lib/ssosession.h
+++ b/lib/ssosession.h
@@ -3,14 +3,11 @@
#pragma once
+#include "util.h"
+
#include <QtCore/QUrl>
#include <QtCore/QObject>
-#include <memory>
-
-class QTcpServer;
-class QTcpSocket;
-
namespace Quotient {
class Connection;
@@ -29,19 +26,20 @@ class Connection;
* connection->prepareForSso(initialDeviceName)->ssoUrl());
* \endcode
*/
-class SsoSession : public QObject {
+class QUOTIENT_API SsoSession : public QObject {
Q_OBJECT
Q_PROPERTY(QUrl ssoUrl READ ssoUrl CONSTANT)
Q_PROPERTY(QUrl callbackUrl READ callbackUrl CONSTANT)
public:
SsoSession(Connection* connection, const QString& initialDeviceName,
const QString& deviceId = {});
- ~SsoSession() override;
+ ~SsoSession() override = default;
+
QUrl ssoUrl() const;
QUrl callbackUrl() const;
private:
class Private;
- std::unique_ptr<Private> d;
+ ImplPtr<Private> d;
};
} // namespace Quotient
diff --git a/lib/syncdata.cpp b/lib/syncdata.cpp
index d0533fc9..78957cbe 100644
--- a/lib/syncdata.cpp
+++ b/lib/syncdata.cpp
@@ -219,14 +219,18 @@ void SyncData::parseJson(const QJsonObject& json, const QString& baseDir)
// We have a Qt container on the right and an STL one on the left
roomData.reserve(roomData.size() + static_cast<size_t>(rs.size()));
for (auto roomIt = rs.begin(); roomIt != rs.end(); ++roomIt) {
- auto roomJson =
- roomIt->isObject()
- ? roomIt->toObject()
- : loadJson(baseDir + fileNameForRoom(roomIt.key()));
- if (roomJson.isEmpty()) {
- unresolvedRoomIds.push_back(roomIt.key());
- continue;
- }
+ QJsonObject roomJson;
+ if (!baseDir.isEmpty()) {
+ // Loading data from the local cache, with room objects saved in
+ // individual files rather than inline
+ roomJson = loadJson(baseDir + fileNameForRoom(roomIt.key()));
+ if (roomJson.isEmpty()) {
+ unresolvedRoomIds.push_back(roomIt.key());
+ continue;
+ }
+ } else // When loading from /sync response, everything is inline
+ roomJson = roomIt->toObject();
+
roomData.emplace_back(roomIt.key(), joinState, roomJson);
const auto& r = roomData.back();
totalEvents += r.state.size() + r.ephemeral.size()
diff --git a/lib/syncdata.h b/lib/syncdata.h
index 7fa77eda..633f4b52 100644
--- a/lib/syncdata.h
+++ b/lib/syncdata.h
@@ -22,7 +22,7 @@ constexpr auto HighlightCountKey = "highlight_count"_ls;
* means that nothing has come from the server; heroes.value().isEmpty()
* means a peculiar case of a room with the only member - the current user.
*/
-struct RoomSummary {
+struct QUOTIENT_API RoomSummary {
Omittable<int> joinedMemberCount;
Omittable<int> invitedMemberCount;
Omittable<QStringList> heroes; //< mxids of users to take part in the room
diff --git a/lib/uri.cpp b/lib/uri.cpp
index c8843dda..6b7d1d20 100644
--- a/lib/uri.cpp
+++ b/lib/uri.cpp
@@ -3,29 +3,34 @@
#include "uri.h"
+#include "util.h"
#include "logging.h"
#include <QtCore/QRegularExpression>
using namespace Quotient;
-struct ReplacePair { QByteArray uriString; char sigil; };
+namespace {
+
+struct ReplacePair { QLatin1String uriString; char sigil; };
/// \brief Defines bi-directional mapping of path prefixes and sigils
///
/// When there are two prefixes for the same sigil, the first matching
/// entry for a given sigil is used.
-static const auto replacePairs = {
- ReplacePair { "u/", '@' },
- { "user/", '@' },
- { "roomid/", '!' },
- { "r/", '#' },
- { "room/", '#' },
+const ReplacePair replacePairs[] = {
+ { "u/"_ls, '@' },
+ { "user/"_ls, '@' },
+ { "roomid/"_ls, '!' },
+ { "r/"_ls, '#' },
+ { "room/"_ls, '#' },
// The notation for bare event ids is not proposed in MSC2312 but there's
// https://github.com/matrix-org/matrix-doc/pull/2644
- { "e/", '$' },
- { "event/", '$' }
+ { "e/"_ls, '$' },
+ { "event/"_ls, '$' }
};
+}
+
Uri::Uri(QByteArray primaryId, QByteArray secondaryId, QString query)
{
if (primaryId.isEmpty())
@@ -75,7 +80,7 @@ static auto decodeFragmentPart(QStringView part)
return QUrl::fromPercentEncoding(part.toLatin1()).toUtf8();
}
-static auto matrixToUrlRegexInit()
+static inline auto matrixToUrlRegexInit()
{
// See https://matrix.org/docs/spec/appendices#matrix-to-navigation
const QRegularExpression MatrixToUrlRE {
diff --git a/lib/uri.h b/lib/uri.h
index d8b892b6..78cd27c8 100644
--- a/lib/uri.h
+++ b/lib/uri.h
@@ -23,7 +23,7 @@ namespace Quotient {
* its type, and obtain components, also in either unencoded (for displaying)
* or encoded (for APIs) form.
*/
-class Uri : private QUrl {
+class QUOTIENT_API Uri : private QUrl {
Q_GADGET
public:
enum Type : char {
diff --git a/lib/uriresolver.h b/lib/uriresolver.h
index ff97324a..9140046c 100644
--- a/lib/uriresolver.h
+++ b/lib/uriresolver.h
@@ -25,7 +25,7 @@ class User;
* gradual implementation. Derived classes are encouraged to override as many
* of them as possible.
*/
-class UriResolverBase {
+class QUOTIENT_API UriResolverBase {
public:
/*! \brief Resolve the resource and dispatch an action depending on its type
*
@@ -105,7 +105,7 @@ protected:
*
* \sa UriResolverBase, UriDispatcher
*/
-UriResolveResult
+QUOTIENT_API UriResolveResult
visitResource(Connection* account, const Uri& uri,
std::function<UriResolveResult(User*, QString)> userHandler,
std::function<void(Room*, QString)> roomEventHandler,
@@ -141,7 +141,7 @@ inline UriResolveResult checkResource(Connection* account, const Uri& uri)
* synchronously - the returned value is the result of resolving the URI,
* not acting on it.
*/
-class UriDispatcher : public QObject, public UriResolverBase {
+class QUOTIENT_API UriDispatcher : public QObject, public UriResolverBase {
Q_OBJECT
public:
explicit UriDispatcher(QObject* parent = nullptr) : QObject(parent) {}
diff --git a/lib/user.cpp b/lib/user.cpp
index 7da71dba..4c3fc9e2 100644
--- a/lib/user.cpp
+++ b/lib/user.cpp
@@ -46,7 +46,7 @@ public:
decltype(User::Private::otherAvatars) User::Private::otherAvatars {};
User::User(QString userId, Connection* connection)
- : QObject(connection), d(new Private(move(userId)))
+ : QObject(connection), d(makeImpl<Private>(move(userId)))
{
setObjectName(id());
if (connection->userId() == id()) {
@@ -61,8 +61,6 @@ Connection* User::connection() const
return static_cast<Connection*>(parent());
}
-User::~User() = default;
-
void User::load()
{
auto* profileJob =
@@ -112,7 +110,7 @@ void User::rename(const QString& newName)
});
}
-void User::rename(const QString& newName, const Room* r)
+void User::rename(const QString& newName, Room* r)
{
if (!r) {
qCWarning(MAIN) << "Passing a null room to two-argument User::rename()"
@@ -121,12 +119,17 @@ void User::rename(const QString& newName, const Room* r)
return;
}
// #481: take the current state and update it with the new name
- auto evtC = r->getCurrentState<RoomMemberEvent>(id())->content();
- Q_ASSERT_X(evtC.membership == Membership::Join, __FUNCTION__,
- "Attempt to rename a user that's not a room member");
- evtC.displayName = sanitized(newName);
- r->setState<RoomMemberEvent>(id(), move(evtC));
- // The state will be updated locally after it arrives with sync
+ if (const auto& maybeEvt = r->currentState().get<RoomMemberEvent>(id())) {
+ auto content = maybeEvt->content();
+ if (content.membership == Membership::Join) {
+ content.displayName = sanitized(newName);
+ r->setState<RoomMemberEvent>(id(), move(content));
+ // The state will be updated locally after it arrives with sync
+ return;
+ }
+ }
+ qCCritical(MEMBERS)
+ << "Attempt to rename a non-member in a room context - ignored";
}
template <typename SourceT>
diff --git a/lib/user.h b/lib/user.h
index 78b72bf2..dfbff4a0 100644
--- a/lib/user.h
+++ b/lib/user.h
@@ -5,6 +5,7 @@
#pragma once
#include "avatar.h"
+#include "util.h"
#include <QtCore/QObject>
@@ -13,7 +14,7 @@ class Connection;
class Room;
class RoomMemberEvent;
-class User : public QObject {
+class QUOTIENT_API User : public QObject {
Q_OBJECT
Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(bool isGuest READ isGuest CONSTANT)
@@ -26,7 +27,6 @@ class User : public QObject {
Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY defaultAvatarChanged)
public:
User(QString userId, Connection* connection);
- ~User() override;
Connection* connection() const;
@@ -96,7 +96,7 @@ public Q_SLOTS:
/// Set a new name in the global user profile
void rename(const QString& newName);
/// Set a new name for the user in one room
- void rename(const QString& newName, const Room* r);
+ void rename(const QString& newName, Room* r);
/// Upload the file and use it as an avatar
bool setAvatar(const QString& fileName);
/// Upload contents of the QIODevice and set that as an avatar
@@ -125,7 +125,7 @@ Q_SIGNALS:
private:
class Private;
- QScopedPointer<Private> d;
+ ImplPtr<Private> d;
template <typename SourceT>
bool doSetAvatar(SourceT&& source);
diff --git a/lib/util.cpp b/lib/util.cpp
index 2dfb09a6..03ebf325 100644
--- a/lib/util.cpp
+++ b/lib/util.cpp
@@ -135,35 +135,3 @@ int Quotient::patchVersion()
{
return Quotient_VERSION_PATCH;
}
-
-// Tests for function_traits<>
-
-using namespace Quotient;
-
-int f_();
-static_assert(std::is_same<fn_return_t<decltype(f_)>, int>::value,
- "Test fn_return_t<>");
-
-void f1_(int, QString);
-static_assert(std::is_same<fn_arg_t<decltype(f1_), 1>, QString>::value,
- "Test fn_arg_t<>");
-
-struct Fo {
- int operator()();
- static constexpr auto l = [] { return 0.0f; };
-};
-static_assert(std::is_same<fn_return_t<Fo>, int>::value,
- "Test return type of function object");
-static_assert(std::is_same<fn_return_t<decltype(Fo::l)>, float>::value,
- "Test return type of lambda");
-
-struct Fo1 {
- void operator()(int);
-};
-static_assert(std::is_same<fn_arg_t<Fo1>, int>(),
- "Test fn_arg_t defaulting to first argument");
-
-template <typename T>
-static QString ft(T&&);
-static_assert(std::is_same<fn_arg_t<decltype(ft<QString>)>, QString&&>(),
- "Test function templates");
diff --git a/lib/util.h b/lib/util.h
index 13efb94b..753eb1ea 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -4,13 +4,13 @@
#pragma once
+#include "quotient_export.h"
+
#include <QtCore/QLatin1String>
#include <QtCore/QHashFunctions>
-#include <functional>
#include <memory>
#include <unordered_map>
-#include <optional>
#ifndef Q_DISABLE_MOVE
// Q_DISABLE_MOVE was introduced in Q_VERSION_CHECK(5,13,0)
@@ -50,198 +50,7 @@ struct HashQ {
template <typename KeyT, typename ValT>
using UnorderedMap = std::unordered_map<KeyT, ValT, HashQ<KeyT>>;
-namespace _impl {
- template <typename TT>
- constexpr inline auto IsOmittableValue = false;
- template <typename TT>
- constexpr inline auto IsOmittable = IsOmittableValue<std::decay_t<TT>>;
-}
-
-constexpr auto none = std::nullopt;
-
-/** `std::optional` with tweaks
- *
- * The tweaks are:
- * - streamlined assignment (operator=)/emplace()ment of values that can be
- * used to implicitly construct the underlying type, including
- * direct-list-initialisation, e.g.:
- * \code
- * struct S { int a; char b; }
- * Omittable<S> o;
- * o = { 1, 'a' }; // std::optional would require o = S { 1, 'a' }
- * \endcode
- * - entirely deleted value(). The technical reason is that Xcode 10 doesn't
- * have it; but besides that, value_or() or (after explicit checking)
- * `operator*()`/`operator->()` are better alternatives within Quotient
- * that doesn't practice throwing exceptions (as doesn't most of Qt).
- * - disabled non-const lvalue operator*() and operator->(), as it's too easy
- * to inadvertently cause a value change through them.
- * - edit() to provide a safe and explicit lvalue accessor instead of those
- * above. Requires the underlying type to be default-constructible.
- * Allows chained initialisation of nested Omittables:
- * \code
- * struct Inner { int member = 10; Omittable<int> innermost; };
- * struct Outer { int anotherMember = 10; Omittable<Inner> inner; };
- * Omittable<Outer> o; // = { 10, std::nullopt };
- * o.edit().inner.edit().innermost.emplace(42);
- * \endcode
- * - merge() - a soft version of operator= that only overwrites its first
- * operand with the second one if the second one is not empty.
- */
-template <typename T>
-class Omittable : public std::optional<T> {
-public:
- using base_type = std::optional<T>;
- using value_type = std::decay_t<T>;
-
- using std::optional<T>::optional;
-
- // Overload emplace() and operator=() to allow passing braced-init-lists
- // (the standard emplace() does direct-initialisation but
- // not direct-list-initialisation).
- using base_type::operator=;
- Omittable& operator=(const value_type& v)
- {
- base_type::operator=(v);
- return *this;
- }
- Omittable& operator=(value_type&& v)
- {
- base_type::operator=(v);
- return *this;
- }
- using base_type::emplace;
- T& emplace(const T& val) { return base_type::emplace(val); }
- T& emplace(T&& val) { return base_type::emplace(std::move(val)); }
-
- // use value_or() or check (with operator! or has_value) before accessing
- // with operator-> or operator*
- // The technical reason is that Xcode 10 has incomplete std::optional
- // that has no value(); but using value() may also mean that you rely
- // on the optional throwing an exception (which is not assumed practice
- // throughout Quotient) or that you spend unnecessary CPU cycles on
- // an extraneous has_value() check.
- value_type& value() = delete;
- const value_type& value() const = delete;
- value_type& edit()
- {
- return this->has_value() ? base_type::operator*() : this->emplace();
- }
-
- [[deprecated("Use '!o' or '!o.has_value()' instead of 'o.omitted()'")]]
- bool omitted() const
- {
- return !this->has_value();
- }
-
- //! Merge the value from another Omittable
- //! \return true if \p other is not omitted and the value of
- //! the current Omittable was different (or omitted),
- //! in other words, if the current Omittable has changed;
- //! false otherwise
- template <typename T1>
- auto merge(const Omittable<T1>& other)
- -> std::enable_if_t<std::is_convertible_v<T1, T>, bool>
- {
- if (!other || (this->has_value() && **this == *other))
- return false;
- emplace(*other);
- return true;
- }
-
- // Hide non-const lvalue operator-> and operator* as these are
- // a bit too surprising: value() & doesn't lazy-create an object;
- // and it's too easy to inadvertently change the underlying value.
-
- const value_type* operator->() const& { return base_type::operator->(); }
- value_type* operator->() && { return base_type::operator->(); }
- const value_type& operator*() const& { return base_type::operator*(); }
- value_type& operator*() && { return base_type::operator*(); }
-};
-template <typename T>
-Omittable(T&&) -> Omittable<T>;
-
-namespace _impl {
- template <typename T>
- constexpr inline auto IsOmittableValue<Omittable<T>> = true;
-}
-
-template <typename T1, typename T2>
-inline auto merge(Omittable<T1>& lhs, T2&& rhs)
-{
- return lhs.merge(std::forward<T2>(rhs));
-}
-
-//! \brief Merge the value from an Omittable
-//! This is an adaptation of Omittable::merge() to the case when the value
-//! on the left hand side is not an Omittable.
-//! \return true if \p rhs is not omitted and the \p lhs value was different,
-//! in other words, if \p lhs has changed;
-//! false otherwise
-template <typename T1, typename T2>
-inline auto merge(T1& lhs, const Omittable<T2>& rhs)
- -> std::enable_if_t<!_impl::IsOmittable<T1>
- && std::is_convertible_v<T2, T1>, bool>
-{
- if (!rhs || lhs == *rhs)
- return false;
- lhs = *rhs;
- return true;
-}
-
-namespace _impl {
- template <typename AlwaysVoid, typename>
- struct fn_traits {};
-}
-
-/// Determine traits of an arbitrary function/lambda/functor
-/*!
- * Doesn't work with generic lambdas and function objects that have
- * operator() overloaded.
- * \sa
- * https://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda#7943765
- */
-template <typename T>
-struct function_traits
- : public _impl::fn_traits<void, std::remove_reference_t<T>> {};
-
-// Specialisation for a function
-template <typename ReturnT, typename... ArgTs>
-struct function_traits<ReturnT(ArgTs...)> {
- using return_type = ReturnT;
- using arg_types = std::tuple<ArgTs...>;
- // Doesn't (and there's no plan to make it) work for "classic"
- // member functions (i.e. outside of functors).
- // See also the comment for wrap_in_function() below
- using function_type = std::function<ReturnT(ArgTs...)>;
-};
-
-namespace _impl {
- // Specialisation for function objects with (non-overloaded) operator()
- // (this includes non-generic lambdas)
- template <typename T>
- struct fn_traits<decltype(void(&T::operator())), T>
- : public fn_traits<void, decltype(&T::operator())> {};
-
- // Specialisation for a member function
- template <typename ReturnT, typename ClassT, typename... ArgTs>
- struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...)>
- : function_traits<ReturnT(ArgTs...)> {};
-
- // Specialisation for a const member function
- template <typename ReturnT, typename ClassT, typename... ArgTs>
- struct fn_traits<void, ReturnT (ClassT::*)(ArgTs...) const>
- : function_traits<ReturnT(ArgTs...)> {};
-} // namespace _impl
-
-template <typename FnT>
-using fn_return_t = typename function_traits<FnT>::return_type;
-
-template <typename FnT, int ArgN = 0>
-using fn_arg_t =
- std::tuple_element_t<ArgN, typename function_traits<FnT>::arg_types>;
-
-inline constexpr auto operator"" _ls(const char* s, std::size_t size)
+constexpr auto operator"" _ls(const char* s, std::size_t size)
{
return QLatin1String(s, int(size));
}
@@ -295,27 +104,68 @@ inline std::pair<InputIt, ForwardIt> findFirstOf(InputIt first, InputIt last,
return std::make_pair(last, sLast);
}
+//! \brief An owning implementation pointer
+//!
+//! This is basically std::unique_ptr<> to hold your pimpl's but without having
+//! to define default constructors/operator=() out of line.
+//! Thanks to https://oliora.github.io/2015/12/29/pimpl-and-rule-of-zero.html
+//! for inspiration
+template <typename ImplType>
+using ImplPtr = std::unique_ptr<ImplType, void (*)(ImplType*)>;
+
+// Why this works (see also the link above): because this defers the moment
+// of requiring sizeof of ImplType to the place where makeImpl is invoked
+// (which is located, necessarily, in the .cpp file after ImplType definition).
+// The stock unique_ptr deleter (std::default_delete) normally needs sizeof
+// at the same spot - as long as you defer definition of the owning type
+// constructors and operator='s to the .cpp file as well. Which means you
+// have to explicitly declare and define them (even if with = default),
+// formally breaking the rule of zero; informally, just adding boilerplate code.
+// The custom deleter itself is instantiated at makeImpl invocation - there's
+// no way earlier to even know how ImplType will be deleted and whether that
+// will need sizeof(ImplType) earlier. In theory it's a tad slower because
+// the deleter is called by the pointer; however, the difference will not
+// be noticeable (if exist at all) for any class with non-trivial contents.
+
+//! \brief make_unique for ImplPtr
+//!
+//! Since std::make_unique is not compatible with ImplPtr, this should be used
+//! in constructors of frontend classes to create implementation instances.
+template <typename ImplType, typename DeleterType = void (*)(ImplType*),
+ typename... ArgTs>
+inline ImplPtr<ImplType> makeImpl(ArgTs&&... args)
+{
+ return ImplPtr<ImplType> { new ImplType(std::forward<ArgTs>(args)...),
+ [](ImplType* impl) { delete impl; } };
+}
+
+template <typename ImplType>
+const inline ImplPtr<ImplType> ZeroImpl()
+{
+ return { nullptr, [](ImplType*) { /* nullptr doesn't need deletion */ } };
+}
+
/** Convert what looks like a URL or a Matrix ID to an HTML hyperlink */
-void linkifyUrls(QString& htmlEscapedText);
+QUOTIENT_API void linkifyUrls(QString& htmlEscapedText);
/** Sanitize the text before showing in HTML
*
* This does toHtmlEscaped() and removes Unicode BiDi marks.
*/
-QString sanitized(const QString& plainText);
+QUOTIENT_API QString sanitized(const QString& plainText);
/** Pretty-print plain text into HTML
*
* This includes HTML escaping of <,>,",& and calling linkifyUrls()
*/
-QString prettyPrint(const QString& plainText);
+QUOTIENT_API QString prettyPrint(const QString& plainText);
/** Return a path to cache directory after making sure that it exists
*
* The returned path has a trailing slash, clients don't need to append it.
* \param dir path to cache directory relative to the standard cache path
*/
-QString cacheLocation(const QString& dirName);
+QUOTIENT_API QString cacheLocation(const QString& dirName);
/** Hue color component of based of the hash of the string.
*
@@ -324,13 +174,13 @@ QString cacheLocation(const QString& dirName);
* Naming and range are the same as QColor's hueF method:
* https://doc.qt.io/qt-5/qcolor.html#integer-vs-floating-point-precision
*/
-qreal stringToHueF(const QString& s);
+QUOTIENT_API qreal stringToHueF(const QString& s);
/** Extract the serverpart from MXID */
-QString serverPart(const QString& mxId);
+QUOTIENT_API QString serverPart(const QString& mxId);
-QString versionString();
-int majorVersion();
-int minorVersion();
-int patchVersion();
+QUOTIENT_API QString versionString();
+QUOTIENT_API int majorVersion();
+QUOTIENT_API int minorVersion();
+QUOTIENT_API int patchVersion();
} // namespace Quotient
diff --git a/quotest/CMakeLists.txt b/quotest/CMakeLists.txt
index 59334e30..ec305620 100644
--- a/quotest/CMakeLists.txt
+++ b/quotest/CMakeLists.txt
@@ -8,7 +8,27 @@ find_package(${Qt} COMPONENTS Concurrent)
add_executable(quotest ${quotest_SRCS})
target_link_libraries(quotest PRIVATE ${Qt}::Core ${Qt}::Test ${Qt}::Concurrent ${PROJECT_NAME})
-option(${PROJECT_NAME}_INSTALL_TESTS "install quotest (former qmc-example) application" ON)
+set_target_properties(quotest PROPERTIES
+ VISIBILITY_INLINES_HIDDEN ON
+ CXX_VISIBILITY_PRESET hidden
+)
+
+if (MSVC)
+ target_compile_options(quotest PUBLIC /EHsc /W4
+ /wd4100 /wd4127 /wd4242 /wd4244 /wd4245 /wd4267 /wd4365 /wd4456 /wd4459
+ /wd4464 /wd4505 /wd4514 /wd4571 /wd4619 /wd4623 /wd4625 /wd4626 /wd4706
+ /wd4710 /wd4774 /wd4820 /wd4946 /wd5026 /wd5027)
+else()
+ foreach (FLAG W Wall Wpedantic Wextra Wno-unused-parameter Werror=return-type)
+ CHECK_CXX_COMPILER_FLAG("-${FLAG}" COMPILER_${FLAG}_SUPPORTED)
+ if (COMPILER_${FLAG}_SUPPORTED AND
+ NOT CMAKE_CXX_FLAGS MATCHES "(^| )-?${FLAG}($| )")
+ target_compile_options(quotest PUBLIC -${FLAG})
+ endif ()
+ endforeach ()
+endif()
+
+option(${PROJECT_NAME}_INSTALL_TESTS "install quotest application" ON)
add_feature_info(InstallQuotest ${PROJECT_NAME}_INSTALL_TESTS
"the library functional test suite")
diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp
index d006c7fb..792faabd 100644
--- a/quotest/quotest.cpp
+++ b/quotest/quotest.cpp
@@ -14,6 +14,7 @@
#include "events/reactionevent.h"
#include "events/redactionevent.h"
#include "events/simplestateevents.h"
+#include "events/roommemberevent.h"
#include <QtTest/QSignalSpy>
#include <QtCore/QCoreApplication>
@@ -101,6 +102,7 @@ private slots:
TEST_DECL(sendMessage)
TEST_DECL(sendReaction)
TEST_DECL(sendFile)
+ TEST_DECL(sendCustomEvent)
TEST_DECL(setTopic)
TEST_DECL(changeName)
TEST_DECL(sendAndRedact)
@@ -125,6 +127,7 @@ private:
[[nodiscard]] bool checkRedactionOutcome(const QByteArray& thisTest,
const QString& evtIdToRedact);
+ template <class EventT>
[[nodiscard]] bool validatePendingEvent(const QString& txnId);
[[nodiscard]] bool checkDirectChat() const;
void finishTest(const TestToken& token, bool condition, const char* file,
@@ -152,12 +155,14 @@ void TestSuite::doTest(const QByteArray& testName)
Q_ARG(TestToken, testName));
}
+template <class EventT>
bool TestSuite::validatePendingEvent(const QString& txnId)
{
auto it = targetRoom->findPendingEvent(txnId);
return it != targetRoom->pendingEvents().end()
&& it->deliveryStatus() == EventStatus::Submitted
- && (*it)->transactionId() == txnId;
+ && (*it)->transactionId() == txnId && is<EventT>(**it)
+ && (*it)->matrixType() == EventT::matrixTypeId();
}
void TestSuite::finishTest(const TestToken& token, bool condition,
@@ -214,6 +219,7 @@ TestManager::TestManager(int& argc, char** argv)
// Big countdown watchdog
QTimer::singleShot(180000, this, [this] {
+ clog << "Time is up, stopping the session\n";
if (testSuite)
conclude();
else
@@ -340,7 +346,7 @@ TEST_IMPL(loadMembers)
TEST_IMPL(sendMessage)
{
auto txnId = targetRoom->postPlainText("Hello, " % origin % " is here");
- if (!validatePendingEvent(txnId)) {
+ if (!validatePendingEvent<RoomMessageEvent>(txnId)) {
clog << "Invalid pending event right after submitting" << endl;
FAIL_TEST();
}
@@ -366,7 +372,7 @@ TEST_IMPL(sendReaction)
const auto targetEvtId = targetRoom->messageEvents().back()->id();
const auto key = QStringLiteral("+1");
const auto txnId = targetRoom->postReaction(targetEvtId, key);
- if (!validatePendingEvent(txnId)) {
+ if (!validatePendingEvent<ReactionEvent>(txnId)) {
clog << "Invalid pending event right after submitting" << endl;
FAIL_TEST();
}
@@ -376,7 +382,7 @@ TEST_IMPL(sendReaction)
if (actualTargetEvtId != targetEvtId)
return false;
const auto reactions = targetRoom->relatedEvents(
- targetEvtId, EventRelation::Annotation());
+ targetEvtId, EventRelation::AnnotationType);
// It's a test room, assuming no interference there should
// be exactly one reaction
if (reactions.size() != 1)
@@ -408,7 +414,7 @@ TEST_IMPL(sendFile)
clog << "Sending file " << tfName.toStdString() << endl;
const auto txnId = targetRoom->postFile(
"Test file", new EventContent::FileContent(tfi));
- if (!validatePendingEvent(txnId)) {
+ if (!validatePendingEvent<RoomMessageEvent>(txnId)) {
clog << "Invalid pending event right after submitting" << endl;
tf->deleteLater();
FAIL_TEST();
@@ -498,8 +504,8 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest,
clog << "File event " << txnId.toStdString()
<< " arrived in the timeline" << endl;
- // This part tests visit()
- return visit(
+ // This part tests switchOnType()
+ return switchOnType(
*evt,
[&](const RoomMessageEvent& e) {
// TODO: check #366 once #368 is implemented
@@ -517,6 +523,50 @@ bool TestSuite::checkFileSendingOutcome(const TestToken& thisTest,
return true;
}
+class CustomEvent : public RoomEvent {
+public:
+ DEFINE_EVENT_TYPEID("quotest.custom", CustomEvent)
+
+ CustomEvent(const QJsonObject& jo)
+ : RoomEvent(typeId(), jo)
+ {}
+ CustomEvent(int testValue)
+ : RoomEvent(typeId(),
+ basicEventJson(matrixTypeId(),
+ QJsonObject { { "testValue"_ls,
+ toJson(testValue) } }))
+ {}
+
+ auto testValue() const { return contentPart<int>("testValue"_ls); }
+};
+REGISTER_EVENT_TYPE(CustomEvent)
+
+TEST_IMPL(sendCustomEvent)
+{
+ auto txnId = targetRoom->postEvent(new CustomEvent(42));
+ if (!validatePendingEvent<CustomEvent>(txnId)) {
+ clog << "Invalid pending event right after submitting" << endl;
+ FAIL_TEST();
+ }
+ connectUntil(
+ targetRoom, &Room::pendingEventAboutToMerge, this,
+ [this, thisTest, txnId](const RoomEvent* evt, int pendingIdx) {
+ const auto& pendingEvents = targetRoom->pendingEvents();
+ Q_ASSERT(pendingIdx >= 0 && pendingIdx < int(pendingEvents.size()));
+
+ if (evt->transactionId() != txnId)
+ return false;
+
+ return switchOnType(*evt,
+ [this, thisTest, &evt](const CustomEvent& e) {
+ FINISH_TEST(!evt->id().isEmpty() && e.testValue() == 42);
+ },
+ [this, thisTest] (const RoomEvent&) { FAIL_TEST(); });
+ });
+ return false;
+
+}
+
TEST_IMPL(setTopic)
{
const auto newTopic = connection()->generateTxnId(); // Just a way to make
@@ -537,18 +587,41 @@ TEST_IMPL(setTopic)
TEST_IMPL(changeName)
{
- auto* const localUser = connection()->user();
- const auto& newName = connection()->generateTxnId(); // See setTopic()
- clog << "Renaming the user to " << newName.toStdString() << endl;
- localUser->rename(newName);
- connectUntil(localUser, &User::defaultNameChanged, this,
- [this, thisTest, localUser, newName] {
- FINISH_TEST(localUser->name() == newName);
- });
+ connectSingleShot(targetRoom, &Room::allMembersLoaded, this, [this, thisTest] {
+ auto* const localUser = connection()->user();
+ const auto& newName = connection()->generateTxnId(); // See setTopic()
+ clog << "Renaming the user to " << newName.toStdString()
+ << " in the target room" << endl;
+ localUser->rename(newName, targetRoom);
+ connectUntil(
+ targetRoom, &Room::aboutToAddNewMessages, this,
+ [this, thisTest, localUser, newName](const RoomEventsRange& evts) {
+ for (const auto& e : evts) {
+ if (const auto* rme = eventCast<const RoomMemberEvent>(e)) {
+ if (rme->stateKey() != localUser->id()
+ || !rme->isRename())
+ continue;
+ if (!rme->newDisplayName()
+ || *rme->newDisplayName() != newName)
+ FAIL_TEST();
+ clog << "Member rename successful, renaming the account"
+ << endl;
+ const auto newN = newName.mid(0, 5);
+ localUser->rename(newN);
+ connectUntil(localUser, &User::defaultNameChanged, this,
+ [this, thisTest, localUser, newN] {
+ targetRoom->localUser()->rename({});
+ FINISH_TEST(localUser->name() == newN);
+ });
+ return true;
+ }
+ }
+ return false;
+ });
+ });
return false;
}
-
TEST_IMPL(showLocalUsername)
{
auto* const localUser = connection()->user();