Compare commits
127 commits
Author | SHA1 | Date | |
---|---|---|---|
c147e02187 | |||
066ab487fc | |||
a8060b393c | |||
321f0b03c8 | |||
d4cec645b5 | |||
a04693e39d | |||
3cce057545 | |||
9a44ae1fa5 | |||
85ff6c25ba | |||
3cc7db8eff | |||
|
ff9a591d6d | ||
8e3f10caff | |||
8bfe88929f | |||
9927bdc38b | |||
|
8083859541 | ||
|
030c374139 | ||
|
2c61b82924 | ||
fb843a1346 | |||
8d82d340a4 | |||
acd60eaba2 | |||
829777935f | |||
0be2648849 | |||
93c5be412e | |||
8f5325b291 | |||
75554c7451 | |||
00af582287 | |||
19835af3cf | |||
e31ef78e71 | |||
be466fbad1 | |||
0a530bfa93 | |||
637eb702a8 | |||
a7d1a28f29 | |||
297e08ba41 | |||
9d688e8596 | |||
23ec80ccba | |||
5fbb03fc46 | |||
81cf0f8d34 | |||
69d797fe51 | |||
4f295fee3c | |||
fffef9876a | |||
283e9ebc4d | |||
21b40a9ccb | |||
76a9c5da0c | |||
8ec0af3205 | |||
4b68da458f | |||
927bdf0dab | |||
5ba97ecc25 | |||
9fff409630 | |||
99fd001292 | |||
2d8f32c257 | |||
77dd28b600 | |||
6f32e99593 | |||
ec362cef55 | |||
e4a2728ef8 | |||
bf11d8a74e | |||
edf1ee60cd | |||
4af16b75bf | |||
bb304ce774 | |||
3c6b611a41 | |||
73d83f55af | |||
b72a837754 | |||
d4bf7e599a | |||
15fb4bbd62 | |||
2aed8a1209 | |||
78ef3664f7 | |||
5aa0f4bca9 | |||
b45a73b723 | |||
758a9d95f3 | |||
dfe72ca36c | |||
db3bc358a7 | |||
0b61b6e928 | |||
820dc845ea | |||
87973b3b67 | |||
b6ba022bff | |||
7b2b7ee5d5 | |||
c50cd1140e | |||
037dabbe06 | |||
2ae75a4b91 | |||
d162494ec8 | |||
7e9eed2075 | |||
7192286aeb | |||
645b92ba51 | |||
80c5e2f2b4 | |||
1f065f23e6 | |||
3c48577eee | |||
0340db7f2f | |||
c3a45ec58c | |||
7ba94e9deb | |||
eac87e713f | |||
d86e2c28a0 | |||
2fcc432aef | |||
e58213b294 | |||
3916aec358 | |||
721d3a1a89 | |||
83cb220175 | |||
18859cb960 | |||
4c20a314f0 | |||
51ac1ac709 | |||
8f949277f6 | |||
ce686e121b | |||
f64e5c2df0 | |||
2c26c7e264 | |||
69e0c88d8d | |||
82d54ba4df | |||
1b66fda318 | |||
9f746d203b | |||
27377e0ec5 | |||
4baa3bccbf | |||
4786388822 | |||
62f02c18d7 | |||
1fcd403dba | |||
5f6691067a | |||
788c6ca556 | |||
bf4a27f35d | |||
0823b35148 | |||
73b1b58a96 | |||
d8b5ccb2da | |||
243edff8bd | |||
da19eb86bb | |||
0ff9f12157 | |||
802e2f11a1 | |||
c708c33a92 | |||
a8a7ce2538 | |||
841e526e59 | |||
6bee149e6b | |||
62a59eb7a1 | |||
296328f12d |
263 changed files with 23785 additions and 9902 deletions
45
.forgejo/workflows/release.yml
Normal file
45
.forgejo/workflows/release.yml
Normal file
|
@ -0,0 +1,45 @@
|
|||
name: Squawk Release workflow
|
||||
run-name: ${{ gitea.actor }} is running Squawk Release workflow on release ${{ gitea.event.release.tag_name }}
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
Archlinux:
|
||||
runs-on: archlinux
|
||||
steps:
|
||||
- name: Download the release tarball
|
||||
run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz
|
||||
|
||||
- name: Calculate SHA256 for the tarball
|
||||
run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV
|
||||
|
||||
- name: Unarchive tarball
|
||||
run: tar -xvzf tarball.tar.gz
|
||||
|
||||
- name: Clone the AUR repository
|
||||
run: |
|
||||
echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key
|
||||
chmod 600 key
|
||||
GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/squawk.git aur
|
||||
chmod 777 -R aur
|
||||
cd aur
|
||||
git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
|
||||
git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }}
|
||||
|
||||
|
||||
- name: Copy PKGBUILD to the directory
|
||||
run: cp squawk/packaging/Archlinux/PKGBUILD aur/
|
||||
|
||||
- name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO
|
||||
working-directory: aur
|
||||
run: |
|
||||
sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD
|
||||
sudo -u build makepkg --printsrcinfo > .SRCINFO
|
||||
|
||||
- name: Commit package to aur
|
||||
working-directory: aur
|
||||
run: |
|
||||
git add PKGBUILD .SRCINFO
|
||||
git commit -m "${{ gitea.event.release.body//\"/\\\" }}"
|
||||
GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push
|
5
.gitmodules
vendored
5
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
|||
[submodule "external/qxmpp"]
|
||||
path = external/qxmpp
|
||||
url = https://github.com/qxmpp-project/qxmpp.git
|
||||
url = https://invent.kde.org/libraries/qxmpp/
|
||||
[submodule "external/lmdbal"]
|
||||
path = external/lmdbal
|
||||
url = https://git.macaw.me/blue/lmdbal
|
||||
|
|
3640
.uncrustify.cfg
Normal file
3640
.uncrustify.cfg
Normal file
File diff suppressed because it is too large
Load diff
70
CHANGELOG.md
70
CHANGELOG.md
|
@ -1,5 +1,75 @@
|
|||
# Changelog
|
||||
|
||||
## Squawk 0.2.4 (UNRELEASED)
|
||||
### Bug fixes
|
||||
- messages to the mucs get sent once again
|
||||
|
||||
### Improvements
|
||||
- it's possible to build against Qt 6 now, Qt6 is the default
|
||||
- got rid of deprecated SimpleCrypt library
|
||||
- look up for proper stanzaID
|
||||
|
||||
## Squawk 0.2.3 (February 04, 2024)
|
||||
### Bug fixes
|
||||
- "Add contact" and "Join conference" menu are enabled once again (pavavno)!
|
||||
- availability is now read from the same section of config file it was stored
|
||||
- automatic avatars (if a contact doesn't have one) get generated once again
|
||||
|
||||
### Improvements
|
||||
- deactivated accounts now don't appear in combobox of "Add contact" and "Join conference" dialogues
|
||||
- all of the expandable roster items now get saved between launches
|
||||
- settings file on the disk is not rewritten every roster element expansion or collapse
|
||||
- removed unnecessary own vcard request at sturtup (used to do it to discover my own avatar)
|
||||
- vcard window now is Info system and it can display more information
|
||||
- reduced vcard request spam in MUCs
|
||||
|
||||
### New features
|
||||
- now you can enable tray icon from settings!
|
||||
- there is a job queue now, this allowes to spread a bit the spam on the server at connection time
|
||||
- squawk now queries clients of it's peers, you can see what programs other people use
|
||||
|
||||
## Squawk 0.2.2 (May 05, 2022)
|
||||
### Bug fixes
|
||||
- now when you remove an account it actually gets removed
|
||||
- segfault on uninitialized Availability in some rare occasions
|
||||
- fixed crash when you open a dialog with someone that has only error messages in archive
|
||||
- message height is now calculated correctly on Chinese and Japanese paragraphs
|
||||
- the app doesn't crash on SIGINT anymore
|
||||
|
||||
### Improvements
|
||||
- there is a way to disable an account and it wouldn't connect when you change availability
|
||||
- if you cancel password query an account becomes inactive and doesn't annoy you anymore
|
||||
- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
|
||||
- if left the password field empty and chose KWallet as a storage Squawk will try to get that password from KWallet before asking you to input it
|
||||
- accounts now connect to the server asynchronously - if one is stopped on password prompt another is connecting
|
||||
- actualized translations, added English localization file
|
||||
|
||||
### New features
|
||||
- new "About" window with links, license, gratitudes
|
||||
- if the authentication failed Squawk will ask again for your password and login
|
||||
- now there is an amount of unread messages showing on top of Squawk launcher icon
|
||||
- notifications now have buttons to open a conversation or to mark that message as read
|
||||
|
||||
## Squawk 0.2.1 (Apr 02, 2022)
|
||||
### Bug fixes
|
||||
- build in release mode now no longer spams warnings
|
||||
- build now correctly installs all build plugin libs
|
||||
- a bug where the correction message was received, the indication was on but the text didn't actually change
|
||||
- message body now doesn't intecept context menu from the whole message
|
||||
- message input now doesn't increase font when you remove everything from it
|
||||
|
||||
### Improvements
|
||||
- reduced amount of places where platform specific path separator is used
|
||||
- now message input is automatically focused when you open a dialog or a room
|
||||
- what() method on unhandled exception now actually tells what happened
|
||||
|
||||
### New features
|
||||
- the settings are here! You con config different stuff from there
|
||||
- now it's possible to set up different qt styles from settings
|
||||
- if you have KConfig nad KConfigWidgets packages installed - you can choose from global color schemes
|
||||
- it's possible now to choose a folder where squawk is going to store downloaded files
|
||||
- now you can correct your message
|
||||
|
||||
## Squawk 0.2.0 (Jan 10, 2022)
|
||||
### Bug fixes
|
||||
- carbon copies switches on again after reconnection
|
||||
|
|
202
CMakeLists.txt
202
CMakeLists.txt
|
@ -1,8 +1,10 @@
|
|||
cmake_minimum_required(VERSION 3.4)
|
||||
project(squawk VERSION 0.1.6 LANGUAGES CXX)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(squawk VERSION 0.2.4 LANGUAGES CXX)
|
||||
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
cmake_policy(SET CMP0077 NEW)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
@ -27,47 +29,50 @@ add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
|
|||
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
|
||||
|
||||
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
|
||||
option(SYSTEM_LMDBAL "Use system lmdbal lib" ON)
|
||||
option(WITH_KWALLET "Build KWallet support module" ON)
|
||||
option(WITH_KIO "Build KIO support module" ON)
|
||||
option(WITH_KCONFIG "Build KConfig support module" ON)
|
||||
option(WITH_OMEMO "Build OMEMO support module" OFF) #it should be off by default untill I sort the problems out
|
||||
|
||||
# Dependencies
|
||||
## Qt
|
||||
find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
|
||||
## Qt, detect and prefer Qt6 if available
|
||||
find_package(Qt6 QUIET CONFIG COMPONENTS Widgets DBus Gui Xml Network Core)
|
||||
if (Qt6_FOUND)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
else()
|
||||
find_package(Qt5 REQUIRED CONFIG COMPONENTS Widgets DBus Gui Xml Network Core)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
endif()
|
||||
|
||||
message(STATUS "Building against Qt${QT_VERSION_MAJOR}")
|
||||
|
||||
find_package(Boost COMPONENTS)
|
||||
|
||||
target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_include_directories(squawk PRIVATE ${Qt5_INCLUDE_DIRS})
|
||||
target_include_directories(squawk PRIVATE ${Qt5Widgets_INCLUDE_DIRS})
|
||||
target_include_directories(squawk PRIVATE ${Qt5DBus_INCLUDE_DIRS})
|
||||
target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS})
|
||||
target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS})
|
||||
target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS})
|
||||
target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS})
|
||||
|
||||
## QXmpp
|
||||
if (SYSTEM_QXMPP)
|
||||
find_package(QXmpp CONFIG)
|
||||
|
||||
if (NOT QXmpp_FOUND)
|
||||
set(SYSTEM_QXMPP OFF)
|
||||
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
|
||||
## OMEMO
|
||||
if (WITH_OMEMO)
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(OMEMO libomemo-c)
|
||||
if (OMEMO_FOUND)
|
||||
target_compile_definitions(squawk PRIVATE WITH_OMEMO)
|
||||
message("Building with support of OMEMO")
|
||||
else ()
|
||||
message("libomemo-c package wasn't found, trying to build without OMEMO support")
|
||||
set(WITH_OMEMO OFF)
|
||||
endif ()
|
||||
else ()
|
||||
message("Building with system QXmpp")
|
||||
message("PKG_CONFIG module wasn't found, can not check libomemo-c support, trying to build without OMEMO support")
|
||||
set(WITH_OMEMO OFF)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT SYSTEM_QXMPP)
|
||||
target_link_libraries(squawk PRIVATE qxmpp)
|
||||
add_subdirectory(external/qxmpp)
|
||||
else ()
|
||||
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
|
||||
endif ()
|
||||
|
||||
## KIO
|
||||
if (WITH_KIO)
|
||||
find_package(KF5KIO CONFIG)
|
||||
find_package(KF${QT_VERSION_MAJOR}KIO CONFIG)
|
||||
|
||||
if (NOT KF5KIO_FOUND)
|
||||
if (NOT KF${QT_VERSION_MAJOR}KIO_FOUND)
|
||||
set(WITH_KIO OFF)
|
||||
message("KIO package wasn't found, KIO support modules wouldn't be built")
|
||||
else ()
|
||||
|
@ -78,9 +83,9 @@ endif ()
|
|||
|
||||
## KWallet
|
||||
if (WITH_KWALLET)
|
||||
find_package(KF5Wallet CONFIG)
|
||||
find_package(KF${QT_VERSION_MAJOR}Wallet CONFIG)
|
||||
|
||||
if (NOT KF5Wallet_FOUND)
|
||||
if (NOT KF${QT_VERSION_MAJOR}Wallet_FOUND)
|
||||
set(WITH_KWALLET OFF)
|
||||
message("KWallet package wasn't found, KWallet support module wouldn't be built")
|
||||
else ()
|
||||
|
@ -89,24 +94,107 @@ if (WITH_KWALLET)
|
|||
endif ()
|
||||
endif ()
|
||||
|
||||
## Signal (TODO)
|
||||
# find_package(Signal REQUIRED)
|
||||
## KConfig
|
||||
if (WITH_KCONFIG)
|
||||
find_package(KF${QT_VERSION_MAJOR}Config CONFIG)
|
||||
if (NOT KF${QT_VERSION_MAJOR}Config_FOUND)
|
||||
set(WITH_KCONFIG OFF)
|
||||
message("KConfig package wasn't found, KConfig support modules wouldn't be built")
|
||||
else()
|
||||
find_package(KF${QT_VERSION_MAJOR}ConfigWidgets CONFIG)
|
||||
if (NOT KF${QT_VERSION_MAJOR}ConfigWidgets_FOUND)
|
||||
set(WITH_KCONFIG OFF)
|
||||
message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built")
|
||||
else()
|
||||
target_compile_definitions(squawk PRIVATE WITH_KCONFIG)
|
||||
message("Building with support of KConfig")
|
||||
message("Building with support of KConfigWidgets")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
## LMDB
|
||||
find_package(LMDB REQUIRED)
|
||||
## QXmpp
|
||||
if (SYSTEM_QXMPP)
|
||||
if (WITH_OMEMO)
|
||||
find_package(QXmppQt${QT_VERSION_MAJOR} CONFIG COMPONENTS Omemo)
|
||||
else ()
|
||||
find_package(QXmppQt${QT_VERSION_MAJOR} CONFIG)
|
||||
endif ()
|
||||
|
||||
# Linking
|
||||
target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
|
||||
target_link_libraries(squawk PRIVATE lmdb)
|
||||
target_link_libraries(squawk PRIVATE simpleCrypt)
|
||||
# Link thread libraries on Linux
|
||||
if (NOT QXmppQt${QT_VERSION_MAJOR}_FOUND)
|
||||
set(SYSTEM_QXMPP OFF)
|
||||
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
|
||||
else ()
|
||||
message("Building with system QXmpp")
|
||||
endif ()
|
||||
endif () #it's endif() + if() and not else() because I want it to have a fallback behaviour
|
||||
if (NOT SYSTEM_QXMPP) #we can fail finding system QXmpp and this way we'll check bundled before failing completely
|
||||
message("Building with bundled QXmpp")
|
||||
|
||||
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/base)
|
||||
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/client)
|
||||
target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src)
|
||||
|
||||
if (WITH_OMEMO)
|
||||
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/omemo)
|
||||
target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src/omemo)
|
||||
set(BUILD_OMEMO ON)
|
||||
set(BUILD_TESTS OFF)
|
||||
else ()
|
||||
set(BUILD_OMEMO OFF)
|
||||
endif ()
|
||||
add_subdirectory(external/qxmpp)
|
||||
add_library(QXmpp::QXmpp ALIAS QXmppQt${QT_VERSION_MAJOR})
|
||||
if (WITH_OMEMO)
|
||||
target_include_directories(QXmppOmemoQt${QT_VERSION_MAJOR} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src)
|
||||
add_library(QXmpp::Omemo ALIAS QXmppOmemoQt${QT_VERSION_MAJOR})
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
## LMDBAL
|
||||
if (SYSTEM_LMDBAL)
|
||||
find_package(lmdbalqt${QT_VERSION_MAJOR})
|
||||
if (NOT lmdbalqt${QT_VERSION_MAJOR}_FOUND)
|
||||
set(SYSTEM_LMDBAL OFF)
|
||||
message("LMDBAL package wasn't found, trying to build with bundled LMDBAL")
|
||||
else ()
|
||||
message("Building with system LMDBAL")
|
||||
endif ()
|
||||
else()
|
||||
message("Building with bundled LMDBAL")
|
||||
set(BUILD_STATIC ON)
|
||||
add_subdirectory(external/lmdbal)
|
||||
add_library(LMDBALQT${QT_VERSION_MAJOR}::LMDBALQT${QT_VERSION_MAJOR} ALIAS LMDBAL)
|
||||
endif()
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
## Linking
|
||||
target_link_libraries(squawk
|
||||
PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::DBus
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Xml
|
||||
LMDBALQT${QT_VERSION_MAJOR}::LMDBALQT${QT_VERSION_MAJOR}
|
||||
OpenSSL::Crypto
|
||||
QXmpp::QXmpp
|
||||
)
|
||||
|
||||
if (WITH_OMEMO)
|
||||
target_link_libraries(squawk PRIVATE QXmpp::Omemo)
|
||||
endif ()
|
||||
|
||||
## Link thread libraries on Linux
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(squawk PRIVATE Threads::Threads)
|
||||
endif()
|
||||
|
||||
# Build type
|
||||
## Build type
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif ()
|
||||
|
@ -114,16 +202,28 @@ endif ()
|
|||
message("Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(squawk PRIVATE
|
||||
"-Wall;-Wextra"
|
||||
"$<$<CONFIG:DEBUG>:-g>"
|
||||
"$<$<CONFIG:RELEASE>:-O3>"
|
||||
"-fno-sized-deallocation" # for eliminating _ZdlPvm
|
||||
)
|
||||
set (COMPILE_OPTIONS -fno-sized-deallocation) # for eliminating _ZdlPvm
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
list(APPEND COMPILE_OPTIONS -O3)
|
||||
endif()
|
||||
if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
list(APPEND COMPILE_OPTIONS -O0)
|
||||
list(APPEND COMPILE_OPTIONS -g3)
|
||||
list(APPEND COMPILE_OPTIONS -Wall)
|
||||
list(APPEND COMPILE_OPTIONS -Wextra)
|
||||
endif()
|
||||
|
||||
message("Compilation options: " ${COMPILE_OPTIONS})
|
||||
target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
|
||||
endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
# I am not really sure about this solution
|
||||
# This should enable plugins to be found in path like /usr/lib/squawk instead of just /usr/lib
|
||||
set(PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/squawk")
|
||||
add_compile_definitions(PLUGIN_PATH="${PLUGIN_PATH}")
|
||||
|
||||
add_subdirectory(main)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(external/simpleCrypt)
|
||||
add_subdirectory(packaging)
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(resources)
|
||||
|
@ -131,13 +231,15 @@ add_subdirectory(shared)
|
|||
add_subdirectory(translations)
|
||||
add_subdirectory(ui)
|
||||
|
||||
# Install the executable
|
||||
## Install the executable
|
||||
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||
install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if (APPLE)
|
||||
add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..."
|
||||
COMMAND "${Qt5Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
|
||||
COMMAND "${Qt${QT_VERSION_MAJOR}Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
|
||||
)
|
||||
endif(APPLE)
|
||||
endif()
|
||||
|
|
|
@ -595,17 +595,17 @@ pointer to where the full notice is found.
|
|||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
|
86
README.md
86
README.md
|
@ -4,17 +4,20 @@
|
|||
[](https://aur.archlinux.org/packages/squawk/)
|
||||
[](https://liberapay.com/macaw.me)
|
||||
|
||||

|
||||

|
||||
|
||||
### Prerequisites
|
||||
|
||||
- QT 5.12 *(lower versions might work but it wasn't tested)*
|
||||
- lmdb
|
||||
- CMake 3.3 or higher
|
||||
- QT 5 or 6
|
||||
- CMake 3.10 or higher
|
||||
- qxmpp 1.1.0 or higher
|
||||
- LMDBAL (my own [library](https://git.macaw.me/blue/lmdbal) for lmdb)
|
||||
- KDE Frameworks: kwallet (optional)
|
||||
- KDE Frameworks: KIO (optional)
|
||||
- Boost
|
||||
- KDE Frameworks: KConfig (optional)
|
||||
- KDE Frameworks: KConfigWidgets (optional)
|
||||
- Boost (just one little hpp from there)
|
||||
- Imagemagick (for compilation, to rasterize an SVG logo)
|
||||
|
||||
### Getting
|
||||
|
||||
|
@ -30,14 +33,49 @@ $ pacaur -S squawk
|
|||
|
||||
### Building
|
||||
|
||||
You can also clone the repo and build it from source
|
||||
You can also the repo and build it from source
|
||||
|
||||
Squawk requires Qt with SSL enabled. It uses CMake as build system.
|
||||
|
||||
Please check the prerequisites and install them before installation.
|
||||
|
||||
---
|
||||
|
||||
There are several ways to build Squawk. The one you need depends on whether you have `qxmpp` and `lmdbal` installed in your system.
|
||||
|
||||
#### Building with system dependencies
|
||||
|
||||
This is the easiest way but it requires you to have `qxmpp` and `lmdbal` installed as system packages. Here is what you do:
|
||||
|
||||
```
|
||||
$ git clone https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake ..
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
#### Building with bundled qxmpp
|
||||
|
||||
If you don't have any of `qxmpp` or `lmdbal` (or both) installed the process is abit mor complicated.
|
||||
On the configuration stage you need to enable one or both entries in the square brackets, depending on what package your system lacks.
|
||||
|
||||
Here is what you do
|
||||
|
||||
```
|
||||
$ git clone --recurse-submodules https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake .. [-D SYSTEM_QXMPP=False] [-D SYSTEM_LMDBAL=False]
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
#### For Windows (Mingw-w64) build
|
||||
|
||||
**Building for windows is not mainteined, but was possible in the past, you can try, but it probably won't work**
|
||||
|
||||
You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
|
||||
|
||||
The best way to acquire library `lmdb` and `boost` is through Msys2.
|
||||
|
@ -46,51 +84,31 @@ First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86
|
|||
|
||||
Then you need to provide the cmake cache entry when calling cmake for configuration:
|
||||
|
||||
```
|
||||
cmake .. -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
|
||||
```
|
||||
|
||||
`<Msys2 Mingw64 Root Directory>`: e.g. `C:/msys64/mingw64`.
|
||||
|
||||
---
|
||||
|
||||
There are two ways to build, it depends whether you have qxmpp installed in your system
|
||||
|
||||
#### Building with system qxmpp
|
||||
|
||||
Here is what you do
|
||||
|
||||
```
|
||||
$ git clone https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
#### Building with bundled qxmpp
|
||||
|
||||
Here is what you do
|
||||
|
||||
```
|
||||
$ git clone --recurse-submodules https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||
$ cmake .. -D SYSTEM_QXMPP=False -D SYSTEM_LMDBAL=False -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
|
||||
You can always refer to `appveyor.yml` to see how AppVeyor build squawk for windows.
|
||||
|
||||
### List of keys
|
||||
|
||||
Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
||||
Here is the list of keys you can pass to configuration phase of `cmake ..`:
|
||||
|
||||
- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
|
||||
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
|
||||
- `SYSTEM_LMDBAL` - `True` tries to link against `LMDABL` installed in the system, `False` builds bundled `LMDBAL` library (default is `True`)
|
||||
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
|
||||
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
|
||||
- `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
|
||||
- `WITH_OMEMO` - `True` builds the OMEMO encryption, requires `qxmpp` of version >= 1.5.0 built with OMEMO support. `False` disables OMEMO support (default is `False`)
|
||||
- `QT_VERSION_MAJOR` - `6` builds against Qt 6, `5` builds against Qt 6, corresponding version of lmdbal and qxmpp should be installed (default is `6`)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license
|
||||
#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code
|
||||
|
||||
# Try to find LMDB headers and library.
|
||||
#
|
||||
# Usage of this module as follows:
|
||||
#
|
||||
# find_package(LMDB)
|
||||
#
|
||||
# Variables used by this module, they can change the default behaviour and need
|
||||
# to be set before calling find_package:
|
||||
#
|
||||
# LMDB_ROOT_DIR Set this variable to the root installation of
|
||||
# LMDB if the module has problems finding the
|
||||
# proper installation path.
|
||||
#
|
||||
# Variables defined by this module:
|
||||
#
|
||||
# LMDB_FOUND System has LMDB library/headers.
|
||||
# LMDB_LIBRARIES The LMDB library.
|
||||
# LMDB_INCLUDE_DIRS The location of LMDB headers.
|
||||
|
||||
find_path(LMDB_ROOT_DIR
|
||||
NAMES include/lmdb.h
|
||||
)
|
||||
|
||||
find_library(LMDB_LIBRARIES
|
||||
NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible
|
||||
HINTS ${LMDB_ROOT_DIR}/lib
|
||||
)
|
||||
|
||||
add_library(lmdb UNKNOWN IMPORTED)
|
||||
set_target_properties(lmdb PROPERTIES
|
||||
IMPORTED_LOCATION ${LMDB_LIBRARIES}
|
||||
)
|
||||
|
||||
find_path(LMDB_INCLUDE_DIRS
|
||||
NAMES lmdb.h
|
||||
HINTS ${LMDB_ROOT_DIR}/include
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LMDB DEFAULT_MSG
|
||||
LMDB_LIBRARIES
|
||||
LMDB_INCLUDE_DIRS
|
||||
)
|
||||
|
||||
mark_as_advanced(
|
||||
LMDB_ROOT_DIR
|
||||
LMDB_LIBRARIES
|
||||
LMDB_INCLUDE_DIRS
|
||||
)
|
|
@ -1,15 +0,0 @@
|
|||
find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
|
||||
find_library(Signal_LIBRARY signal-protocol-c)
|
||||
mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
|
||||
|
||||
if (Signal_FOUND AND NOT TARGET Signal::Signal)
|
||||
add_library(Signal::Signal UNKNOWN IMPORTED)
|
||||
set_target_properties(Signal::Signal PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION "${Signal_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
|
||||
)
|
||||
endif ()
|
|
@ -3,32 +3,32 @@ if(WIN32)
|
|||
set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
|
||||
endif(WIN32)
|
||||
|
||||
target_sources(squawk PRIVATE
|
||||
account.cpp
|
||||
account.h
|
||||
adapterFuctions.cpp
|
||||
archive.cpp
|
||||
archive.h
|
||||
conference.cpp
|
||||
conference.h
|
||||
contact.cpp
|
||||
contact.h
|
||||
main.cpp
|
||||
networkaccess.cpp
|
||||
networkaccess.h
|
||||
rosteritem.cpp
|
||||
rosteritem.h
|
||||
${SIGNALCATCHER_SOURCE}
|
||||
signalcatcher.h
|
||||
squawk.cpp
|
||||
squawk.h
|
||||
storage.cpp
|
||||
storage.h
|
||||
urlstorage.cpp
|
||||
urlstorage.h
|
||||
)
|
||||
set(SOURCE_FILES
|
||||
account.cpp
|
||||
adapterfunctions.cpp
|
||||
conference.cpp
|
||||
contact.cpp
|
||||
rosteritem.cpp
|
||||
${SIGNALCATCHER_SOURCE}
|
||||
squawk.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
account.h
|
||||
adapterfunctions.h
|
||||
conference.h
|
||||
contact.h
|
||||
rosteritem.h
|
||||
signalcatcher.h
|
||||
squawk.h
|
||||
)
|
||||
|
||||
target_sources(squawk PRIVATE ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
|
||||
|
||||
add_subdirectory(handlers)
|
||||
add_subdirectory(passwordStorageEngines)
|
||||
add_subdirectory(components)
|
||||
add_subdirectory(delayManager)
|
||||
add_subdirectory(utils)
|
||||
|
|
844
core/account.cpp
844
core/account.cpp
File diff suppressed because it is too large
Load diff
115
core/account.h
115
core/account.h
|
@ -15,9 +15,7 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_ACCOUNT_H
|
||||
#define CORE_ACCOUNT_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QCryptographicHash>
|
||||
|
@ -29,9 +27,14 @@
|
|||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <list>
|
||||
|
||||
#include <QXmppRosterManager.h>
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
#include <QXmppCarbonManagerV2.h>
|
||||
#else
|
||||
#include <QXmppCarbonManager.h>
|
||||
#endif
|
||||
#include <QXmppDiscoveryManager.h>
|
||||
#include <QXmppMamManager.h>
|
||||
#include <QXmppMucManager.h>
|
||||
|
@ -39,32 +42,58 @@
|
|||
#include <QXmppBookmarkManager.h>
|
||||
#include <QXmppBookmarkSet.h>
|
||||
#include <QXmppUploadRequestManager.h>
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppVCardManager.h>
|
||||
#include <QXmppMessageReceiptManager.h>
|
||||
#include <QXmppPubSubManager.h>
|
||||
|
||||
#include "shared/shared.h"
|
||||
#include <shared/shared.h>
|
||||
#include <shared/identity.h>
|
||||
#include <shared/info.h>
|
||||
#include <shared/clientid.h>
|
||||
#include "contact.h"
|
||||
#include "conference.h"
|
||||
#include "networkaccess.h"
|
||||
#include <core/components/networkaccess.h>
|
||||
#include <core/delayManager/manager.h>
|
||||
|
||||
#include "handlers/messagehandler.h"
|
||||
#include "handlers/rosterhandler.h"
|
||||
#include "handlers/vcardhandler.h"
|
||||
#include "handlers/discoveryhandler.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
#include <QXmppTrustManager.h>
|
||||
#ifdef WITH_OMEMO
|
||||
#include <QXmppOmemoManager.h>
|
||||
#include "handlers/omemohandler.h"
|
||||
#endif
|
||||
#include "handlers/trusthandler.h"
|
||||
#endif
|
||||
|
||||
class Account : public QObject
|
||||
{
|
||||
namespace Core {
|
||||
|
||||
class Account : public QObject {
|
||||
Q_OBJECT
|
||||
friend class MessageHandler;
|
||||
friend class RosterHandler;
|
||||
friend class VCardHandler;
|
||||
friend class DiscoveryHandler;
|
||||
#ifdef WITH_OMEMO
|
||||
friend class OmemoHandler;
|
||||
friend class TrustHandler;
|
||||
#endif
|
||||
public:
|
||||
enum class Error {
|
||||
authentication,
|
||||
other,
|
||||
none
|
||||
};
|
||||
|
||||
Account(
|
||||
const QString& p_login,
|
||||
const QString& p_server,
|
||||
const QString& p_password,
|
||||
const QString& p_name,
|
||||
bool p_active,
|
||||
NetworkAccess* p_net,
|
||||
QObject* parent = 0);
|
||||
~Account();
|
||||
|
@ -76,8 +105,12 @@ public:
|
|||
QString getPassword() const;
|
||||
QString getResource() const;
|
||||
QString getAvatarPath() const;
|
||||
QString getBareJid() const;
|
||||
QString getFullJid() const;
|
||||
Shared::Availability getAvailability() const;
|
||||
Shared::AccountPassword getPasswordType() const;
|
||||
Error getLastError() const;
|
||||
bool getActive() const;
|
||||
|
||||
void setName(const QString& p_name);
|
||||
void setLogin(const QString& p_login);
|
||||
|
@ -86,8 +119,8 @@ public:
|
|||
void setResource(const QString& p_resource);
|
||||
void setAvailability(Shared::Availability avail);
|
||||
void setPasswordType(Shared::AccountPassword pt);
|
||||
QString getFullJid() const;
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void setActive(bool p_active);
|
||||
void requestArchive(const QString& jid, int count, const QString& before);
|
||||
void subscribeToContact(const QString& jid, const QString& reason);
|
||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||
|
@ -97,19 +130,24 @@ public:
|
|||
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
|
||||
void renameContactRequest(const QString& jid, const QString& newName);
|
||||
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||
void setContactEncryption(const QString& jid, Shared::EncryptionProtocol value);
|
||||
|
||||
void setRoomJoined(const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& jid, bool joined);
|
||||
void removeRoomRequest(const QString& jid);
|
||||
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void uploadVCard(const Shared::VCard& card);
|
||||
void updateInfo(const Shared::Info& info);
|
||||
void resendMessage(const QString& jid, const QString& id);
|
||||
void replaceMessage(const QString& originalId, const Shared::Message& data);
|
||||
void invalidatePassword();
|
||||
|
||||
void discoverInfo(const QString& address, const QString& node);
|
||||
|
||||
public slots:
|
||||
void connect();
|
||||
void disconnect();
|
||||
void reconnect();
|
||||
void requestVCard(const QString& jid);
|
||||
void requestInfo(const QString& jid);
|
||||
|
||||
signals:
|
||||
void changed(const QMap<QString, QVariant>& data);
|
||||
|
@ -133,9 +171,12 @@ signals:
|
|||
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& jid, const QString& nickName);
|
||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
||||
void infoReady(const Shared::Info& info);
|
||||
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
||||
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
||||
void needPassword();
|
||||
void infoDiscovered(const QString& address, const QString& node, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
|
||||
void pepSupportChanged(Shared::Support support);
|
||||
|
||||
private:
|
||||
QString name;
|
||||
|
@ -144,7 +185,26 @@ private:
|
|||
QXmppConfiguration config;
|
||||
QXmppPresence presence;
|
||||
Shared::ConnectionState state;
|
||||
|
||||
MessageHandler* mh;
|
||||
RosterHandler* rh;
|
||||
VCardHandler* vh;
|
||||
DiscoveryHandler* dh;
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
TrustHandler* th;
|
||||
#endif
|
||||
#ifdef WITH_OMEMO
|
||||
OmemoHandler* oh;
|
||||
|
||||
QXmppOmemoManager* om;
|
||||
#endif
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
QXmppTrustManager* tm;
|
||||
QXmppCarbonManagerV2* cm;
|
||||
QXmppPubSubManager* psm;
|
||||
#else
|
||||
QXmppCarbonManager* cm;
|
||||
#endif
|
||||
QXmppMamManager* am;
|
||||
QXmppMucManager* mm;
|
||||
QXmppBookmarkManager* bm;
|
||||
|
@ -156,16 +216,14 @@ private:
|
|||
bool reconnectScheduled;
|
||||
QTimer* reconnectTimer;
|
||||
|
||||
std::set<QString> pendingVCardRequests;
|
||||
|
||||
QString avatarHash;
|
||||
QString avatarType;
|
||||
bool ownVCardRequestInProgress;
|
||||
NetworkAccess* network;
|
||||
DelayManager::Manager* delay;
|
||||
Shared::AccountPassword passwordType;
|
||||
|
||||
MessageHandler* mh;
|
||||
RosterHandler* rh;
|
||||
Error lastError;
|
||||
Shared::Support pepSupport;
|
||||
bool active;
|
||||
bool notReadyPassword;
|
||||
bool loadingOmemo;
|
||||
|
||||
private slots:
|
||||
void onClientStateChange(QXmppClient::State state);
|
||||
|
@ -178,22 +236,13 @@ private slots:
|
|||
void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
|
||||
|
||||
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
||||
|
||||
void onVCardReceived(const QXmppVCardIq& card);
|
||||
void onOwnVCardReceived(const QXmppVCardIq& card);
|
||||
|
||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
private:
|
||||
void handleDisconnection();
|
||||
void onReconnectTimer();
|
||||
void setPepSupport(Shared::Support support);
|
||||
void runDiscoveryService();
|
||||
};
|
||||
|
||||
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
||||
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
||||
}
|
||||
|
||||
|
||||
#endif // CORE_ACCOUNT_H
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
@ -15,10 +15,8 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CORE_ADAPTER_FUNCTIONS_H
|
||||
#define CORE_ADAPTER_FUNCTIONS_H
|
||||
|
||||
#include "account.h"
|
||||
#include "adapterfunctions.h"
|
||||
|
||||
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
|
||||
{
|
||||
|
@ -271,5 +269,3 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
|
|||
iq.setEmails(emails);
|
||||
iq.setPhones(phs);
|
||||
}
|
||||
|
||||
#endif // CORE_ADAPTER_FUNCTIONS_H
|
47
core/adapterfunctions.h
Normal file
47
core/adapterfunctions.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CORE_ADAPTER_FUNCTIONS_H
|
||||
#define CORE_ADAPTER_FUNCTIONS_H
|
||||
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppTask.h>
|
||||
#include <QXmppPromise.h>
|
||||
#include <shared/vcard.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
||||
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
||||
|
||||
template<typename T>
|
||||
QXmppTask<T> makeReadyTask(T &&value) {
|
||||
QXmppPromise<T> promise;
|
||||
promise.finish(std::move(value));
|
||||
return promise.task();
|
||||
}
|
||||
|
||||
inline QXmppTask<void> makeReadyTask() {
|
||||
QXmppPromise<void> promise;
|
||||
promise.finish();
|
||||
return promise.task();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // CORE_ADAPTER_FUNCTIONS_H
|
1006
core/archive.cpp
1006
core/archive.cpp
File diff suppressed because it is too large
Load diff
197
core/archive.h
197
core/archive.h
|
@ -1,197 +0,0 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_ARCHIVE_H
|
||||
#define CORE_ARCHIVE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QCryptographicHash>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "shared/exception.h"
|
||||
#include <lmdb.h>
|
||||
#include <list>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Archive : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class AvatarInfo;
|
||||
|
||||
Archive(const QString& jid, QObject* parent = 0);
|
||||
~Archive();
|
||||
|
||||
void open(const QString& account);
|
||||
void close();
|
||||
|
||||
bool addElement(const Shared::Message& message);
|
||||
unsigned int addElements(const std::list<Shared::Message>& messages);
|
||||
Shared::Message getElement(const QString& id) const;
|
||||
bool hasElement(const QString& id) const;
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
Shared::Message oldest();
|
||||
QString oldestId();
|
||||
Shared::Message newest();
|
||||
QString newestId();
|
||||
void clear();
|
||||
long unsigned int size() const;
|
||||
std::list<Shared::Message> getBefore(int count, const QString& id);
|
||||
bool isFromTheBeginning();
|
||||
void setFromTheBeginning(bool is);
|
||||
bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
|
||||
AvatarInfo getAvatarInfo(const QString& resource = "") const;
|
||||
bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
|
||||
void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
|
||||
QString idByStanzaId(const QString& stanzaId) const;
|
||||
QString stanzaIdById(const QString& id) const;
|
||||
|
||||
public:
|
||||
const QString jid;
|
||||
|
||||
public:
|
||||
class Directory:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
Directory(const std::string& p_path):Exception(), path(p_path){}
|
||||
|
||||
std::string getMessage() const{return "Can't create directory for database at " + path;}
|
||||
private:
|
||||
std::string path;
|
||||
};
|
||||
|
||||
class Closed:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){}
|
||||
|
||||
std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;}
|
||||
private:
|
||||
std::string operation;
|
||||
std::string account;
|
||||
};
|
||||
|
||||
class NotFound:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){}
|
||||
|
||||
std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;}
|
||||
private:
|
||||
std::string key;
|
||||
std::string account;
|
||||
};
|
||||
|
||||
class Empty:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
Empty(const std::string& acc):Exception(), account(acc){}
|
||||
|
||||
std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";}
|
||||
private:
|
||||
std::string account;
|
||||
};
|
||||
|
||||
class Exist:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){}
|
||||
|
||||
std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";}
|
||||
private:
|
||||
std::string account;
|
||||
std::string key;
|
||||
};
|
||||
|
||||
class NoAvatar:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){
|
||||
if (resource.size() == 0) {
|
||||
resource = "for himself";
|
||||
}
|
||||
}
|
||||
|
||||
std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;}
|
||||
private:
|
||||
std::string element;
|
||||
std::string resource;
|
||||
};
|
||||
|
||||
class Unknown:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){}
|
||||
|
||||
std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;}
|
||||
private:
|
||||
std::string account;
|
||||
std::string msg;
|
||||
};
|
||||
|
||||
|
||||
class AvatarInfo {
|
||||
public:
|
||||
AvatarInfo();
|
||||
AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
|
||||
|
||||
void deserialize(char* pointer, uint32_t size);
|
||||
void serialize(QByteArray* ba) const;
|
||||
|
||||
QString type;
|
||||
QByteArray hash;
|
||||
bool autogenerated;
|
||||
};
|
||||
|
||||
private:
|
||||
bool opened;
|
||||
bool fromTheBeginning;
|
||||
MDB_env* environment;
|
||||
MDB_dbi main; //id to message
|
||||
MDB_dbi order; //time to id
|
||||
MDB_dbi stats;
|
||||
MDB_dbi avatars;
|
||||
MDB_dbi sid; //stanzaId to id
|
||||
|
||||
bool getStatBoolValue(const std::string& id, MDB_txn* txn);
|
||||
std::string getStatStringValue(const std::string& id, MDB_txn* txn);
|
||||
|
||||
bool setStatValue(const std::string& id, bool value, MDB_txn* txn);
|
||||
bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
|
||||
bool readAvatarInfo(AvatarInfo& target, const std::string& res, MDB_txn* txn) const;
|
||||
void printOrder();
|
||||
void printKeys();
|
||||
bool dropAvatar(const std::string& resource);
|
||||
Shared::Message getMessage(const std::string& id, MDB_txn* txn) const;
|
||||
Shared::Message getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc);
|
||||
Shared::Message edge(bool end);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_ARCHIVE_H
|
15
core/components/CMakeLists.txt
Normal file
15
core/components/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
set(SOURCE_FILES
|
||||
networkaccess.cpp
|
||||
clientcache.cpp
|
||||
urlstorage.cpp
|
||||
archive.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
networkaccess.h
|
||||
clientcache.h
|
||||
urlstorage.h
|
||||
archive.h
|
||||
)
|
||||
|
||||
target_sources(squawk PRIVATE ${SOURCE_FILES})
|
419
core/components/archive.cpp
Normal file
419
core/components/archive.cpp
Normal file
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "archive.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Core::Archive::Archive(const QString& account, const QString& p_jid, QObject* parent):
|
||||
QObject(parent),
|
||||
jid(p_jid),
|
||||
account(account),
|
||||
opened(false),
|
||||
db(account + "/" + jid),
|
||||
messages(db.addStorage<QString, Shared::Message>("messages")),
|
||||
order(db.addStorage<uint64_t, QString>("order", true)),
|
||||
stats(db.addStorage<QString, QVariant>("stats")),
|
||||
avatars(db.addStorage<QString, AvatarInfo>("avatars")),
|
||||
stanzaIdToId(db.addStorage<QString, QString>("stanzaIdToId")),
|
||||
cursor(order->createCursor())
|
||||
{}
|
||||
|
||||
Core::Archive::~Archive() {
|
||||
close();
|
||||
}
|
||||
|
||||
void Core::Archive::open() {
|
||||
db.open();
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
|
||||
AvatarInfo info;
|
||||
bool hasAvatar = false;
|
||||
try {
|
||||
avatars->getRecord(jid, info, txn);
|
||||
hasAvatar = true;
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
if (!hasAvatar)
|
||||
return;
|
||||
|
||||
QFile ava(db.getPath() + "/" + jid + "." + info.type);
|
||||
if (ava.exists())
|
||||
return;
|
||||
|
||||
try {
|
||||
avatars->removeRecord(jid, txn);
|
||||
txn.commit();
|
||||
} catch (const std::exception& e) {
|
||||
qDebug() << e.what();
|
||||
qDebug() << "error opening archive" << jid << "for account" << account
|
||||
<< ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::close() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
bool Core::Archive::addElement(const Shared::Message& message) {
|
||||
QString id = message.getId();
|
||||
qDebug() << "Adding message with id " << id;
|
||||
|
||||
try {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
messages->addRecord(id, message, txn);
|
||||
order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
|
||||
QString stanzaId = message.getStanzaId();
|
||||
if (!stanzaId.isEmpty())
|
||||
stanzaIdToId->addRecord(stanzaId, id, txn);
|
||||
|
||||
txn.commit();
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
qDebug() << "Could not add message with id " + id;
|
||||
qDebug() << e.what();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Core::Archive::clear() {
|
||||
db.drop();
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::getElement(const QString& id) const {
|
||||
return messages->getRecord(id);
|
||||
}
|
||||
|
||||
bool Core::Archive::hasElement(const QString& id) const {
|
||||
return messages->checkRecord(id);
|
||||
}
|
||||
|
||||
void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVariant>& data) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
Shared::Message msg = messages->getRecord(id, txn);
|
||||
|
||||
bool hadStanzaId = !msg.getStanzaId().isEmpty();
|
||||
QDateTime oTime = msg.getTime();
|
||||
bool idChange = msg.change(data);
|
||||
QString newId = msg.getId();
|
||||
QDateTime nTime = msg.getTime();
|
||||
|
||||
bool orderChange = oTime != nTime;
|
||||
if (idChange || orderChange) {
|
||||
if (idChange)
|
||||
messages->removeRecord(id, txn);
|
||||
|
||||
if (orderChange)
|
||||
order->removeRecord(oTime.toMSecsSinceEpoch(), txn);
|
||||
|
||||
order->forceRecord(nTime.toMSecsSinceEpoch(), newId, txn);
|
||||
}
|
||||
|
||||
QString sid = msg.getStanzaId();
|
||||
if (!sid.isEmpty() && (idChange || !hadStanzaId))
|
||||
stanzaIdToId->forceRecord(sid, newId, txn);
|
||||
|
||||
messages->forceRecord(newId, msg, txn);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::newest() const {
|
||||
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
|
||||
|
||||
try {
|
||||
cursor.open(txn);
|
||||
while (true) {
|
||||
std::pair<uint64_t, QString> pair = cursor.prev();
|
||||
Shared::Message msg = messages->getRecord(pair.second, txn);
|
||||
if (msg.serverStored()) {
|
||||
cursor.close();
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
cursor.close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Archive::newestId() const {
|
||||
Shared::Message msg = newest();
|
||||
return msg.getId();
|
||||
}
|
||||
|
||||
QString Core::Archive::oldestId() const {
|
||||
Shared::Message msg = oldest();
|
||||
return msg.getId();
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::oldest() const {
|
||||
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
|
||||
|
||||
try {
|
||||
cursor.open(txn);
|
||||
while (true) {
|
||||
std::pair<uint64_t, QString> pair = cursor.next();
|
||||
Shared::Message msg = messages->getRecord(pair.second, txn);
|
||||
if (msg.serverStored()) {
|
||||
cursor.close();
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
cursor.close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages) {
|
||||
unsigned int success = 0;
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
for (const Shared::Message& message : messages) {
|
||||
QString id = message.getId();
|
||||
bool added = false;
|
||||
try {
|
||||
Core::Archive::messages->addRecord(id, message, txn);
|
||||
added = true;
|
||||
} catch (const LMDBAL::Exist& e) {}
|
||||
|
||||
if (!added)
|
||||
continue;
|
||||
|
||||
order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
|
||||
|
||||
QString sid = message.getStanzaId();
|
||||
if (!sid.isEmpty())
|
||||
stanzaIdToId->addRecord(sid, id, txn);
|
||||
|
||||
++success;
|
||||
}
|
||||
txn.commit();
|
||||
return success;
|
||||
}
|
||||
|
||||
long unsigned int Core::Archive::size() const {
|
||||
return order->count();
|
||||
}
|
||||
|
||||
std::list<Shared::Message> Core::Archive::getBefore(unsigned int count, const QString& id) {
|
||||
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
|
||||
std::list<Shared::Message> res;
|
||||
try {
|
||||
cursor.open(txn);
|
||||
if (!id.isEmpty()) {
|
||||
Shared::Message reference = messages->getRecord(id, txn);
|
||||
uint64_t stamp = reference.getTime().toMSecsSinceEpoch();
|
||||
cursor.set(stamp);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < count; ++i) {
|
||||
std::pair<uint64_t, QString> pair;
|
||||
cursor.prev(pair.first, pair.second);
|
||||
|
||||
res.emplace_back();
|
||||
Shared::Message& msg = res.back();
|
||||
messages->getRecord(pair.second, msg, txn);
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return res;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
cursor.close();
|
||||
if (res.empty())
|
||||
throw e;
|
||||
else
|
||||
return res;
|
||||
} catch (...) {
|
||||
cursor.close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::isFromTheBeginning() const {
|
||||
try {
|
||||
return stats->getRecord("fromTheBeginning").toBool();
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::setFromTheBeginning(bool is) {
|
||||
stats->forceRecord("fromTheBeginning", is);
|
||||
}
|
||||
|
||||
Shared::EncryptionProtocol Core::Archive::encryption() const {
|
||||
try {
|
||||
return stats->getRecord("encryption").value<Shared::EncryptionProtocol>();
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return Shared::EncryptionProtocol::none;
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::setEncryption(Shared::EncryptionProtocol is) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
Shared::EncryptionProtocol current = Shared::EncryptionProtocol::none;
|
||||
try {
|
||||
current = stats->getRecord("encryption", txn).value<Shared::EncryptionProtocol>();
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
if (is != current) {
|
||||
stats->forceRecord("encryption", static_cast<uint8_t>(is), txn);
|
||||
txn.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Core::Archive::idByStanzaId(const QString& stanzaId) const {
|
||||
return stanzaIdToId->getRecord(stanzaId);
|
||||
}
|
||||
|
||||
QString Core::Archive::stanzaIdById(const QString& id) const {
|
||||
try {
|
||||
Shared::Message msg = getElement(id);
|
||||
return msg.getStanzaId();
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
AvatarInfo oldInfo;
|
||||
bool haveAvatar = false;
|
||||
QString res = resource.isEmpty() ? jid : resource;
|
||||
try {
|
||||
avatars->getRecord(res, oldInfo, txn);
|
||||
haveAvatar = true;
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
if (data.size() == 0) {
|
||||
if (!haveAvatar)
|
||||
return false;
|
||||
|
||||
avatars->removeRecord(res, txn);
|
||||
txn.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString currentPath = db.getPath();
|
||||
bool needToRemoveOld = false;
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(data);
|
||||
QByteArray newHash(hash.result());
|
||||
if (haveAvatar) {
|
||||
if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash)
|
||||
return false;
|
||||
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type);
|
||||
if (oldAvatar.exists()) {
|
||||
if (oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type + ".bak")) {
|
||||
needToRemoveOld = true;
|
||||
} else {
|
||||
qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
QMimeDatabase mimedb;
|
||||
QMimeType type = mimedb.mimeTypeForData(data);
|
||||
QString ext = type.preferredSuffix();
|
||||
QFile newAvatar(currentPath + "/" + res + "." + ext);
|
||||
if (!newAvatar.open(QFile::WriteOnly)) {
|
||||
qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
|
||||
if (needToRemoveOld) {
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
|
||||
oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
newAvatar.write(data);
|
||||
newAvatar.close();
|
||||
|
||||
newInfo.type = ext;
|
||||
newInfo.hash = newHash;
|
||||
newInfo.autogenerated = generated;
|
||||
try {
|
||||
avatars->forceRecord(res, newInfo, txn);
|
||||
txn.commit();
|
||||
} catch (...) {
|
||||
qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
|
||||
if (needToRemoveOld) {
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
|
||||
oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (needToRemoveOld) {
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
|
||||
oldAvatar.remove();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const {
|
||||
try {
|
||||
avatars->getRecord(resource.isEmpty() ? jid : resource, target);
|
||||
return true;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const {
|
||||
avatars->readAll(data);
|
||||
}
|
||||
|
||||
Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const {
|
||||
return avatars->getRecord(resource);
|
||||
}
|
||||
|
||||
Core::Archive::AvatarInfo::AvatarInfo():
|
||||
type(),
|
||||
hash(),
|
||||
autogenerated(false)
|
||||
{}
|
||||
|
||||
Core::Archive::AvatarInfo::AvatarInfo(const QString& p_type, const QByteArray& p_hash, bool p_autogenerated):
|
||||
type(p_type),
|
||||
hash(p_hash),
|
||||
autogenerated(p_autogenerated)
|
||||
{}
|
||||
|
||||
QDataStream & operator<<(QDataStream& out, const Core::Archive::AvatarInfo& info) {
|
||||
out << info.type;
|
||||
out << info.hash;
|
||||
out << info.autogenerated;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream & operator>>(QDataStream& in, Core::Archive::AvatarInfo& info) {
|
||||
in >> info.type;
|
||||
in >> info.hash;
|
||||
in >> info.autogenerated;
|
||||
|
||||
return in;
|
||||
}
|
101
core/components/archive.h
Normal file
101
core/components/archive.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QCryptographicHash>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
#include <QDataStream>
|
||||
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/exception.h"
|
||||
#include <list>
|
||||
|
||||
#include <base.h>
|
||||
#include <storage.h>
|
||||
#include <cursor.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Archive : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
class AvatarInfo;
|
||||
|
||||
Archive(const QString& account, const QString& jid, QObject* parent = 0);
|
||||
~Archive();
|
||||
|
||||
void open();
|
||||
void close();
|
||||
|
||||
bool addElement(const Shared::Message& message);
|
||||
unsigned int addElements(const std::list<Shared::Message>& messages);
|
||||
Shared::Message getElement(const QString& id) const;
|
||||
bool hasElement(const QString& id) const;
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
Shared::Message oldest() const;
|
||||
QString oldestId() const;
|
||||
Shared::Message newest() const;
|
||||
QString newestId() const;
|
||||
void clear();
|
||||
long unsigned int size() const;
|
||||
std::list<Shared::Message> getBefore(unsigned int count, const QString& id);
|
||||
bool isFromTheBeginning() const;
|
||||
void setFromTheBeginning(bool is);
|
||||
Shared::EncryptionProtocol encryption() const;
|
||||
bool setEncryption(Shared::EncryptionProtocol value); //returns true if changed, false otherwise
|
||||
bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
|
||||
AvatarInfo getAvatarInfo(const QString& resource = "") const;
|
||||
bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
|
||||
void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
|
||||
QString idByStanzaId(const QString& stanzaId) const;
|
||||
QString stanzaIdById(const QString& id) const;
|
||||
|
||||
public:
|
||||
const QString jid;
|
||||
const QString account;
|
||||
|
||||
public:
|
||||
class AvatarInfo {
|
||||
public:
|
||||
AvatarInfo();
|
||||
AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
|
||||
|
||||
QString type;
|
||||
QByteArray hash;
|
||||
bool autogenerated;
|
||||
};
|
||||
|
||||
private:
|
||||
bool opened;
|
||||
LMDBAL::Base db;
|
||||
LMDBAL::Storage<QString, Shared::Message>* messages;
|
||||
LMDBAL::Storage<uint64_t, QString>* order;
|
||||
LMDBAL::Storage<QString, QVariant>* stats;
|
||||
LMDBAL::Storage<QString, AvatarInfo>* avatars;
|
||||
LMDBAL::Storage<QString, QString>* stanzaIdToId;
|
||||
mutable LMDBAL::Cursor<uint64_t, QString> cursor;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QDataStream& operator << (QDataStream &out, const Core::Archive::AvatarInfo& info);
|
||||
QDataStream& operator >> (QDataStream &in, Core::Archive::AvatarInfo& info);
|
77
core/components/clientcache.cpp
Normal file
77
core/components/clientcache.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "clientcache.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Core::ClientCache::ClientCache():
|
||||
db("clients"),
|
||||
cache(db.addCache<QString, Shared::ClientInfo>("info")),
|
||||
requested(),
|
||||
specific()
|
||||
{
|
||||
db.open();
|
||||
}
|
||||
|
||||
Core::ClientCache::~ClientCache() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
void Core::ClientCache::open() {
|
||||
db.open();}
|
||||
|
||||
void Core::ClientCache::close() {
|
||||
db.close();}
|
||||
|
||||
bool Core::ClientCache::checkClient(const Shared::ClientId& p_id) {
|
||||
QString id = p_id.getId();
|
||||
if (requested.count(id) == 0 && !cache->checkRecord(id)) {
|
||||
requested.emplace(id, p_id);
|
||||
emit requestClientInfo(id);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Core::ClientCache::registerClientInfo (
|
||||
const QString& sourceFullJid,
|
||||
const QString& id,
|
||||
const std::set<Shared::Identity>& identities,
|
||||
const std::set<QString>& features)
|
||||
{
|
||||
std::map<QString, Shared::ClientInfo>::iterator itr = requested.find(id);
|
||||
if (itr != requested.end()) {
|
||||
Shared::ClientInfo& info = itr->second;
|
||||
info.identities = identities;
|
||||
info.extensions = features;
|
||||
|
||||
bool valid = info.valid();
|
||||
if (valid) {
|
||||
cache->addRecord(id, info);
|
||||
} else {
|
||||
info.specificPresence = sourceFullJid;
|
||||
specific.insert(std::make_pair(sourceFullJid, info));
|
||||
}
|
||||
requested.erase(id);
|
||||
return valid;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
63
core/components/clientcache.h
Normal file
63
core/components/clientcache.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <cache.h>
|
||||
|
||||
#include <shared/clientid.h>
|
||||
#include <shared/clientinfo.h>
|
||||
#include <shared/identity.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ClientCache : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ClientCache();
|
||||
~ClientCache();
|
||||
|
||||
void open();
|
||||
void close();
|
||||
|
||||
signals:
|
||||
void requestClientInfo(const QString& id);
|
||||
|
||||
public slots:
|
||||
bool checkClient(const Shared::ClientId& id);
|
||||
bool registerClientInfo(
|
||||
const QString& sourceFullJid,
|
||||
const QString& id,
|
||||
const std::set<Shared::Identity>& identities,
|
||||
const std::set<QString>& features
|
||||
);
|
||||
|
||||
private:
|
||||
LMDBAL::Base db;
|
||||
LMDBAL::Cache<QString, Shared::ClientInfo>* cache;
|
||||
std::map<QString, Shared::ClientInfo> requested;
|
||||
std::map<QString, Shared::ClientInfo> specific;
|
||||
};
|
||||
|
||||
}
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
|
@ -28,17 +27,18 @@ Core::NetworkAccess::NetworkAccess(QObject* parent):
|
|||
manager(0),
|
||||
storage("fileURLStorage"),
|
||||
downloads(),
|
||||
uploads()
|
||||
uploads(),
|
||||
currentPath()
|
||||
{
|
||||
QSettings settings;
|
||||
currentPath = settings.value("downloadsPath").toString();
|
||||
}
|
||||
|
||||
Core::NetworkAccess::~NetworkAccess()
|
||||
{
|
||||
Core::NetworkAccess::~NetworkAccess() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::downladFile(const QString& url)
|
||||
{
|
||||
void Core::NetworkAccess::downladFile(const QString& url) {
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
|
||||
|
@ -47,27 +47,25 @@ void Core::NetworkAccess::downladFile(const QString& url)
|
|||
std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
|
||||
if (p.first.size() > 0) {
|
||||
QFileInfo info(p.first);
|
||||
if (info.exists() && info.isFile()) {
|
||||
if (info.exists() && info.isFile())
|
||||
emit downloadFileComplete(p.second, p.first);
|
||||
} else {
|
||||
else
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} else {
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} catch (const Archive::NotFound& e) {
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
|
||||
storage.addFile(url);
|
||||
startDownload(std::list<Shared::MessageInfo>(), url);
|
||||
} catch (const Archive::Unknown& e) {
|
||||
} catch (const LMDBAL::Unknown& e) {
|
||||
qDebug() << "Error requesting file path:" << e.what();
|
||||
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::start()
|
||||
{
|
||||
void Core::NetworkAccess::start() {
|
||||
if (!running) {
|
||||
manager = new QNetworkAccessManager();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
|
@ -78,8 +76,7 @@ void Core::NetworkAccess::start()
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::stop()
|
||||
{
|
||||
void Core::NetworkAccess::stop() {
|
||||
if (running) {
|
||||
storage.close();
|
||||
manager->deleteLater();
|
||||
|
@ -93,8 +90,7 @@ void Core::NetworkAccess::stop()
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
|
@ -112,8 +108,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
||||
{
|
||||
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) {
|
||||
qDebug() << "DEBUG: DOWNLOAD ERROR";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
qDebug() << rpl->errorString();
|
||||
|
@ -131,12 +126,11 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
|
||||
{
|
||||
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors) {
|
||||
qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
|
||||
for (const QSslError& err : errors) {
|
||||
for (const QSslError& err : errors)
|
||||
qDebug() << err.errorString();
|
||||
}
|
||||
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
|
@ -151,9 +145,7 @@ void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
{
|
||||
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) {
|
||||
QString errorText("");
|
||||
switch (code) {
|
||||
case QNetworkReply::NoError:
|
||||
|
@ -277,8 +269,7 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
|||
}
|
||||
|
||||
|
||||
void Core::NetworkAccess::onDownloadFinished()
|
||||
{
|
||||
void Core::NetworkAccess::onDownloadFinished() {
|
||||
qDebug() << "DEBUG: DOWNLOAD FINISHED";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
|
@ -293,16 +284,16 @@ void Core::NetworkAccess::onDownloadFinished()
|
|||
QStringList hops = url.split("/");
|
||||
QString fileName = hops.back();
|
||||
QString jid;
|
||||
if (dwn->messages.size() > 0) {
|
||||
if (dwn->messages.size() > 0)
|
||||
jid = dwn->messages.front().jid;
|
||||
} else {
|
||||
else
|
||||
qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
|
||||
}
|
||||
|
||||
QString path = prepareDirectory(jid);
|
||||
if (path.size() > 0) {
|
||||
path = checkFileName(fileName, path);
|
||||
|
||||
QFile file(path);
|
||||
QFile file(Shared::resolvePath(path));
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dwn->reply->readAll());
|
||||
file.close();
|
||||
|
@ -316,11 +307,10 @@ void Core::NetworkAccess::onDownloadFinished()
|
|||
err = "Couldn't prepare a directory for file";
|
||||
}
|
||||
|
||||
if (path.size() > 0) {
|
||||
if (path.size() > 0)
|
||||
emit downloadFileComplete(dwn->messages, path);
|
||||
} else {
|
||||
else
|
||||
emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
|
||||
}
|
||||
}
|
||||
|
||||
dwn->reply->deleteLater();
|
||||
|
@ -329,8 +319,7 @@ void Core::NetworkAccess::onDownloadFinished()
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
|
||||
{
|
||||
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url) {
|
||||
Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
|
||||
QNetworkRequest req(url);
|
||||
dwn->reply = manager->get(req);
|
||||
|
@ -346,8 +335,7 @@ void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& ms
|
|||
emit loadFileProgress(dwn->messages, 0, false);
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
||||
{
|
||||
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) {
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||
|
@ -365,8 +353,7 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onUploadFinished()
|
||||
{
|
||||
void Core::NetworkAccess::onUploadFinished() {
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||
|
@ -376,34 +363,30 @@ void Core::NetworkAccess::onUploadFinished()
|
|||
Transfer* upl = itr->second;
|
||||
if (upl->success) {
|
||||
qDebug() << "upload success for" << url;
|
||||
|
||||
storage.addFile(upl->messages, upl->url, upl->path);
|
||||
emit uploadFileComplete(upl->messages, upl->url, upl->path);
|
||||
|
||||
// Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
|
||||
if (upl->path.startsWith(QDir::tempPath() + QStringLiteral("/squawk_img_attach_")) && upl->path.endsWith(".png")) {
|
||||
if (upl->path.startsWith(QDir::tempPath() + QDir::separator() + QStringLiteral("squawk_img_attach_")) && upl->path.endsWith(".png")) {
|
||||
QString err = "";
|
||||
QString downloadDirPath = prepareDirectory(upl->messages.front().jid);
|
||||
if (downloadDirPath.size() > 0) {
|
||||
QString newPath = downloadDirPath + "/" + upl->path.mid(QDir::tempPath().length() + 1);
|
||||
QString newPath = downloadDirPath + QDir::separator() + upl->path.mid(QDir::tempPath().length() + 1);
|
||||
|
||||
// Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
|
||||
bool copyResult = QFile::copy(upl->path, newPath);
|
||||
|
||||
if (copyResult) {
|
||||
// Change storage
|
||||
storage.setPath(upl->url, newPath);
|
||||
} else {
|
||||
bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath));
|
||||
if (copyResult)
|
||||
upl->path = newPath; // Change storage
|
||||
else
|
||||
err = "copying to " + newPath + " failed";
|
||||
}
|
||||
} else {
|
||||
err = "Couldn't prepare a directory for file";
|
||||
}
|
||||
|
||||
if (err.size() != 0) {
|
||||
if (err.size() != 0)
|
||||
qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
|
||||
}
|
||||
}
|
||||
|
||||
storage.addFile(upl->messages, upl->url, upl->path);
|
||||
emit uploadFileComplete(upl->messages, upl->url, upl->path);
|
||||
}
|
||||
|
||||
upl->reply->deleteLater();
|
||||
|
@ -414,8 +397,7 @@ void Core::NetworkAccess::onUploadFinished()
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||
|
@ -433,14 +415,13 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
|
|||
}
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
||||
{
|
||||
QString p;
|
||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) {
|
||||
QString p = Shared::squawkifyPath(path);
|
||||
|
||||
try {
|
||||
p = storage.getUrl(path);
|
||||
} catch (const Archive::NotFound& err) {
|
||||
|
||||
p = storage.getUrl(p);
|
||||
} catch (const LMDBAL::NotFound& err) {
|
||||
p = "";
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
@ -448,8 +429,13 @@ QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
|||
return p;
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
|
||||
{
|
||||
void Core::NetworkAccess::uploadFile(
|
||||
const Shared::MessageInfo& info,
|
||||
const QString& path,
|
||||
const QUrl& put,
|
||||
const QUrl& get,
|
||||
const QMap<QString, QString> headers
|
||||
) {
|
||||
QFile* file = new QFile(path);
|
||||
Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
|
||||
QNetworkRequest req(put);
|
||||
|
@ -476,22 +462,18 @@ void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QStr
|
|||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
|
||||
storage.addFile(url, account, jid, id);
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
if (itr != downloads.end())
|
||||
itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
|
||||
storage.addFile(url, path, account, jid, id);
|
||||
}
|
||||
|
||||
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
|
||||
{
|
||||
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path) {
|
||||
for (const std::pair<const QString, Transfer*>& pair : uploads) {
|
||||
Transfer* info = pair.second;
|
||||
if (pair.second->path == path) {
|
||||
|
@ -513,53 +495,66 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri
|
|||
return false;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||
{
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
path += "/" + QApplication::applicationName();
|
||||
QString Core::NetworkAccess::prepareDirectory(const QString& jid) {
|
||||
QString path = currentPath;
|
||||
QString addition;
|
||||
if (jid.size() > 0) {
|
||||
path += "/" + jid;
|
||||
addition = jid;
|
||||
path += QDir::separator() + jid;
|
||||
}
|
||||
|
||||
QDir location(path);
|
||||
|
||||
if (!location.exists()) {
|
||||
bool res = location.mkpath(path);
|
||||
if (!res) {
|
||||
if (!res)
|
||||
return "";
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
else
|
||||
return "squawk://" + addition;
|
||||
}
|
||||
return path;
|
||||
return "squawk://" + addition;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
|
||||
{
|
||||
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path) {
|
||||
QStringList parts = name.split(".");
|
||||
QString suffix("");
|
||||
QStringList::const_iterator sItr = parts.begin();
|
||||
QString realName = *sItr;
|
||||
++sItr;
|
||||
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
||||
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr)
|
||||
suffix += "." + (*sItr);
|
||||
}
|
||||
|
||||
QString postfix("");
|
||||
QFileInfo proposedName(path + "/" + realName + suffix);
|
||||
QString resolvedPath = Shared::resolvePath(path);
|
||||
QString count("");
|
||||
QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(path + "/" + realName + count + suffix);
|
||||
count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||
}
|
||||
|
||||
return proposedName.absoluteFilePath();
|
||||
|
||||
return path + QDir::separator() + realName + count + suffix;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id) {
|
||||
return storage.addMessageAndCheckForPath(url, account, jid, id);
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path) {
|
||||
return storage.deletedFile(path);
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) {
|
||||
QDir dir(currentPath);
|
||||
bool success = true;
|
||||
qDebug() << "moving" << currentPath << "to" << newPath;
|
||||
for (QString fileName : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
||||
success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
|
||||
|
||||
if (!success)
|
||||
qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
|
||||
|
||||
currentPath = newPath;
|
||||
}
|
|
@ -16,8 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_NETWORKACCESS_H
|
||||
#define CORE_NETWORKACCESS_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
|
@ -26,20 +25,17 @@
|
|||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <shared/pathcheck.h>
|
||||
#include "urlstorage.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
|
||||
//TODO Need to describe how to get rid of records when file is no longer reachable;
|
||||
class NetworkAccess : public QObject
|
||||
{
|
||||
class NetworkAccess : public QObject {
|
||||
Q_OBJECT
|
||||
struct Transfer;
|
||||
public:
|
||||
|
@ -65,6 +61,7 @@ public slots:
|
|||
void downladFile(const QString& url);
|
||||
void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||
void moveFilesDirectory(const QString& newPath);
|
||||
|
||||
private:
|
||||
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
|
@ -87,6 +84,7 @@ private:
|
|||
UrlStorage storage;
|
||||
std::map<QString, Transfer*> downloads;
|
||||
std::map<QString, Transfer*> uploads;
|
||||
QString currentPath;
|
||||
|
||||
struct Transfer {
|
||||
std::list<Shared::MessageInfo> messages;
|
||||
|
@ -100,5 +98,3 @@ private:
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_NETWORKACCESS_H
|
252
core/components/urlstorage.cpp
Normal file
252
core/components/urlstorage.cpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
|
||||
#include "urlstorage.h"
|
||||
|
||||
Core::UrlStorage::UrlStorage(const QString& p_name):
|
||||
base(p_name),
|
||||
urlToInfo(base.addStorage<QString, UrlInfo>("urlToInfo")),
|
||||
pathToUrl(base.addStorage<QString, QString>("pathToUrl"))
|
||||
{}
|
||||
|
||||
Core::UrlStorage::~UrlStorage() {
|
||||
close();
|
||||
}
|
||||
|
||||
void Core::UrlStorage::open() {
|
||||
base.open();
|
||||
}
|
||||
|
||||
void Core::UrlStorage::close() {
|
||||
base.close();
|
||||
}
|
||||
|
||||
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite) {
|
||||
LMDBAL::WriteTransaction txn = base.beginTransaction();
|
||||
writeInfo(key, info, txn, overwrite);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, const LMDBAL::WriteTransaction& txn, bool overwrite) {
|
||||
if (overwrite)
|
||||
urlToInfo->forceRecord(key, info, txn);
|
||||
else
|
||||
urlToInfo->addRecord(key, info, txn);
|
||||
|
||||
if (info.hasPath())
|
||||
pathToUrl->forceRecord(info.getPath(), key, txn);
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url) {
|
||||
addToInfo(url, "", "", "");
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url, const QString& path) {
|
||||
addToInfo(url, "", "", "", path);
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
|
||||
addToInfo(url, account, jid, id);
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
|
||||
addToInfo(url, account, jid, id, path);
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
|
||||
UrlInfo info (path, msgs);
|
||||
writeInfo(url, info, true);
|
||||
}
|
||||
|
||||
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id){
|
||||
return addToInfo(url, account, jid, id).getPath();
|
||||
}
|
||||
|
||||
Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
|
||||
const QString& url,
|
||||
const QString& account,
|
||||
const QString& jid,
|
||||
const QString& id,
|
||||
const QString& path
|
||||
) {
|
||||
UrlInfo info;
|
||||
LMDBAL::WriteTransaction txn = base.beginTransaction();
|
||||
|
||||
try {
|
||||
urlToInfo->getRecord(url, info, txn);
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
bool pathChange = false;
|
||||
bool listChange = false;
|
||||
if (path != "-s") {
|
||||
if (info.getPath() != path) {
|
||||
info.setPath(path);
|
||||
pathChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (account.size() > 0 && jid.size() > 0 && id.size() > 0)
|
||||
listChange = info.addMessage(account, jid, id);
|
||||
|
||||
if (pathChange || listChange) {
|
||||
writeInfo(url, info, txn, true);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path) {
|
||||
std::list<Shared::MessageInfo> list;
|
||||
LMDBAL::WriteTransaction txn = base.beginTransaction();
|
||||
UrlInfo info;
|
||||
|
||||
try {
|
||||
urlToInfo->getRecord(url, info, txn);
|
||||
info.getMessages(list);
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
info.setPath(path);
|
||||
writeInfo(url, info, txn, true);
|
||||
txn.commit();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url) {
|
||||
std::list<Shared::MessageInfo> list;
|
||||
LMDBAL::WriteTransaction txn = base.beginTransaction();
|
||||
UrlInfo info;
|
||||
|
||||
urlToInfo->getRecord(url, info, txn);
|
||||
urlToInfo->removeRecord(url, txn);
|
||||
info.getMessages(list);
|
||||
|
||||
if (info.hasPath())
|
||||
pathToUrl->removeRecord(info.getPath(), txn);
|
||||
|
||||
txn.commit();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path) {
|
||||
std::list<Shared::MessageInfo> list;
|
||||
LMDBAL::WriteTransaction txn = base.beginTransaction();
|
||||
|
||||
QString url = pathToUrl->getRecord(path, txn);
|
||||
pathToUrl->removeRecord(path, txn);
|
||||
|
||||
UrlInfo info = urlToInfo->getRecord(url, txn);
|
||||
info.getMessages(list);
|
||||
info.setPath(QString());
|
||||
urlToInfo->changeRecord(url, info, txn);
|
||||
|
||||
txn.commit();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QString Core::UrlStorage::getUrl(const QString& path) {
|
||||
return pathToUrl->getRecord(path);
|
||||
}
|
||||
|
||||
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url) {
|
||||
UrlInfo info = urlToInfo->getRecord(url);
|
||||
std::list<Shared::MessageInfo> container;
|
||||
info.getMessages(container);
|
||||
return std::make_pair(info.getPath(), container);
|
||||
}
|
||||
|
||||
Core::UrlStorage::UrlInfo::UrlInfo():
|
||||
localPath(),
|
||||
messages() {}
|
||||
|
||||
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
|
||||
localPath(path),
|
||||
messages() {}
|
||||
|
||||
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
|
||||
localPath(path),
|
||||
messages(msgs) {}
|
||||
|
||||
Core::UrlStorage::UrlInfo::~UrlInfo() {}
|
||||
|
||||
bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id) {
|
||||
for (const Shared::MessageInfo& info : messages) {
|
||||
if (info.account == acc && info.jid == jid && info.messageId == id)
|
||||
return false;
|
||||
}
|
||||
messages.emplace_back(acc, jid, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const {
|
||||
data << localPath;
|
||||
std::list<Shared::MessageInfo>::size_type size = messages.size();
|
||||
data << quint32(size);
|
||||
for (const Shared::MessageInfo& info : messages) {
|
||||
data << info.account;
|
||||
data << info.jid;
|
||||
data << info.messageId;
|
||||
}
|
||||
}
|
||||
|
||||
QDataStream & operator << (QDataStream& in, const Core::UrlStorage::UrlInfo& info) {
|
||||
info.serialize(in);
|
||||
return in;
|
||||
}
|
||||
|
||||
QDataStream & operator >> (QDataStream& out, Core::UrlStorage::UrlInfo& info) {
|
||||
info.deserialize(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data) {
|
||||
data >> localPath;
|
||||
quint32 size;
|
||||
data >> size;
|
||||
for (quint32 i = 0; i < size; ++i) {
|
||||
messages.emplace_back();
|
||||
Shared::MessageInfo& info = messages.back();
|
||||
data >> info.account;
|
||||
data >> info.jid;
|
||||
data >> info.messageId;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const {
|
||||
for (const Shared::MessageInfo& info : messages)
|
||||
container.emplace_back(info);
|
||||
}
|
||||
|
||||
QString Core::UrlStorage::UrlInfo::getPath() const {
|
||||
return localPath;
|
||||
}
|
||||
|
||||
bool Core::UrlStorage::UrlInfo::hasPath() const {
|
||||
return localPath.size() > 0;
|
||||
}
|
||||
|
||||
void Core::UrlStorage::UrlInfo::setPath(const QString& path) {
|
||||
localPath = path;
|
||||
}
|
|
@ -16,25 +16,23 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_URLSTORAGE_H
|
||||
#define CORE_URLSTORAGE_H
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
#include <lmdb.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "archive.h"
|
||||
#include <storage.h>
|
||||
|
||||
#include <shared/messageinfo.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class UrlStorage
|
||||
{
|
||||
class UrlStorage {
|
||||
public:
|
||||
class UrlInfo;
|
||||
|
||||
public:
|
||||
UrlStorage(const QString& name);
|
||||
~UrlStorage();
|
||||
|
@ -55,20 +53,16 @@ public:
|
|||
std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
|
||||
|
||||
private:
|
||||
QString name;
|
||||
bool opened;
|
||||
MDB_env* environment;
|
||||
MDB_dbi base;
|
||||
MDB_dbi map;
|
||||
LMDBAL::Base base;
|
||||
LMDBAL::Storage<QString, UrlInfo>* urlToInfo;
|
||||
LMDBAL::Storage<QString, QString>* pathToUrl;
|
||||
|
||||
private:
|
||||
void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
|
||||
void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
|
||||
void readInfo(const QString& key, UrlInfo& info);
|
||||
void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn);
|
||||
void writeInfo(const QString& key, const UrlInfo& info, const LMDBAL::WriteTransaction& txn, bool overwrite = false);
|
||||
UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
|
||||
|
||||
private:
|
||||
public:
|
||||
class UrlInfo {
|
||||
public:
|
||||
UrlInfo(const QString& path);
|
||||
|
@ -96,4 +90,5 @@ private:
|
|||
|
||||
}
|
||||
|
||||
#endif // CORE_URLSTORAGE_H
|
||||
QDataStream& operator >> (QDataStream &in, Core::UrlStorage::UrlInfo& info);
|
||||
QDataStream& operator << (QDataStream &out, const Core::UrlStorage::UrlInfo& info);
|
|
@ -42,57 +42,48 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
|
|||
connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError);
|
||||
|
||||
room->setNickName(nick);
|
||||
if (autoJoin) {
|
||||
if (autoJoin)
|
||||
room->join();
|
||||
}
|
||||
|
||||
archive->readAllResourcesAvatars(exParticipants);
|
||||
}
|
||||
|
||||
Core::Conference::~Conference()
|
||||
{
|
||||
if (joined) {
|
||||
Core::Conference::~Conference(){
|
||||
if (joined)
|
||||
room->leave();
|
||||
}
|
||||
|
||||
room->deleteLater();
|
||||
}
|
||||
|
||||
QString Core::Conference::getNick() const
|
||||
{
|
||||
QString Core::Conference::getNick() const {
|
||||
return nick;
|
||||
}
|
||||
|
||||
bool Core::Conference::getAutoJoin()
|
||||
{
|
||||
bool Core::Conference::getAutoJoin() const {
|
||||
return autoJoin;
|
||||
}
|
||||
|
||||
bool Core::Conference::getJoined() const
|
||||
{
|
||||
bool Core::Conference::getJoined() const {
|
||||
return joined;
|
||||
}
|
||||
|
||||
void Core::Conference::setJoined(bool p_joined)
|
||||
{
|
||||
void Core::Conference::setJoined(bool p_joined) {
|
||||
if (joined != p_joined) {
|
||||
if (p_joined) {
|
||||
if (p_joined)
|
||||
room->join();
|
||||
} else {
|
||||
else
|
||||
room->leave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Conference::setAutoJoin(bool p_autoJoin)
|
||||
{
|
||||
void Core::Conference::setAutoJoin(bool p_autoJoin) {
|
||||
if (autoJoin != p_autoJoin) {
|
||||
autoJoin = p_autoJoin;
|
||||
emit autoJoinChanged(autoJoin);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Conference::setNick(const QString& p_nick)
|
||||
{
|
||||
void Core::Conference::setNick(const QString& p_nick) {
|
||||
if (nick != p_nick) {
|
||||
if (joined) {
|
||||
room->setNickName(p_nick);
|
||||
|
@ -103,105 +94,99 @@ void Core::Conference::setNick(const QString& p_nick)
|
|||
}
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomJoined()
|
||||
{
|
||||
void Core::Conference::onRoomJoined() {
|
||||
joined = true;
|
||||
emit joinedChanged(joined);
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomLeft()
|
||||
{
|
||||
void Core::Conference::onRoomLeft() {
|
||||
joined = false;
|
||||
emit joinedChanged(joined);
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomNameChanged(const QString& p_name)
|
||||
{
|
||||
void Core::Conference::onRoomNameChanged(const QString& p_name) {
|
||||
setName(p_name);
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomNickNameChanged(const QString& p_nick)
|
||||
{
|
||||
void Core::Conference::onRoomNickNameChanged(const QString& p_nick) {
|
||||
if (p_nick != nick) {
|
||||
nick = p_nick;
|
||||
emit nickChanged(nick);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomError(const QXmppStanza::Error& err)
|
||||
{
|
||||
void Core::Conference::onRoomError(const QXmppStanza::Error& err) {
|
||||
qDebug() << "MUC" << jid << "error:" << err.text();
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomParticipantAdded(const QString& p_name)
|
||||
{
|
||||
void Core::Conference::onRoomParticipantAdded(const QString& p_name) {
|
||||
QStringList comps = p_name.split("/");
|
||||
QString resource = comps.back();
|
||||
QXmppPresence pres = room->participantPresence(p_name);
|
||||
QXmppMucItem mi = pres.mucItem();
|
||||
if (resource == jid) {
|
||||
if (resource == jid)
|
||||
resource = "";
|
||||
}
|
||||
|
||||
std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource);
|
||||
bool hasAvatar = itr != exParticipants.end();
|
||||
|
||||
if (resource.size() > 0) {
|
||||
QDateTime lastInteraction = pres.lastUserInteraction();
|
||||
if (!lastInteraction.isValid()) {
|
||||
if (!lastInteraction.isValid())
|
||||
lastInteraction = QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"lastActivity", lastInteraction},
|
||||
{"availability", pres.availableStatusType()},
|
||||
{"status", pres.statusText()},
|
||||
{"affiliation", mi.affiliation()},
|
||||
{"role", mi.role()}
|
||||
};
|
||||
|
||||
if (hasAvatar) {
|
||||
if (itr->second.autogenerated) {
|
||||
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
|
||||
} else {
|
||||
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
|
||||
{"role", mi.role()},
|
||||
{"client", QVariant::fromValue(
|
||||
Shared::ClientId(
|
||||
pres.capabilityNode(),
|
||||
pres.capabilityVer().toBase64(),
|
||||
pres.capabilityHash())
|
||||
)
|
||||
}
|
||||
cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type);
|
||||
} else {
|
||||
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
|
||||
cData.insert("avatarPath", "");
|
||||
requestVCard(p_name);
|
||||
}
|
||||
};
|
||||
careAboutAvatar(hasAvatar, itr->second, cData, resource, p_name);
|
||||
|
||||
emit addParticipant(resource, cData);
|
||||
|
||||
if (!hasAvatar) // because this way vCard is already requested, no need to handle possible avatar update
|
||||
return;
|
||||
}
|
||||
|
||||
handlePossibleAvatarUpdate(pres, resource, hasAvatar, itr->second);
|
||||
}
|
||||
|
||||
void Core::Conference::handlePossibleAvatarUpdate (
|
||||
const QXmppPresence& pres,
|
||||
const QString& resource,
|
||||
bool hasAvatar,
|
||||
const Archive::AvatarInfo& info
|
||||
) {
|
||||
switch (pres.vCardUpdateType()) {
|
||||
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
|
||||
if (!hasAvatar || !itr->second.autogenerated) {
|
||||
case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
|
||||
if (!hasAvatar || !info.autogenerated)
|
||||
setAutoGeneratedAvatar(resource);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
|
||||
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
|
||||
if (hasAvatar) {
|
||||
if (itr->second.autogenerated || itr->second.hash != pres.photoHash()) {
|
||||
emit requestVCard(p_name);
|
||||
}
|
||||
if (info.autogenerated || info.hash != pres.photoHash())
|
||||
emit requestVCard(pres.from());
|
||||
} else {
|
||||
emit requestVCard(p_name);
|
||||
emit requestVCard(pres.from());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomParticipantChanged(const QString& p_name)
|
||||
{
|
||||
void Core::Conference::onRoomParticipantChanged(const QString& p_name) {
|
||||
QStringList comps = p_name.split("/");
|
||||
QString resource = comps.back();
|
||||
QXmppPresence pres = room->participantPresence(p_name);
|
||||
|
@ -209,22 +194,27 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
|
|||
handlePresence(pres);
|
||||
if (resource != jid) {
|
||||
QDateTime lastInteraction = pres.lastUserInteraction();
|
||||
if (!lastInteraction.isValid()) {
|
||||
if (!lastInteraction.isValid())
|
||||
lastInteraction = QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
|
||||
emit changeParticipant(resource, {
|
||||
{"lastActivity", lastInteraction},
|
||||
{"availability", pres.availableStatusType()},
|
||||
{"status", pres.statusText()},
|
||||
{"affiliation", mi.affiliation()},
|
||||
{"role", mi.role()}
|
||||
{"role", mi.role()},
|
||||
{"client", QVariant::fromValue(
|
||||
Shared::ClientId(
|
||||
pres.capabilityNode(),
|
||||
pres.capabilityVer().toBase64(),
|
||||
pres.capabilityHash())
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomParticipantRemoved(const QString& p_name)
|
||||
{
|
||||
void Core::Conference::onRoomParticipantRemoved(const QString& p_name) {
|
||||
QStringList comps = p_name.split("/");
|
||||
QString resource = comps.back();
|
||||
if (resource == jid) {
|
||||
|
@ -234,69 +224,40 @@ void Core::Conference::onRoomParticipantRemoved(const QString& p_name)
|
|||
}
|
||||
}
|
||||
|
||||
QString Core::Conference::getSubject() const
|
||||
{
|
||||
if (joined) {
|
||||
QString Core::Conference::getSubject() const {
|
||||
if (joined)
|
||||
return room->subject();
|
||||
} else {
|
||||
else
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Conference::onRoomSubjectChanged(const QString& p_name)
|
||||
{
|
||||
void Core::Conference::onRoomSubjectChanged(const QString& p_name) {
|
||||
emit subjectChanged(p_name);
|
||||
}
|
||||
|
||||
void Core::Conference::handlePresence(const QXmppPresence& pres)
|
||||
{
|
||||
void Core::Conference::handlePresence(const QXmppPresence& pres) {
|
||||
QString id = pres.from();
|
||||
QStringList comps = id.split("/");
|
||||
QString jid = comps.front();
|
||||
QString resource("");
|
||||
if (comps.size() > 1) {
|
||||
if (comps.size() > 1)
|
||||
resource = comps.back();
|
||||
}
|
||||
|
||||
switch (pres.vCardUpdateType()) {
|
||||
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
|
||||
Archive::AvatarInfo info;
|
||||
bool hasAvatar = readAvatarInfo(info, resource);
|
||||
if (!hasAvatar || !info.autogenerated) {
|
||||
setAutoGeneratedAvatar(resource);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
|
||||
Archive::AvatarInfo info;
|
||||
bool hasAvatar = readAvatarInfo(info, resource);
|
||||
if (hasAvatar) {
|
||||
if (info.autogenerated || info.hash != pres.photoHash()) {
|
||||
emit requestVCard(id);
|
||||
}
|
||||
} else {
|
||||
emit requestVCard(id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Archive::AvatarInfo info;
|
||||
bool hasAvatar = readAvatarInfo(info, resource);
|
||||
handlePossibleAvatarUpdate(pres, resource, hasAvatar, info);
|
||||
}
|
||||
|
||||
bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
|
||||
{
|
||||
bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) {
|
||||
Archive::AvatarInfo newInfo;
|
||||
bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource);
|
||||
if (result && resource.size() != 0) {
|
||||
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
|
||||
if (itr == exParticipants.end()) {
|
||||
if (itr == exParticipants.end())
|
||||
exParticipants.insert(std::make_pair(resource, newInfo));
|
||||
} else {
|
||||
else
|
||||
itr->second = newInfo;
|
||||
}
|
||||
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
|
||||
{"avatarPath", avatarPath(resource) + "." + newInfo.type}
|
||||
|
@ -306,17 +267,15 @@ bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
|
||||
{
|
||||
bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) {
|
||||
bool result = RosterItem::setAvatar(data, info, resource);
|
||||
if (result && resource.size() != 0) {
|
||||
if (data.size() > 0) {
|
||||
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
|
||||
if (itr == exParticipants.end()) {
|
||||
if (itr == exParticipants.end())
|
||||
exParticipants.insert(std::make_pair(resource, info));
|
||||
} else {
|
||||
else
|
||||
itr->second = info;
|
||||
}
|
||||
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
|
||||
|
@ -324,9 +283,8 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
|
|||
});
|
||||
} else {
|
||||
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
|
||||
if (itr != exParticipants.end()) {
|
||||
if (itr != exParticipants.end())
|
||||
exParticipants.erase(itr);
|
||||
}
|
||||
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(Shared::Avatar::empty)},
|
||||
|
@ -339,25 +297,31 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
|
|||
return result;
|
||||
}
|
||||
|
||||
Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource)
|
||||
{
|
||||
Shared::VCard result = RosterItem::handleResponseVCard(card, resource);
|
||||
|
||||
if (resource.size() > 0) {
|
||||
void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource, Shared::VCard& out) {
|
||||
RosterItem::handleResponseVCard(card, resource, out);
|
||||
if (resource.size() > 0)
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(result.getAvatarType())},
|
||||
{"avatarPath", result.getAvatarPath()}
|
||||
{"avatarState", static_cast<uint>(out.getAvatarType())},
|
||||
{"avatarPath", out.getAvatarPath()}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QMap<QString, QVariant> Core::Conference::getAllAvatars() const {
|
||||
QMap<QString, QVariant> result;
|
||||
for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants)
|
||||
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QMap<QString, QVariant> Core::Conference::getAllAvatars() const
|
||||
{
|
||||
QMap<QString, QVariant> result;
|
||||
for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants) {
|
||||
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
|
||||
}
|
||||
return result;
|
||||
QMap<QString, QVariant> Core::Conference::getInfo() const {
|
||||
QMap<QString, QVariant> data = RosterItem::getInfo();
|
||||
|
||||
data.insert("autoJoin", getAutoJoin());
|
||||
data.insert("joined", getJoined());
|
||||
data.insert("nick", getNick());
|
||||
data.insert("avatars", getAllAvatars());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_CONFERENCE_H
|
||||
#define CORE_CONFERENCE_H
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
|
||||
|
@ -26,16 +25,11 @@
|
|||
#include <set>
|
||||
|
||||
#include "rosteritem.h"
|
||||
#include "shared/global.h"
|
||||
#include <shared/global.h>
|
||||
#include <shared/clientid.h>
|
||||
|
||||
namespace Core
|
||||
{
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class Conference : public RosterItem
|
||||
{
|
||||
namespace Core {
|
||||
class Conference : public RosterItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Conference(const QString& p_jid, const QString& p_account, bool p_autoJoin, const QString& p_name, const QString& p_nick, QXmppMucRoom* p_room);
|
||||
|
@ -48,12 +42,13 @@ public:
|
|||
bool getJoined() const;
|
||||
void setJoined(bool p_joined);
|
||||
|
||||
bool getAutoJoin();
|
||||
bool getAutoJoin() const;
|
||||
void setAutoJoin(bool p_autoJoin);
|
||||
void handlePresence(const QXmppPresence & pres) override;
|
||||
bool setAutoGeneratedAvatar(const QString& resource = "") override;
|
||||
Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override;
|
||||
void handleResponseVCard(const QXmppVCardIq & card, const QString &resource, Shared::VCard& out) override;
|
||||
QMap<QString, QVariant> getAllAvatars() const;
|
||||
QMap<QString, QVariant> getInfo() const override;
|
||||
|
||||
signals:
|
||||
void nickChanged(const QString& nick);
|
||||
|
@ -67,6 +62,14 @@ signals:
|
|||
protected:
|
||||
bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override;
|
||||
|
||||
private:
|
||||
void handlePossibleAvatarUpdate(
|
||||
const QXmppPresence& pres,
|
||||
const QString& resource,
|
||||
bool hasAvatar,
|
||||
const Archive::AvatarInfo& info
|
||||
);
|
||||
|
||||
private:
|
||||
QString nick;
|
||||
QXmppMucRoom* room;
|
||||
|
@ -89,5 +92,3 @@ private slots:
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_CONFERENCE_H
|
||||
|
|
|
@ -22,55 +22,49 @@
|
|||
Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent):
|
||||
RosterItem(pJid, account, parent),
|
||||
groups(),
|
||||
subscriptionState(Shared::SubscriptionState::unknown)
|
||||
{
|
||||
}
|
||||
subscriptionState(Shared::SubscriptionState::unknown),
|
||||
pep(Shared::Support::unknown)
|
||||
|
||||
Core::Contact::~Contact()
|
||||
{
|
||||
}
|
||||
#ifdef WITH_OMEMO
|
||||
,omemoBundles(Shared::Possible::unknown)
|
||||
#endif
|
||||
{}
|
||||
|
||||
QSet<QString> Core::Contact::getGroups() const
|
||||
{
|
||||
Core::Contact::~Contact() {}
|
||||
|
||||
QSet<QString> Core::Contact::getGroups() const {
|
||||
return groups;
|
||||
}
|
||||
|
||||
unsigned int Core::Contact::groupsCount() const
|
||||
{
|
||||
unsigned int Core::Contact::groupsCount() const {
|
||||
return groups.size();
|
||||
}
|
||||
|
||||
void Core::Contact::setGroups(const QSet<QString>& set)
|
||||
{
|
||||
void Core::Contact::setGroups(const QSet<QString>& set) {
|
||||
QSet<QString> toRemove = groups - set;
|
||||
QSet<QString> toAdd = set - groups;
|
||||
|
||||
groups = set;
|
||||
|
||||
for (QSet<QString>::iterator itr = toRemove.begin(), end = toRemove.end(); itr != end; ++itr) {
|
||||
emit groupRemoved(*itr);
|
||||
}
|
||||
for (const QString& group : toRemove)
|
||||
emit groupRemoved(group);
|
||||
|
||||
for (QSet<QString>::iterator itr = toAdd.begin(), end = toAdd.end(); itr != end; ++itr) {
|
||||
emit groupAdded(*itr);
|
||||
}
|
||||
for (const QString& group : toAdd)
|
||||
emit groupAdded(group);
|
||||
}
|
||||
|
||||
Shared::SubscriptionState Core::Contact::getSubscriptionState() const
|
||||
{
|
||||
Shared::SubscriptionState Core::Contact::getSubscriptionState() const {
|
||||
return subscriptionState;
|
||||
}
|
||||
|
||||
void Core::Contact::setSubscriptionState(Shared::SubscriptionState state)
|
||||
{
|
||||
void Core::Contact::setSubscriptionState(Shared::SubscriptionState state) {
|
||||
if (subscriptionState != state) {
|
||||
subscriptionState = state;
|
||||
emit subscriptionStateChanged(subscriptionState);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Contact::handlePresence(const QXmppPresence& pres)
|
||||
{
|
||||
void Core::Contact::handlePresence(const QXmppPresence& pres) {
|
||||
switch (pres.vCardUpdateType()) {
|
||||
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
|
||||
break;
|
||||
|
@ -79,18 +73,17 @@ void Core::Contact::handlePresence(const QXmppPresence& pres)
|
|||
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
|
||||
Archive::AvatarInfo info;
|
||||
bool hasAvatar = readAvatarInfo(info);
|
||||
if (!hasAvatar || !info.autogenerated) {
|
||||
if (!hasAvatar || !info.autogenerated)
|
||||
setAutoGeneratedAvatar();
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
|
||||
Archive::AvatarInfo info;
|
||||
bool hasAvatar = readAvatarInfo(info);
|
||||
if (hasAvatar) {
|
||||
if (info.autogenerated || info.hash != pres.photoHash()) {
|
||||
if (info.autogenerated || info.hash != pres.photoHash())
|
||||
emit requestVCard(jid);
|
||||
}
|
||||
} else {
|
||||
emit requestVCard(jid);
|
||||
}
|
||||
|
@ -98,3 +91,23 @@ void Core::Contact::handlePresence(const QXmppPresence& pres)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Contact::setPepSupport(Shared::Support support) {
|
||||
if (pep != support)
|
||||
pep = support;
|
||||
}
|
||||
|
||||
Shared::Support Core::Contact::getPepSupport() const {
|
||||
return pep;
|
||||
}
|
||||
|
||||
QMap<QString, QVariant> Core::Contact::getInfo() const {
|
||||
QMap<QString, QVariant> data = RosterItem::getInfo();
|
||||
|
||||
data.insert("state", QVariant::fromValue(subscriptionState));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -16,17 +16,18 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_CONTACT_H
|
||||
#define CORE_CONTACT_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include "rosteritem.h"
|
||||
|
||||
#include <shared/enums.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Contact : public RosterItem
|
||||
{
|
||||
class Contact : public RosterItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Contact(const QString& pJid, const QString& account, QObject* parent = 0);
|
||||
|
@ -38,7 +39,11 @@ public:
|
|||
|
||||
void setSubscriptionState(Shared::SubscriptionState state);
|
||||
Shared::SubscriptionState getSubscriptionState() const;
|
||||
void setPepSupport(Shared::Support support);
|
||||
Shared::Support getPepSupport() const;
|
||||
|
||||
void handlePresence(const QXmppPresence & pres) override;
|
||||
QMap<QString, QVariant> getInfo() const override;
|
||||
|
||||
signals:
|
||||
void groupAdded(const QString& name);
|
||||
|
@ -48,7 +53,11 @@ signals:
|
|||
private:
|
||||
QSet<QString> groups;
|
||||
Shared::SubscriptionState subscriptionState;
|
||||
Shared::Support pep;
|
||||
|
||||
#ifdef WITH_OMEMO
|
||||
public:
|
||||
Shared::Possible omemoBundles;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#endif // CORE_CONTACT_H
|
||||
|
|
26
core/delayManager/CMakeLists.txt
Normal file
26
core/delayManager/CMakeLists.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
set(SOURCE_FILES
|
||||
manager.cpp
|
||||
job.cpp
|
||||
cardinternal.cpp
|
||||
infoforuser.cpp
|
||||
owncardinternal.cpp
|
||||
owninfoforuser.cpp
|
||||
contact.cpp
|
||||
info.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
manager.h
|
||||
job.h
|
||||
cardinternal.h
|
||||
infoforuser.h
|
||||
owncardinternal.h
|
||||
owninfoforuser.h
|
||||
contact.h
|
||||
info.h
|
||||
)
|
||||
|
||||
target_sources(squawk PRIVATE
|
||||
${SOURCE_FILES}
|
||||
${HEADER_FILES}
|
||||
)
|
30
core/delayManager/cardinternal.cpp
Normal file
30
core/delayManager/cardinternal.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "cardinternal.h"
|
||||
|
||||
Core::DelayManager::CardInternal::CardInternal(Id p_id, const QString& p_jid) :
|
||||
Job(p_id, Type::cardInternal),
|
||||
Contact(p_id, p_jid, Type::cardInternal)
|
||||
{}
|
||||
|
||||
|
||||
Core::DelayManager::CardInternal::CardInternal(const CardInternal& other) :
|
||||
Job(other),
|
||||
Contact(other)
|
||||
{}
|
35
core/delayManager/cardinternal.h
Normal file
35
core/delayManager/cardinternal.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "contact.h"
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class CardInternal : public Contact {
|
||||
public:
|
||||
CardInternal(Id id, const QString& jid);
|
||||
CardInternal(const CardInternal& other);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
28
core/delayManager/contact.cpp
Normal file
28
core/delayManager/contact.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "contact.h"
|
||||
|
||||
Core::DelayManager::Contact::Contact(const Contact& other):
|
||||
Job(other),
|
||||
jid(other.jid) {}
|
||||
|
||||
|
||||
Core::DelayManager::Contact::Contact(Id p_id, const QString& p_jid, Type p_type):
|
||||
Job(p_id, p_type),
|
||||
jid(p_jid) {}
|
38
core/delayManager/contact.h
Normal file
38
core/delayManager/contact.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "job.h"
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class Contact : public virtual Job {
|
||||
protected:
|
||||
Contact(Id id, const QString& jid, Type type);
|
||||
Contact(const Contact& other);
|
||||
|
||||
public:
|
||||
const QString jid;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
61
core/delayManager/info.cpp
Normal file
61
core/delayManager/info.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "info.h"
|
||||
|
||||
Core::DelayManager::Info::Info(Id p_id, Type p_type) :
|
||||
Job(p_id, p_type),
|
||||
stage(Stage::waitingForVCard),
|
||||
info(nullptr)
|
||||
{}
|
||||
|
||||
Core::DelayManager::Info::Info(const Info& other) :
|
||||
Job(other),
|
||||
stage(other.stage),
|
||||
info(nullptr)
|
||||
{}
|
||||
|
||||
Core::DelayManager::Info::~Info() {
|
||||
if (stage == Stage::waitingForBundles) {
|
||||
delete info;
|
||||
}
|
||||
}
|
||||
|
||||
Core::DelayManager::Info::Stage Core::DelayManager::Info::getStage() const {
|
||||
return stage;
|
||||
}
|
||||
|
||||
void Core::DelayManager::Info::receivedVCard(const Shared::VCard& card) {
|
||||
if (stage != Stage::waitingForVCard)
|
||||
throw 245;
|
||||
|
||||
info = new Shared::VCard(card);
|
||||
|
||||
#ifdef WITH_OMEMO
|
||||
stage = Stage::waitingForBundles;
|
||||
#endif
|
||||
}
|
||||
|
||||
Shared::VCard * Core::DelayManager::Info::claim() {
|
||||
if (stage != Stage::waitingForBundles)
|
||||
throw 246;
|
||||
|
||||
Shared::VCard* res = info;
|
||||
info = nullptr;
|
||||
return res;
|
||||
}
|
55
core/delayManager/info.h
Normal file
55
core/delayManager/info.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "job.h"
|
||||
|
||||
#include <shared/vcard.h>
|
||||
#include <shared/info.h>
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class Info : public virtual Job {
|
||||
public:
|
||||
enum class Stage {
|
||||
waitingForVCard,
|
||||
waitingForBundles,
|
||||
finished
|
||||
};
|
||||
|
||||
protected:
|
||||
Info(Id id, Type type);
|
||||
Info(const Info& other);
|
||||
|
||||
public:
|
||||
~Info();
|
||||
|
||||
void receivedVCard(const Shared::VCard& card);
|
||||
Shared::VCard* claim();
|
||||
Stage getStage() const;
|
||||
|
||||
|
||||
private:
|
||||
Stage stage;
|
||||
Shared::VCard* info;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
31
core/delayManager/infoforuser.cpp
Normal file
31
core/delayManager/infoforuser.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "infoforuser.h"
|
||||
|
||||
Core::DelayManager::InfoForUser::InfoForUser(Id p_id, const QString& p_jid) :
|
||||
Job(p_id, Type::infoForUser),
|
||||
Contact(p_id, p_jid, Type::infoForUser),
|
||||
Info(p_id, Type::infoForUser)
|
||||
{}
|
||||
|
||||
Core::DelayManager::InfoForUser::InfoForUser(const InfoForUser& other) :
|
||||
Job(other),
|
||||
Contact(other),
|
||||
Info(other)
|
||||
{}
|
34
core/delayManager/infoforuser.h
Normal file
34
core/delayManager/infoforuser.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "contact.h"
|
||||
#include "info.h"
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class InfoForUser : public Contact, public Info {
|
||||
public:
|
||||
InfoForUser(Id id, const QString& jid);
|
||||
InfoForUser(const InfoForUser& other);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
30
core/delayManager/job.cpp
Normal file
30
core/delayManager/job.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "job.h"
|
||||
|
||||
Core::DelayManager::Job::Job(Id p_id, Type p_type) :
|
||||
id (p_id),
|
||||
type (p_type) {}
|
||||
|
||||
|
||||
Core::DelayManager::Job::Job(const Job& other) :
|
||||
id(other.id),
|
||||
type(other.type) {}
|
||||
|
||||
Core::DelayManager::Job::~Job() {}
|
54
core/delayManager/job.h
Normal file
54
core/delayManager/job.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class Job {
|
||||
public:
|
||||
typedef uint16_t Id;
|
||||
|
||||
enum class Type {
|
||||
cardInternal,
|
||||
ownCardInternal,
|
||||
infoForUser,
|
||||
ownInfoForUser
|
||||
};
|
||||
inline static constexpr const char * const TypeString[] = {
|
||||
"cardInternal",
|
||||
"ownCardInternal",
|
||||
"infoForUser",
|
||||
"ownInfoForUser"
|
||||
};
|
||||
protected:
|
||||
Job(Id id, Type type);
|
||||
Job(const Job& other);
|
||||
|
||||
public:
|
||||
virtual ~Job();
|
||||
|
||||
const Id id;
|
||||
const Type type;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
439
core/delayManager/manager.cpp
Normal file
439
core/delayManager/manager.cpp
Normal file
|
@ -0,0 +1,439 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "cardinternal.h"
|
||||
#include "infoforuser.h"
|
||||
#include "owncardinternal.h"
|
||||
#include "owninfoforuser.h"
|
||||
|
||||
Core::DelayManager::Manager::Manager(const QString& poj, Job::Id mpj, QObject* parent) :
|
||||
QObject(parent),
|
||||
maxParallelJobs(mpj),
|
||||
nextJobId(1),
|
||||
scheduledJobs(),
|
||||
scheduledJobsById(scheduledJobs.get<id>()),
|
||||
jobSequence(scheduledJobs.get<sequence>()),
|
||||
runningJobs(),
|
||||
ownVCardJobId(0),
|
||||
ownInfoJobId(0),
|
||||
scheduledVCards(),
|
||||
requestedVCards(),
|
||||
#ifdef WITH_OMEMO
|
||||
requestedBundles(),
|
||||
#endif
|
||||
ownJid(poj)
|
||||
{}
|
||||
|
||||
Core::DelayManager::Manager::~Manager() {
|
||||
for (const std::pair<const Job::Id, Job*>& pair : runningJobs)
|
||||
delete pair.second;
|
||||
|
||||
for (Job* job : jobSequence)
|
||||
delete job;
|
||||
}
|
||||
|
||||
Core::DelayManager::Job::Id Core::DelayManager::Manager::getNextJobId() {
|
||||
Job::Id id = nextJobId++;
|
||||
if (id == 0)
|
||||
id = nextJobId++;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::getInfo(const QString& jid) {
|
||||
if (jid == ownJid)
|
||||
return getOwnInfo();
|
||||
|
||||
Job* job = nullptr;
|
||||
#ifdef WITH_OMEMO
|
||||
std::map<QString, Job::Id>::const_iterator bitr = requestedBundles.find(jid);
|
||||
if (bitr != requestedBundles.end()) {
|
||||
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(bitr->second);
|
||||
if (itr == runningJobs.end())
|
||||
throw JobNotFound(bitr->second);
|
||||
|
||||
job = itr->second;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
job = getVCardJob(jid);
|
||||
|
||||
if (job != nullptr) {
|
||||
if (job->type == Job::Type::cardInternal)
|
||||
replaceJob(new InfoForUser(job->id, jid));
|
||||
} else
|
||||
scheduleJob(new InfoForUser(getNextJobId(), jid));
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::getOwnInfo() {
|
||||
if (ownInfoJobId == 0) {
|
||||
if (ownVCardJobId != 0)
|
||||
replaceJob(new OwnInfoForUser(ownVCardJobId));
|
||||
else
|
||||
scheduleJob(new OwnInfoForUser(getNextJobId()));
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::getVCard(const QString& jid) {
|
||||
Job* job = getVCardJob(jid);
|
||||
if (job == nullptr)
|
||||
scheduleJob(new CardInternal(getNextJobId(), jid));
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::getOwnVCard() {
|
||||
if (ownVCardJobId == 0)
|
||||
scheduleJob(new OwnCardInternal(getNextJobId()));
|
||||
}
|
||||
|
||||
bool Core::DelayManager::Manager::isOwnVCardPending() const {
|
||||
return ownVCardJobId != 0;
|
||||
}
|
||||
|
||||
Core::DelayManager::Job* Core::DelayManager::Manager::getVCardJob(const QString& jid) {
|
||||
Job* job = nullptr;
|
||||
std::map<QString, Job::Id>::const_iterator sitr = scheduledVCards.find(jid);
|
||||
if (sitr == scheduledVCards.end()) {
|
||||
std::map<QString, Job::Id>::const_iterator ritr = requestedVCards.find(jid);
|
||||
if (ritr != requestedVCards.end()) {
|
||||
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(ritr->second);
|
||||
if (itr == runningJobs.end())
|
||||
throw JobNotFound(ritr->second, "getVCardJob:1");
|
||||
|
||||
job = itr->second;
|
||||
}
|
||||
} else {
|
||||
StorageById::const_iterator itr = scheduledJobsById.find(sitr->second);
|
||||
if (itr == scheduledJobsById.end())
|
||||
throw JobNotFound(sitr->second, "getVCardJob:2");
|
||||
|
||||
job = *itr;
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
void Core::DelayManager::Manager::preScheduleJob(Job* job) {
|
||||
switch (job->type) {
|
||||
case Job::Type::cardInternal:
|
||||
scheduledVCards.emplace(dynamic_cast<CardInternal*>(job)->jid, job->id);
|
||||
break;
|
||||
case Job::Type::ownCardInternal:
|
||||
ownVCardJobId = job->id;
|
||||
break;
|
||||
case Job::Type::infoForUser:
|
||||
scheduledVCards.emplace(dynamic_cast<InfoForUser*>(job)->jid, job->id);
|
||||
break;
|
||||
case Job::Type::ownInfoForUser:
|
||||
ownVCardJobId = job->id;
|
||||
ownInfoJobId = job->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::scheduleJob(Job* job) {
|
||||
preScheduleJob(job);
|
||||
if (runningJobs.size() < maxParallelJobs)
|
||||
executeJob(job);
|
||||
else
|
||||
scheduledJobs.push_back(job);
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::preExecuteJob(Job* job) {
|
||||
switch (job->type) {
|
||||
case Job::Type::cardInternal:
|
||||
case Job::Type::infoForUser: {
|
||||
Contact* cij = dynamic_cast<Contact*>(job);
|
||||
requestedVCards.emplace(cij->jid, job->id);
|
||||
scheduledVCards.erase(cij->jid);
|
||||
}
|
||||
break;
|
||||
case Job::Type::ownInfoForUser:
|
||||
case Job::Type::ownCardInternal:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::executeJob(Job* job) {
|
||||
preExecuteJob(job);
|
||||
runningJobs.emplace(job->id, job);
|
||||
switch (job->type) {
|
||||
case Job::Type::cardInternal:
|
||||
case Job::Type::infoForUser:
|
||||
emit requestVCard(dynamic_cast<Contact*>(job)->jid);
|
||||
break;
|
||||
case Job::Type::ownInfoForUser:
|
||||
case Job::Type::ownCardInternal:
|
||||
emit requestOwnVCard();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::jobIsDone(Job::Id jobId) {
|
||||
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
|
||||
if (itr == runningJobs.end())
|
||||
throw JobNotFound(jobId, "jobIsDone");
|
||||
|
||||
Job* job = itr->second;
|
||||
delete job;
|
||||
runningJobs.erase(itr);
|
||||
if (scheduledJobs.size() > 0) {
|
||||
Job* job = scheduledJobs.front();
|
||||
scheduledJobs.pop_front();
|
||||
executeJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::replaceJob(Job* job) {
|
||||
preScheduleJob(job);
|
||||
std::map<Job::Id, Job*>::iterator itr = runningJobs.find(job->id);
|
||||
if (itr != runningJobs.end()) {
|
||||
preExecuteJob(job);
|
||||
delete itr->second;
|
||||
itr->second = job;
|
||||
} else {
|
||||
StorageById::iterator sitr = scheduledJobsById.find(job->id);
|
||||
if (sitr != scheduledJobsById.end()) {
|
||||
delete *(sitr);
|
||||
scheduledJobsById.replace(sitr, job);
|
||||
} else
|
||||
throw JobNotFound(job->id, "replaceJob");
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::jobIsCanceled(Job* job, bool wasRunning) {
|
||||
switch (job->type) {
|
||||
case Job::Type::cardInternal: {
|
||||
CardInternal* jb = dynamic_cast<CardInternal*>(job);
|
||||
if (wasRunning)
|
||||
requestedVCards.erase(jb->jid);
|
||||
else
|
||||
scheduledVCards.erase(jb->jid);
|
||||
|
||||
emit gotVCard(jb->jid, Shared::VCard());
|
||||
}
|
||||
break;
|
||||
case Job::Type::infoForUser: {
|
||||
InfoForUser* jb = dynamic_cast<InfoForUser*>(job);
|
||||
switch (jb->getStage()) {
|
||||
case InfoForUser::Stage::waitingForVCard:
|
||||
if (wasRunning)
|
||||
requestedVCards.erase(jb->jid);
|
||||
else
|
||||
scheduledVCards.erase(jb->jid);
|
||||
|
||||
emit gotVCard(jb->jid, Shared::VCard());
|
||||
break;
|
||||
case InfoForUser::Stage::waitingForBundles:
|
||||
#ifdef WITH_OMEMO
|
||||
requestedBundles.erase(jb->jid);
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
emit gotInfo(Shared::Info(jb->jid));
|
||||
}
|
||||
break;
|
||||
case Job::Type::ownInfoForUser: {
|
||||
OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
|
||||
if (jb->getStage() == OwnInfoForUser::Stage::waitingForVCard) {
|
||||
ownVCardJobId = 0;
|
||||
emit gotOwnVCard(Shared::VCard());
|
||||
}
|
||||
ownInfoJobId = 0;
|
||||
emit gotOwnInfo(Shared::Info (ownJid));
|
||||
}
|
||||
|
||||
break;
|
||||
case Job::Type::ownCardInternal:
|
||||
ownVCardJobId = 0;
|
||||
emit gotOwnVCard(Shared::VCard());
|
||||
break;
|
||||
}
|
||||
|
||||
delete job;
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::disconnected() {
|
||||
for (const std::pair<const Job::Id, Job*> pair : runningJobs)
|
||||
jobIsCanceled(pair.second, true);
|
||||
|
||||
for (Job* job : scheduledJobs)
|
||||
jobIsCanceled(job, false);
|
||||
|
||||
runningJobs.clear();
|
||||
scheduledJobs.clear();
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared::VCard& card) {
|
||||
std::map<QString, Job::Id>::const_iterator cardItr = requestedVCards.find(jid);
|
||||
if (cardItr == requestedVCards.end()) {
|
||||
qDebug() << "received VCard for" << jid << "but it was never requested through manager, ignoring";
|
||||
return;
|
||||
}
|
||||
Job::Id jobId = cardItr->second;
|
||||
requestedVCards.erase(cardItr);
|
||||
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
|
||||
if (itr == runningJobs.end())
|
||||
throw JobNotFound(jobId, "receivedVCard");
|
||||
|
||||
Job* job = itr->second;
|
||||
|
||||
switch (job->type) {
|
||||
case Job::Type::cardInternal:
|
||||
jobIsDone(jobId);
|
||||
emit gotVCard(jid, card);
|
||||
break;
|
||||
case Job::Type::infoForUser: {
|
||||
#ifdef WITH_OMEMO
|
||||
requestedBundles.emplace(jid, jobId);
|
||||
InfoForUser* jb = dynamic_cast<InfoForUser*>(job);
|
||||
jb->receivedVCard(card);
|
||||
emit requestBundles(jid);
|
||||
#else
|
||||
Shared::Info info(jid);
|
||||
info.turnIntoContact(card);
|
||||
emit gotInfo(info);
|
||||
jobIsDone(jobId);
|
||||
#endif
|
||||
emit gotVCard(jid, card);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw UnexpectedJobType(job->type, "receivedVCard");
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::receivedOwnVCard(const Shared::VCard& card) {
|
||||
if (ownVCardJobId == 0) {
|
||||
qDebug() << "received own VCard for" << ownJid << "but it was never requested through manager, ignoring";
|
||||
return;
|
||||
}
|
||||
Job::Id jobId = ownVCardJobId;
|
||||
ownVCardJobId = 0;
|
||||
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
|
||||
if (itr == runningJobs.end())
|
||||
throw JobNotFound(jobId, "receivedOwnVCard");
|
||||
|
||||
Job* job = itr->second;
|
||||
switch (job->type) {
|
||||
case Job::Type::ownCardInternal:
|
||||
jobIsDone(jobId);
|
||||
emit gotOwnVCard(card);
|
||||
break;
|
||||
case Job::Type::ownInfoForUser: {
|
||||
#ifdef WITH_OMEMO
|
||||
OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
|
||||
jb->receivedVCard(card);
|
||||
emit requestOwnBundles();
|
||||
#else
|
||||
Shared::Info info(ownJid);
|
||||
info.turnIntoOwnAccount(card);
|
||||
emit gotOwnInfo(info);
|
||||
jobIsDone(jobId);
|
||||
#endif
|
||||
emit gotOwnVCard(card);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw UnexpectedJobType(job->type, "receivedVCard");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_OMEMO
|
||||
void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys) {
|
||||
std::map<QString, Job::Id>::const_iterator itr = requestedBundles.find(jid);
|
||||
if (itr == requestedBundles.end()) {
|
||||
qDebug() << "received bundles for" << jid << "but they were never requested through manager, ignoring";
|
||||
return;
|
||||
}
|
||||
|
||||
Job::Id jobId = itr->second;
|
||||
requestedBundles.erase(itr);
|
||||
std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
|
||||
if (jitr == runningJobs.end())
|
||||
throw JobNotFound(jobId, "receivedBundles");
|
||||
|
||||
Job* jb = jitr->second;
|
||||
InfoForUser* job = dynamic_cast<InfoForUser*>(jb);
|
||||
|
||||
Shared::Info info(jid);
|
||||
info.turnIntoContact(job->claim(), new std::list<Shared::KeyInfo>(keys));
|
||||
emit gotInfo(info);
|
||||
jobIsDone(jobId);
|
||||
}
|
||||
|
||||
void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::KeyInfo>& keys) {
|
||||
if (ownInfoJobId == 0) {
|
||||
qDebug() << "received own bundles for" << ownJid << "but they were never requested through manager, ignoring";
|
||||
return;
|
||||
}
|
||||
Job::Id jobId = ownInfoJobId;
|
||||
ownInfoJobId = 0;
|
||||
std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
|
||||
if (jitr == runningJobs.end())
|
||||
throw JobNotFound(jobId, "receivedOwnBundles");
|
||||
|
||||
Job* jb = jitr->second;
|
||||
OwnInfoForUser* job = dynamic_cast<OwnInfoForUser*>(jb);
|
||||
|
||||
Shared::Info info(ownJid);
|
||||
info.turnIntoOwnAccount(job->claim(), new std::list<Shared::KeyInfo>(keys));
|
||||
emit gotOwnInfo(info);
|
||||
jobIsDone(jobId);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Core::DelayManager::Manager::setOwnJid(const QString& jid) {
|
||||
ownJid = jid;
|
||||
}
|
||||
|
||||
|
||||
Core::DelayManager::Manager::UnexpectedJobType::UnexpectedJobType(Job::Type p_type, const std::string& p_method):
|
||||
Exception(),
|
||||
type(p_type),
|
||||
method(p_method)
|
||||
{}
|
||||
|
||||
std::string Core::DelayManager::Manager::UnexpectedJobType::getMessage() const{
|
||||
std::string msg("Unexpected job type: ");
|
||||
msg += Job::TypeString[static_cast<int>(type)];
|
||||
if (method.size() > 0)
|
||||
msg += " in method " + method;
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
Core::DelayManager::Manager::JobNotFound::JobNotFound(Job::Id p_id, const std::string& p_method) :
|
||||
Exception(),
|
||||
id(p_id),
|
||||
method(p_method)
|
||||
{}
|
||||
|
||||
std::string Core::DelayManager::Manager::JobNotFound::getMessage() const {
|
||||
std::string msg("Job with id ");
|
||||
msg += std::to_string(id);
|
||||
msg += " was not found";
|
||||
if (method.size() > 0)
|
||||
msg += " in method " + method;
|
||||
|
||||
return msg;
|
||||
}
|
153
core/delayManager/manager.h
Normal file
153
core/delayManager/manager.h
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/sequenced_index.hpp>
|
||||
#include <boost/multi_index/member.hpp>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <shared/vcard.h>
|
||||
#include <shared/info.h>
|
||||
#include <shared/exception.h>
|
||||
|
||||
#include "job.h"
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class Manager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Manager(const QString& ownJid, Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
|
||||
~Manager();
|
||||
|
||||
void setOwnJid(const QString& jid);
|
||||
bool isOwnVCardPending() const;
|
||||
|
||||
public slots:
|
||||
void getOwnVCard();
|
||||
void getOwnInfo();
|
||||
void getVCard(const QString& jid);
|
||||
void getInfo(const QString& jid);
|
||||
|
||||
signals:
|
||||
void requestVCard(const QString& jid);
|
||||
void requestOwnVCard();
|
||||
|
||||
#ifdef WITH_OMEMO
|
||||
void requestBundles(const QString& jid);
|
||||
void requestOwnBundles();
|
||||
#endif
|
||||
|
||||
void gotVCard(const QString& jid, const Shared::VCard& info);
|
||||
void gotOwnVCard(const Shared::VCard& info);
|
||||
void gotInfo(const Shared::Info& info);
|
||||
void gotOwnInfo(const Shared::Info& info);
|
||||
|
||||
public slots:
|
||||
void disconnected();
|
||||
void receivedOwnVCard(const Shared::VCard& card);
|
||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
||||
|
||||
#ifdef WITH_OMEMO
|
||||
void receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys);
|
||||
void receivedOwnBundles(const std::list<Shared::KeyInfo>& keys);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void preScheduleJob(Job* job);
|
||||
void scheduleJob(Job* job);
|
||||
void preExecuteJob(Job* job);
|
||||
void executeJob(Job* job);
|
||||
void jobIsCanceled(Job* job, bool wasRunning);
|
||||
void jobIsDone(Job::Id jobId);
|
||||
Job::Id getNextJobId();
|
||||
void replaceJob(Job* job);
|
||||
Job* getVCardJob(const QString& jid);
|
||||
|
||||
private:
|
||||
struct id {};
|
||||
struct sequence {};
|
||||
|
||||
typedef boost::multi_index_container<
|
||||
Job*,
|
||||
boost::multi_index::indexed_by<
|
||||
boost::multi_index::sequenced<
|
||||
boost::multi_index::tag<sequence>
|
||||
>,
|
||||
boost::multi_index::ordered_unique<
|
||||
boost::multi_index::tag<id>,
|
||||
boost::multi_index::member<
|
||||
Job,
|
||||
const Job::Id,
|
||||
&Job::id
|
||||
>
|
||||
>
|
||||
>
|
||||
> Storage;
|
||||
|
||||
|
||||
typedef Storage::index<id>::type StorageById;
|
||||
typedef Storage::index<sequence>::type StorageSequence;
|
||||
Job::Id maxParallelJobs;
|
||||
Job::Id nextJobId;
|
||||
|
||||
Storage scheduledJobs;
|
||||
StorageById& scheduledJobsById;
|
||||
StorageSequence& jobSequence;
|
||||
std::map<Job::Id, Job*> runningJobs;
|
||||
|
||||
Job::Id ownVCardJobId;
|
||||
Job::Id ownInfoJobId;
|
||||
std::map<QString, Job::Id> scheduledVCards;
|
||||
std::map<QString, Job::Id> requestedVCards;
|
||||
#ifdef WITH_OMEMO
|
||||
std::map<QString, Job::Id> requestedBundles;
|
||||
#endif
|
||||
QString ownJid;
|
||||
|
||||
public:
|
||||
class UnexpectedJobType: public Utils::Exception {
|
||||
public:
|
||||
UnexpectedJobType(Job::Type p_type, const std::string& p_method = "");
|
||||
std::string getMessage() const override;
|
||||
private:
|
||||
Job::Type type;
|
||||
std::string method;
|
||||
};
|
||||
|
||||
class JobNotFound: public Utils::Exception {
|
||||
public:
|
||||
JobNotFound(Job::Id p_id, const std::string& p_method = "");
|
||||
std::string getMessage() const override;
|
||||
private:
|
||||
Job::Id id;
|
||||
std::string method;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
}
|
32
core/delayManager/owncardinternal.cpp
Normal file
32
core/delayManager/owncardinternal.cpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "owncardinternal.h"
|
||||
|
||||
Core::DelayManager::OwnCardInternal::OwnCardInternal(Id p_id) :
|
||||
Job(p_id, Type::ownCardInternal)
|
||||
{}
|
||||
|
||||
Core::DelayManager::OwnCardInternal::OwnCardInternal(Id p_id, Type p_type) :
|
||||
Job(p_id, p_type)
|
||||
{}
|
||||
|
||||
Core::DelayManager::OwnCardInternal::OwnCardInternal(const OwnCardInternal& other) :
|
||||
Job(other)
|
||||
{}
|
||||
|
36
core/delayManager/owncardinternal.h
Normal file
36
core/delayManager/owncardinternal.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "job.h"
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class OwnCardInternal : public Job {
|
||||
protected:
|
||||
OwnCardInternal(Id id, Type type);
|
||||
|
||||
public:
|
||||
OwnCardInternal(Id id);
|
||||
OwnCardInternal(const OwnCardInternal& other);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
29
core/delayManager/owninfoforuser.cpp
Normal file
29
core/delayManager/owninfoforuser.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "owninfoforuser.h"
|
||||
|
||||
Core::DelayManager::OwnInfoForUser::OwnInfoForUser(Id p_id) :
|
||||
Job(p_id, Type::ownInfoForUser),
|
||||
Info(p_id, Type::ownInfoForUser)
|
||||
{}
|
||||
|
||||
Core::DelayManager::OwnInfoForUser::OwnInfoForUser(const OwnInfoForUser& other) :
|
||||
Job(other),
|
||||
Info(other)
|
||||
{}
|
34
core/delayManager/owninfoforuser.h
Normal file
34
core/delayManager/owninfoforuser.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "contact.h"
|
||||
#include "info.h"
|
||||
|
||||
namespace Core {
|
||||
namespace DelayManager {
|
||||
|
||||
class OwnInfoForUser : public Info {
|
||||
public:
|
||||
OwnInfoForUser(Id id);
|
||||
OwnInfoForUser(const OwnInfoForUser& other);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +1,22 @@
|
|||
target_sources(squawk PRIVATE
|
||||
set(SOURCE_FILES
|
||||
messagehandler.cpp
|
||||
messagehandler.h
|
||||
rosterhandler.cpp
|
||||
vcardhandler.cpp
|
||||
discoveryhandler.cpp
|
||||
trusthandler.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
messagehandler.h
|
||||
rosterhandler.h
|
||||
)
|
||||
vcardhandler.h
|
||||
discoveryhandler.h
|
||||
trusthandler.h
|
||||
)
|
||||
|
||||
if(WITH_OMEMO)
|
||||
list(APPEND SOURCE_FILES omemohandler.cpp)
|
||||
list(APPEND HEADER_FILES omemohandler.h)
|
||||
endif()
|
||||
|
||||
target_sources(squawk PRIVATE ${SOURCE_FILES})
|
||||
|
|
155
core/handlers/discoveryhandler.cpp
Normal file
155
core/handlers/discoveryhandler.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
// Squawk messenger.
|
||||
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "discoveryhandler.h"
|
||||
#include "core/account.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Core::DiscoveryHandler::DiscoveryHandler(Core::Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
omemoToCarbonsConnected (false) {}
|
||||
|
||||
Core::DiscoveryHandler::~DiscoveryHandler() {}
|
||||
|
||||
void Core::DiscoveryHandler::initialize()
|
||||
{
|
||||
QObject::connect(acc->dm, &QXmppDiscoveryManager::itemsReceived, this, &DiscoveryHandler::onItemsReceived);
|
||||
QObject::connect(acc->dm, &QXmppDiscoveryManager::infoReceived, this, &DiscoveryHandler::onInfoReceived);
|
||||
|
||||
acc->dm->setClientType("pc");
|
||||
acc->dm->setClientCategory("client");
|
||||
acc->dm->setClientName(qApp->applicationDisplayName() + " " + qApp->applicationVersion());
|
||||
acc->dm->setClientCapabilitiesNode("https://git.macaw.me/blue/squawk");
|
||||
}
|
||||
|
||||
void Core::DiscoveryHandler::onItemsReceived(const QXmppDiscoveryIq& items)
|
||||
{
|
||||
QString server = acc->getServer();
|
||||
if (items.from() == server) {
|
||||
std::set<QString> needToRequest;
|
||||
qDebug() << "Server items list received for account " << acc->getName() << ":";
|
||||
for (QXmppDiscoveryIq::Item item : items.items()) {
|
||||
QString jid = item.jid();
|
||||
if (jid != server) {
|
||||
qDebug() << " Node" << jid;
|
||||
needToRequest.insert(jid);
|
||||
} else {
|
||||
qDebug() << " " << item.node().toStdString().c_str();
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString& jid : needToRequest) {
|
||||
acc->dm->requestInfo(jid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DiscoveryHandler::onInfoReceived(const QXmppDiscoveryIq& info)
|
||||
{
|
||||
QString from = info.from();
|
||||
QString server = acc->getServer();
|
||||
QString accName = acc->getName();
|
||||
QString bareJid = acc->getBareJid();
|
||||
if (from == server) {
|
||||
bool enableCC = false;
|
||||
qDebug() << "Server info received for account" << accName;
|
||||
QStringList features = info.features();
|
||||
qDebug() << "List of supported features of the server " << server << ":";
|
||||
for (const QString& feature : features) {
|
||||
qDebug() << " " << feature.toStdString().c_str();
|
||||
if (feature == "urn:xmpp:carbons:2") {
|
||||
enableCC = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (enableCC) {
|
||||
qDebug() << "Enabling carbon copies for account" << accName;
|
||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
|
||||
acc->cm->setCarbonsEnabled(true);
|
||||
#endif
|
||||
#ifdef WITH_OMEMO
|
||||
if (!omemoToCarbonsConnected && acc->oh->hasOwnDevice()) {
|
||||
// connect(this, &QXmppCarbonManager::messageSent, acc->om, &QXmppOmemoManager::handleMessage);
|
||||
// connect(this, &QXmppCarbonManager::messageReceived, acc->om, &QXmppOmemoManager::handleMessage);
|
||||
omemoToCarbonsConnected = true;
|
||||
}
|
||||
} else {
|
||||
if (omemoToCarbonsConnected) {
|
||||
// disconnect(this, &QXmppCarbonManager::messageSent, acc->om, &QXmppOmemoManager::handleMessage);
|
||||
// disconnect(this, &QXmppCarbonManager::messageReceived, acc->om, &QXmppOmemoManager::handleMessage);
|
||||
omemoToCarbonsConnected = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
qDebug() << "Requesting account" << accName << "capabilities";
|
||||
acc->dm->requestInfo(bareJid);
|
||||
} else if (from == bareJid) {
|
||||
qDebug() << "Received capabilities for account" << accName << ":";
|
||||
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
|
||||
bool pepSupported = false;
|
||||
for (const QXmppDiscoveryIq::Identity& identity : identities) {
|
||||
QString type = identity.type();
|
||||
QString category = identity.category();
|
||||
qDebug() << " " << category << type;
|
||||
if (type == "pep" && category == "pubsub") {
|
||||
pepSupported = true;
|
||||
}
|
||||
}
|
||||
acc->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
|
||||
} else {
|
||||
QString node = info.queryNode();
|
||||
if (!node.isEmpty()) {
|
||||
qDebug() << "Received features and identities for account" << accName << "about" << from;
|
||||
QStringList feats = info.features();
|
||||
std::set<Shared::Identity> identities;
|
||||
std::set<QString> features(feats.begin(), feats.end());
|
||||
QList<QXmppDiscoveryIq::Identity> idents = info.identities();
|
||||
for (const QXmppDiscoveryIq::Identity& ident : idents) {
|
||||
Shared::Identity identity;
|
||||
identity.category = ident.category();
|
||||
identity.language = ident.language();
|
||||
identity.name = ident.name();
|
||||
identity.type = ident.type();
|
||||
identities.insert(identity);
|
||||
|
||||
qDebug() << " " << identity.name << identity.category << identity.type;
|
||||
}
|
||||
for (const QString& feat : features) {
|
||||
qDebug() << " " << feat;
|
||||
}
|
||||
emit acc->infoDiscovered(from, node, identities, features);
|
||||
} else {
|
||||
Contact* cont = acc->rh->getContact(from);
|
||||
if (cont != nullptr) {
|
||||
qDebug() << "Received info for account" << accName << "about contact" << from;
|
||||
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
|
||||
bool pepSupported = false;
|
||||
for (const QXmppDiscoveryIq::Identity& identity : identities) {
|
||||
QString type = identity.type();
|
||||
QString category = identity.category();
|
||||
qDebug() << " " << category << type;
|
||||
if (type == "pep" && category == "pubsub") {
|
||||
pepSupported = true;
|
||||
}
|
||||
}
|
||||
cont->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
core/handlers/discoveryhandler.h
Normal file
48
core/handlers/discoveryhandler.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Squawk messenger.
|
||||
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef CORE_DISCOVERYHANDLER_H
|
||||
#define CORE_DISCOVERYHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QXmppDiscoveryManager.h>
|
||||
#include <QXmppDiscoveryIq.h>
|
||||
|
||||
namespace Core {
|
||||
class Account;
|
||||
|
||||
class DiscoveryHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DiscoveryHandler(Account* account);
|
||||
~DiscoveryHandler();
|
||||
|
||||
void initialize();
|
||||
|
||||
private slots:
|
||||
void onItemsReceived (const QXmppDiscoveryIq& items);
|
||||
void onInfoReceived (const QXmppDiscoveryIq& info);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
bool omemoToCarbonsConnected;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_DISCOVERYHANDLER_H
|
|
@ -19,186 +19,218 @@
|
|||
#include "messagehandler.h"
|
||||
#include "core/account.h"
|
||||
|
||||
static const QMap<QString, QVariant> statePending({{"state", static_cast<uint8_t>(Shared::Message::State::pending)}});
|
||||
static const QMap<QString, QVariant> stateDelivered({{"state", static_cast<uint8_t>(Shared::Message::State::delivered)}});
|
||||
static const QMap<QString, QVariant> stateSent({{"state", static_cast<uint8_t>(Shared::Message::State::sent)}});
|
||||
|
||||
Core::MessageHandler::MessageHandler(Core::Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
pendingStateMessages(),
|
||||
uploadingSlotsQueue()
|
||||
{
|
||||
}
|
||||
{}
|
||||
|
||||
void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
||||
{
|
||||
void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) {
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
#ifdef WITH_OMEMO
|
||||
switch (msg.encryptionMethod()) {
|
||||
case QXmpp::NoEncryption:
|
||||
break; //just do nothing
|
||||
case QXmpp::UnknownEncryption:
|
||||
qDebug() << "Account" << acc->getName() << "received a message with unknown encryption type";
|
||||
break; //let it go the way it is, there is nothing I can do here
|
||||
case QXmpp::Otr:
|
||||
qDebug() << "Account" << acc->getName() << "received an OTR encrypted message, not supported yet";
|
||||
break; //let it go the way it is, there is nothing I can do yet
|
||||
case QXmpp::LegacyOpenPgp:
|
||||
qDebug() << "Account" << acc->getName() << "received an LegacyOpenPgp encrypted message, not supported yet";
|
||||
break; //let it go the way it is, there is nothing I can do yet
|
||||
case QXmpp::Ox:
|
||||
qDebug() << "Account" << acc->getName() << "received an Ox encrypted message, not supported yet";
|
||||
break; //let it go the way it is, there is nothing I can do yet
|
||||
case QXmpp::Omemo0:
|
||||
qDebug() << "Account" << acc->getName() << "received an Omemo0 encrypted message, not supported yet";
|
||||
break; //let it go the way it is, there is nothing I can do yet
|
||||
case QXmpp::Omemo1:
|
||||
qDebug() << "Account" << acc->getName() << "received an Omemo1 encrypted message, not supported yet";
|
||||
break; //let it go the way it is, there is nothing I can do yet
|
||||
case QXmpp::Omemo2:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
bool handled = false;
|
||||
switch (msg.type()) {
|
||||
case QXmppMessage::Normal:
|
||||
qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping";
|
||||
break;
|
||||
case QXmppMessage::Chat:
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
handled = handleChatMessage(msg, false, msg.isCarbonForwarded(), true);
|
||||
#else
|
||||
handled = handleChatMessage(msg);
|
||||
#endif
|
||||
break;
|
||||
case QXmppMessage::GroupChat:
|
||||
handled = handleGroupMessage(msg);
|
||||
break;
|
||||
case QXmppMessage::Error: {
|
||||
QString id = msg.id();
|
||||
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
||||
if (itr != pendingStateMessages.end()) {
|
||||
QString jid = itr->second;
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||
{"errorText", msg.error().text()}
|
||||
};
|
||||
if (cnt != 0) {
|
||||
cnt->changeMessage(id, cData);
|
||||
}
|
||||
;
|
||||
emit acc->changeMessage(jid, id, cData);
|
||||
pendingStateMessages.erase(itr);
|
||||
handled = true;
|
||||
} else {
|
||||
case QXmppMessage::Error:
|
||||
handled = handlePendingMessageError(msg.id(), msg.error().text());
|
||||
if (!handled)
|
||||
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
case QXmppMessage::Headline:
|
||||
qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
|
||||
break;
|
||||
}
|
||||
if (!handled) {
|
||||
if (!handled)
|
||||
logMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
|
||||
{
|
||||
if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
|
||||
Shared::Message sMsg(Shared::Message::chat);
|
||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||
QString jid = sMsg.getPenPalJid();
|
||||
Contact* cnt = acc->rh->getContact(jid);
|
||||
if (cnt == 0) {
|
||||
cnt = acc->rh->addOutOfRosterContact(jid);
|
||||
}
|
||||
if (outgoing) {
|
||||
if (forwarded) {
|
||||
sMsg.setState(Shared::Message::State::sent);
|
||||
}
|
||||
} else {
|
||||
sMsg.setState(Shared::Message::State::delivered);
|
||||
}
|
||||
QString oId = msg.replaceId();
|
||||
if (oId.size() > 0) {
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"body", sMsg.getBody()},
|
||||
{"stamp", sMsg.getTime()}
|
||||
};
|
||||
cnt->correctMessageInArchive(oId, sMsg);
|
||||
emit acc->changeMessage(jid, oId, cData);
|
||||
} else {
|
||||
cnt->appendMessageToArchive(sMsg);
|
||||
emit acc->message(sMsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
bool Core::MessageHandler::handlePendingMessageError(const QString& id, const QString& errorText) {
|
||||
return adjustPendingMessage(id, {
|
||||
{"state", static_cast<uint8_t>(Shared::Message::State::error)},
|
||||
{"errorText", errorText}
|
||||
}, true);
|
||||
}
|
||||
|
||||
bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
|
||||
{
|
||||
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
|
||||
if (msg.body().isEmpty() && msg.outOfBandUrl().isEmpty())
|
||||
return false;
|
||||
|
||||
Shared::Message sMsg(Shared::Message::chat);
|
||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||
QString jid = sMsg.getPenPalJid();
|
||||
Contact* cnt = acc->rh->getContact(jid);
|
||||
if (cnt == 0) {
|
||||
cnt = acc->rh->addOutOfRosterContact(jid);
|
||||
qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
|
||||
}
|
||||
if (sMsg.getOutgoing()) {
|
||||
if (sMsg.getForwarded())
|
||||
sMsg.setState(Shared::Message::State::sent);
|
||||
} else {
|
||||
sMsg.setState(Shared::Message::State::delivered);
|
||||
}
|
||||
QString oId = msg.replaceId();
|
||||
if (oId.size() > 0) {
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"body", sMsg.getBody()},
|
||||
{"stamp", sMsg.getTime()}
|
||||
};
|
||||
cnt->correctMessageInArchive(oId, sMsg);
|
||||
emit acc->changeMessage(jid, oId, cData);
|
||||
} else {
|
||||
cnt->appendMessageToArchive(sMsg);
|
||||
emit acc->message(sMsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
|
||||
const QString& body(msg.body());
|
||||
if (body.size() != 0) {
|
||||
QString id = msg.id();
|
||||
|
||||
Shared::Message sMsg(Shared::Message::groupChat);
|
||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||
QString jid = sMsg.getPenPalJid();
|
||||
Conference* cnt = acc->rh->getConference(jid);
|
||||
if (cnt == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
|
||||
if (pItr != pendingStateMessages.end()) {
|
||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||
cnt->changeMessage(id, cData);
|
||||
pendingStateMessages.erase(pItr);
|
||||
emit acc->changeMessage(jid, id, cData);
|
||||
} else {
|
||||
QString oId = msg.replaceId();
|
||||
if (oId.size() > 0) {
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"body", sMsg.getBody()},
|
||||
{"stamp", sMsg.getTime()}
|
||||
};
|
||||
cnt->correctMessageInArchive(oId, sMsg);
|
||||
emit acc->changeMessage(jid, oId, cData);
|
||||
} else {
|
||||
cnt->appendMessageToArchive(sMsg);
|
||||
QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
|
||||
if (sMsg.getTime() > minAgo) { //otherwise it's considered a delayed delivery, most probably MUC history receipt
|
||||
emit acc->message(sMsg);
|
||||
} else {
|
||||
//qDebug() << "Delayed delivery: ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (body.isEmpty())
|
||||
return false;
|
||||
|
||||
Shared::Message sMsg(Shared::Message::groupChat);
|
||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||
QString jid = sMsg.getPenPalJid();
|
||||
Conference* cnt = acc->rh->getConference(jid);
|
||||
if (cnt == 0)
|
||||
return false;
|
||||
|
||||
bool result = adjustPendingMessage(msg.id(), stateDelivered, true);
|
||||
if (result) //then it was an echo of my own sent message, nothing else needs to be done
|
||||
return result;
|
||||
|
||||
QString oId = msg.replaceId();
|
||||
if (oId.size() > 0) {
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"body", sMsg.getBody()},
|
||||
{"stamp", sMsg.getTime()}
|
||||
};
|
||||
cnt->correctMessageInArchive(oId, sMsg);
|
||||
emit acc->changeMessage(jid, oId, cData);
|
||||
} else {
|
||||
cnt->appendMessageToArchive(sMsg);
|
||||
QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
|
||||
if (sMsg.getTime() > minAgo) //otherwise it's considered a delayed delivery, most probably MUC history initial fetch
|
||||
emit acc->message(sMsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
|
||||
{
|
||||
const QDateTime& time(source.stamp());
|
||||
QString id;
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
||||
id = source.originId();
|
||||
if (id.size() == 0) {
|
||||
id = source.id();
|
||||
}
|
||||
target.setStanzaId(source.stanzaId());
|
||||
#else
|
||||
id = source.id();
|
||||
#endif
|
||||
target.setId(id);
|
||||
void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const {
|
||||
initializeIDs(target, source);
|
||||
QString messageId = target.getId();
|
||||
if (messageId.size() == 0) {
|
||||
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
||||
messageId = target.getId();
|
||||
qDebug() << "Had do initialize a message with no id, assigning autogenerated" << messageId;
|
||||
}
|
||||
target.setFrom(source.from());
|
||||
target.setTo(source.to());
|
||||
target.setBody(source.body());
|
||||
target.setForwarded(forwarded);
|
||||
#ifdef WITH_OMEMO
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
if (source.encryptionMethod() == QXmpp::EncryptionMethod::Omemo2)
|
||||
target.setEncryption(Shared::EncryptionProtocol::omemo2);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (guessing) {
|
||||
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
||||
outgoing = true;
|
||||
} else {
|
||||
outgoing = false;
|
||||
}
|
||||
}
|
||||
if (guessing)
|
||||
outgoing = target.getFromJid() == acc->getBareJid();
|
||||
|
||||
const QDateTime& time(source.stamp());
|
||||
target.setOutgoing(outgoing);
|
||||
if (time.isValid()) {
|
||||
if (time.isValid())
|
||||
target.setTime(time);
|
||||
} else {
|
||||
else
|
||||
target.setCurrentTime();
|
||||
}
|
||||
|
||||
|
||||
QString oob = source.outOfBandUrl();
|
||||
if (oob.size() > 0) {
|
||||
if (oob.size() > 0)
|
||||
target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
|
||||
}
|
||||
|
||||
target.setOutOfBandUrl(oob);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
|
||||
{
|
||||
void Core::MessageHandler::initializeIDs(Shared::Message& target, const QXmppMessage& source) const {
|
||||
QString id;
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
||||
id = source.originId();
|
||||
if (id.size() == 0)
|
||||
id = source.id();
|
||||
|
||||
QString stanzaID;
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
// here I'm looking preferably for id generated by myself, but if there isn't - any is better than nothing
|
||||
QVector<QXmppStanzaId> sIDs = source.stanzaIds();
|
||||
for (const QXmppStanzaId& sID : sIDs) {
|
||||
bool match = sID.by == acc->getBareJid();
|
||||
if (stanzaID.isEmpty() || match)
|
||||
stanzaID = sID.id;
|
||||
|
||||
if (match)
|
||||
break;
|
||||
}
|
||||
#else
|
||||
stanzaID = source.stanzaId();
|
||||
#endif
|
||||
target.setStanzaId(stanzaID);
|
||||
qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stanzaId:" << stanzaID;
|
||||
#else
|
||||
id = source.id();
|
||||
#endif
|
||||
target.setId(id);
|
||||
}
|
||||
|
||||
|
||||
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason) {
|
||||
qDebug() << reason;
|
||||
qDebug() << "- from: " << msg.from();
|
||||
qDebug() << "- to: " << msg.to();
|
||||
|
@ -207,279 +239,395 @@ void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& re
|
|||
qDebug() << "- state: " << msg.state();
|
||||
qDebug() << "- stamp: " << msg.stamp();
|
||||
qDebug() << "- id: " << msg.id();
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
||||
qDebug() << "- stanzaId: " << msg.stanzaId();
|
||||
#endif
|
||||
qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl();
|
||||
qDebug() << "==============================";
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg)
|
||||
{
|
||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
|
||||
void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg) {
|
||||
handleChatMessage(msg, false, true);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
|
||||
{
|
||||
void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg) {
|
||||
handleChatMessage(msg, true, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
|
||||
{
|
||||
std::optional<Shared::MessageInfo> Core::MessageHandler::getOriginalPendingMessageId(const QString& id, bool clear) {
|
||||
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
||||
if (itr != pendingStateMessages.end()) {
|
||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||
RosterItem* ri = acc->rh->getRosterItem(itr->second);
|
||||
if (ri != 0) {
|
||||
ri->changeMessage(id, cData);
|
||||
Shared::MessageInfo info(acc->name, itr->second, itr->first);
|
||||
std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
|
||||
if (itrC != pendingCorrectionMessages.end()) {
|
||||
if (itrC->second.size() > 0)
|
||||
info.jid = itrC->second;
|
||||
|
||||
if (clear)
|
||||
pendingCorrectionMessages.erase(itrC);
|
||||
}
|
||||
emit acc->changeMessage(itr->second, id, cData);
|
||||
pendingStateMessages.erase(itr);
|
||||
|
||||
if (clear)
|
||||
pendingStateMessages.erase(itr);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage)
|
||||
{
|
||||
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id) {
|
||||
SHARED_UNUSED(jid);
|
||||
adjustPendingMessage(id, {{"state", static_cast<uint>(Shared::Message::State::delivered)}}, true);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId) {
|
||||
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
|
||||
pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId));
|
||||
prepareUpload(data, newMessage);
|
||||
} else {
|
||||
performSending(data, newMessage);
|
||||
performSending(data, originalId, newMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
|
||||
{
|
||||
void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage) {
|
||||
QString jid = data.getPenPalJid();
|
||||
QString id = data.getId();
|
||||
QString oob = data.getOutOfBandUrl();
|
||||
qDebug() << "Sending message with id:" << id;
|
||||
if (originalId.size() > 0)
|
||||
qDebug() << "To replace the one with id:" << originalId;
|
||||
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
bool sent = false;
|
||||
QMap<QString, QVariant> changes;
|
||||
if (newMessage && originalId.size() > 0)
|
||||
newMessage = false;
|
||||
|
||||
QDateTime sendTime = QDateTime::currentDateTimeUtc();
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
||||
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
||||
msg.setOriginId(id);
|
||||
#endif
|
||||
msg.setId(id);
|
||||
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
||||
msg.setOutOfBandUrl(oob);
|
||||
msg.setReceiptRequested(true);
|
||||
msg.setStamp(sendTime);
|
||||
|
||||
sent = acc->client.sendPacket(msg);
|
||||
//sent = false;
|
||||
|
||||
if (sent) {
|
||||
data.setState(Shared::Message::State::sent);
|
||||
} else {
|
||||
data.setState(Shared::Message::State::error);
|
||||
data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
|
||||
}
|
||||
|
||||
} else {
|
||||
data.setState(Shared::Message::State::error);
|
||||
data.setErrorText("You are is offline or reconnecting");
|
||||
}
|
||||
std::pair<Shared::Message::State, QString> result = scheduleSending(data, sendTime, originalId);
|
||||
data.setState(result.first);
|
||||
data.setErrorText(result.second);
|
||||
|
||||
Shared::Message::State mstate = data.getState();
|
||||
changes.insert("state", static_cast<uint>(mstate));
|
||||
if (mstate == Shared::Message::State::error) {
|
||||
changes.insert("errorText", data.getErrorText());
|
||||
}
|
||||
if (oob.size() > 0) {
|
||||
changes.insert("outOfBandUrl", oob);
|
||||
}
|
||||
if (newMessage) {
|
||||
data.setTime(sendTime);
|
||||
}
|
||||
changes.insert("stamp", sendTime);
|
||||
|
||||
if (ri != 0) {
|
||||
if (newMessage) {
|
||||
QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
|
||||
if (ri != nullptr) {
|
||||
if (newMessage)
|
||||
ri->appendMessageToArchive(data);
|
||||
} else {
|
||||
ri->changeMessage(id, changes);
|
||||
}
|
||||
if (sent) {
|
||||
else
|
||||
ri->changeMessage(originalId.isEmpty() ? id : originalId, changes);
|
||||
|
||||
if (data.getState() != Shared::Message::State::error) {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
if (originalId.size() > 0)
|
||||
pendingCorrectionMessages.insert(std::make_pair(id, originalId));
|
||||
} else {
|
||||
pendingStateMessages.erase(id);
|
||||
pendingCorrectionMessages.erase(id);
|
||||
}
|
||||
}
|
||||
|
||||
emit acc->changeMessage(jid, id, changes);
|
||||
emit acc->changeMessage(jid, originalId.isEmpty() ? id : originalId, changes);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage)
|
||||
{
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QString jid = data.getPenPalJid();
|
||||
QString id = data.getId();
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
if (!ri) {
|
||||
qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||
return;
|
||||
}
|
||||
QString path = data.getAttachPath();
|
||||
QString url = acc->network->getFileRemoteUrl(path);
|
||||
if (url.size() != 0) {
|
||||
sendMessageWithLocalUploadedFile(data, url, newMessage);
|
||||
} else {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
if (newMessage) {
|
||||
ri->appendMessageToArchive(data);
|
||||
} else {
|
||||
QMap<QString, QVariant> changes({
|
||||
{"state", (uint)Shared::Message::State::pending}
|
||||
});
|
||||
ri->changeMessage(id, changes);
|
||||
emit acc->changeMessage(jid, id, changes);
|
||||
}
|
||||
//this checks if the file is already uploading, and if so it subscribes to it's success, so, i need to do stuff only if the network knows nothing of this file
|
||||
if (!acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
|
||||
if (acc->um->serviceFound()) {
|
||||
QFileInfo file(path);
|
||||
if (file.exists() && file.isReadable()) {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
uploadingSlotsQueue.emplace_back(path, id);
|
||||
if (uploadingSlotsQueue.size() == 1) {
|
||||
acc->um->requestUploadSlot(file);
|
||||
}
|
||||
} else {
|
||||
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
|
||||
}
|
||||
std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending(
|
||||
const Shared::Message& message,
|
||||
const QDateTime& sendTime,
|
||||
const QString& originalId
|
||||
) {
|
||||
if (acc->state != Shared::ConnectionState::connected)
|
||||
return {Shared::Message::State::error, "You are is offline or reconnecting"};
|
||||
|
||||
QXmppMessage msg = createPacket(message, sendTime, originalId);
|
||||
QString id = msg.id();
|
||||
#ifdef WITH_OMEMO
|
||||
if (message.getEncryption() == Shared::EncryptionProtocol::omemo2) {
|
||||
QXmppTask<QXmppE2eeExtension::MessageEncryptResult> task = acc->om->encryptMessage(std::move(msg), std::nullopt);
|
||||
if (task.isFinished()) {
|
||||
const QXmppE2eeExtension::MessageEncryptResult& res = task.result();
|
||||
if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(res)) {
|
||||
qDebug() << "Successfully encrypted a message";
|
||||
const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(res);
|
||||
encrypted->setBody(QString());
|
||||
encrypted->setOutOfBandUrl(QString());
|
||||
bool success = acc->client.sendPacket(*encrypted.get());
|
||||
if (success) {
|
||||
qDebug() << "Successfully sent an encrypted message";
|
||||
return {Shared::Message::State::sent, ""};
|
||||
} else {
|
||||
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
|
||||
qDebug() << "Couldn't sent an encrypted message";
|
||||
return {Shared::Message::State::error, "Error sending successfully encrypted message"};
|
||||
}
|
||||
} else if (std::holds_alternative<QXmppError>(res)) {
|
||||
qDebug() << "Couldn't encrypt a message";
|
||||
const QXmppError& err = std::get<QXmppError>(res);
|
||||
return {Shared::Message::State::error, err.description};
|
||||
} else {
|
||||
qDebug() << "Couldn't encrypt a message";
|
||||
return {Shared::Message::State::error, "Unexpected error ecryptng the message"};
|
||||
}
|
||||
} else {
|
||||
task.then(this, [this, id] (QXmppE2eeExtension::MessageEncryptResult&& result) {
|
||||
if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(result)) {
|
||||
qDebug() << "Successfully encrypted a message";
|
||||
const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(result);
|
||||
encrypted->setBody(QString());
|
||||
encrypted->setOutOfBandUrl(QString());
|
||||
bool success = acc->client.sendPacket(*encrypted.get());
|
||||
if (success) {
|
||||
qDebug() << "Successfully sent an encrypted message";
|
||||
if (!adjustPendingMessage(id, stateSent, false))
|
||||
qDebug() << "Encrypted message has been successfully sent, but it couldn't be found to update the sate";
|
||||
} else {
|
||||
qDebug() << "Couldn't sent an encrypted message";
|
||||
handlePendingMessageError(id, "Error sending successfully encrypted message");
|
||||
}
|
||||
} else if (std::holds_alternative<QXmppError>(result)) {
|
||||
qDebug() << "Couldn't encrypt a message";
|
||||
const QXmppError& err = std::get<QXmppError>(result);
|
||||
handlePendingMessageError(id, err.description);
|
||||
} else {
|
||||
qDebug() << "Couldn't encrypt a message";
|
||||
handlePendingMessageError(id, "Unexpected error ecryptng the message");
|
||||
}
|
||||
});
|
||||
return {Shared::Message::State::pending, ""};
|
||||
}
|
||||
} else {
|
||||
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
|
||||
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
bool success = acc->client.sendPacket(msg);
|
||||
if (success)
|
||||
return {Shared::Message::State::sent, ""};
|
||||
else
|
||||
return {Shared::Message::State::error, "Error sending message, internal QXMPP error"};
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
|
||||
{
|
||||
bool Core::MessageHandler::adjustPendingMessage(const QString& messageId, const QMap<QString, QVariant>& data, bool final) {
|
||||
std::optional<Shared::MessageInfo> info = getOriginalPendingMessageId(messageId, final);
|
||||
if (info) {
|
||||
RosterItem* ri = acc->rh->getRosterItem(info->jid);
|
||||
if (ri != nullptr)
|
||||
ri->changeMessage(info->messageId, data);
|
||||
|
||||
emit acc->changeMessage(info->jid, info->messageId, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const {
|
||||
QMap<QString, QVariant> changes;
|
||||
|
||||
QString oob = data.getOutOfBandUrl();
|
||||
Shared::Message::State mstate = data.getState();
|
||||
changes.insert("state", static_cast<uint>(mstate));
|
||||
if (mstate == Shared::Message::State::error)
|
||||
changes.insert("errorText", data.getErrorText());
|
||||
|
||||
if (oob.size() > 0)
|
||||
changes.insert("outOfBandUrl", oob);
|
||||
|
||||
if (newMessage)
|
||||
data.setTime(time);
|
||||
|
||||
if (originalId.size() > 0)
|
||||
changes.insert("body", data.getBody());
|
||||
|
||||
changes.insert("stamp", time);
|
||||
|
||||
//sometimes (when the image is pasted with ctrl+v)
|
||||
//I start sending message with one path, then copy it to downloads directory
|
||||
//so, the final path changes. Let's assume it changes always since it costs me close to nothing
|
||||
QString attachPath = data.getAttachPath();
|
||||
if (attachPath.size() > 0) {
|
||||
QString squawkified = Shared::squawkifyPath(attachPath);
|
||||
changes.insert("attachPath", squawkified);
|
||||
if (attachPath != squawkified)
|
||||
data.setAttachPath(squawkified);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const {
|
||||
QXmppMessage msg(QString(), data.getTo(), data.getBody(), data.getThread());
|
||||
QString id(data.getId());
|
||||
|
||||
if (originalId.size() > 0)
|
||||
msg.setReplaceId(originalId);
|
||||
|
||||
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
||||
msg.setOriginId(id);
|
||||
#endif
|
||||
msg.setId(id);
|
||||
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
||||
msg.setOutOfBandUrl(data.getOutOfBandUrl());
|
||||
msg.setReceiptRequested(true);
|
||||
msg.setStamp(time);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage) {
|
||||
if (acc->state != Shared::ConnectionState::connected) {
|
||||
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
|
||||
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
QString jid = data.getPenPalJid();
|
||||
QString id = data.getId();
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
if (ri == nullptr) {
|
||||
qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = data.getAttachPath();
|
||||
QString url = acc->network->getFileRemoteUrl(path);
|
||||
if (url.size() != 0)
|
||||
return sendMessageWithLocalUploadedFile(data, url, newMessage);
|
||||
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
if (newMessage) {
|
||||
ri->appendMessageToArchive(data);
|
||||
} else {
|
||||
ri->changeMessage(id, statePending);
|
||||
emit acc->changeMessage(jid, id, statePending);
|
||||
}
|
||||
|
||||
//this checks if the file is already uploading, and if so it subscribes to it's success,
|
||||
//So, I need to do stuff only if the network knows nothing of this file
|
||||
if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path))
|
||||
return;
|
||||
|
||||
if (!acc->um->serviceFound()) {
|
||||
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo file(path);
|
||||
if (file.exists() && file.isReadable()) {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
uploadingSlotsQueue.emplace_back(file, id);
|
||||
if (uploadingSlotsQueue.size() == 1)
|
||||
acc->um->requestUploadSlot(file);
|
||||
} else {
|
||||
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot) {
|
||||
if (uploadingSlotsQueue.size() == 0) {
|
||||
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
|
||||
} else {
|
||||
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
|
||||
const std::pair<QFileInfo, QString>& pair = uploadingSlotsQueue.front();
|
||||
const QString& mId = pair.second;
|
||||
QString palJid = pendingStateMessages.at(mId);
|
||||
acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
||||
acc->network->uploadFile({acc->name, palJid, mId}, pair.first.path(), slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
||||
|
||||
uploadingSlotsQueue.pop_front();
|
||||
if (uploadingSlotsQueue.size() > 0) {
|
||||
if (uploadingSlotsQueue.size() > 0)
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
|
||||
{
|
||||
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) {
|
||||
QString err(request.error().text());
|
||||
if (uploadingSlotsQueue.size() == 0) {
|
||||
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
|
||||
qDebug() << err;
|
||||
} else {
|
||||
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
|
||||
const std::pair<QFileInfo, QString>& pair = uploadingSlotsQueue.front();
|
||||
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
|
||||
handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
|
||||
|
||||
uploadingSlotsQueue.pop_front();
|
||||
if (uploadingSlotsQueue.size() > 0) {
|
||||
if (uploadingSlotsQueue.size() > 0)
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||
{
|
||||
void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path) {
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"attachPath", path}
|
||||
};
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
|
||||
if (cnt != 0) {
|
||||
if (cnt->changeMessage(info.messageId, cData)) {
|
||||
emit acc->changeMessage(info.jid, info.messageId, cData);
|
||||
}
|
||||
}
|
||||
if (info.account != acc->getName())
|
||||
continue;
|
||||
|
||||
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
|
||||
if (cnt != nullptr) {
|
||||
bool changed = cnt->changeMessage(info.messageId, cData);
|
||||
if (changed)
|
||||
emit acc->changeMessage(info.jid, info.messageId, cData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
|
||||
{
|
||||
if (up) {
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
handleUploadError(info.jid, info.messageId, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up) {
|
||||
if (!up)
|
||||
return;
|
||||
|
||||
for (const Shared::MessageInfo& info : msgs)
|
||||
if (info.account == acc->getName())
|
||||
handleUploadError(info.jid, info.messageId, text);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
|
||||
{
|
||||
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText) {
|
||||
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
|
||||
pendingStateMessages.erase(jid);
|
||||
pendingStateMessages.erase(messageId);
|
||||
pendingCorrectionMessages.erase(messageId);
|
||||
requestChangeMessage(jid, messageId, {
|
||||
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||
{"errorText", errorText}
|
||||
});
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||
{
|
||||
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
RosterItem* ri = acc->rh->getRosterItem(info.jid);
|
||||
if (ri != 0) {
|
||||
Shared::Message msg = ri->getMessage(info.messageId);
|
||||
sendMessageWithLocalUploadedFile(msg, path, false);
|
||||
} else {
|
||||
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||
}
|
||||
if (info.account != acc->getName())
|
||||
continue;
|
||||
|
||||
RosterItem* ri = acc->rh->getRosterItem(info.jid);
|
||||
if (ri != nullptr) {
|
||||
Shared::Message msg = ri->getMessage(info.messageId);
|
||||
msg.setAttachPath(path);
|
||||
sendMessageWithLocalUploadedFile(msg, url, false);
|
||||
} else {
|
||||
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
|
||||
{
|
||||
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage) {
|
||||
msg.setOutOfBandUrl(url);
|
||||
if (msg.getBody().size() == 0) { //not sure why, but most messages do that
|
||||
if (msg.getBody().size() == 0) //not sure why, but most messengers do that
|
||||
msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
|
||||
}
|
||||
performSending(msg, newMessage);
|
||||
|
||||
performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage);
|
||||
//TODO removal/progress update
|
||||
}
|
||||
|
||||
static const std::set<QString> allowerToChangeKeys({
|
||||
static const std::set<QString> allowedToChangeKeys({
|
||||
"attachPath",
|
||||
"outOfBandUrl",
|
||||
"state",
|
||||
"errorText"
|
||||
});
|
||||
|
||||
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data) {
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
if (cnt != 0) {
|
||||
if (cnt != nullptr) {
|
||||
bool allSupported = true;
|
||||
QString unsupportedString;
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
|
||||
if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||
allSupported = false; //to make a message to look like if it was edited
|
||||
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
|
||||
break; //because the underlying tech assumes that the change is initiated by user
|
||||
} //not by system
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
|
||||
if (allowedToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||
allSupported = false; //to make a message to look like if it was edited
|
||||
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
|
||||
break; //because the underlying tech assumes that
|
||||
} //the change is initiated by user, not by system
|
||||
}
|
||||
if (allSupported) {
|
||||
cnt->changeMessage(messageId, data);
|
||||
|
@ -491,18 +639,23 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
|
|||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
|
||||
{
|
||||
void Core::MessageHandler::resendMessage(const QString& jid, const QString& id) {
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
if (cnt != 0) {
|
||||
if (cnt != nullptr) {
|
||||
try {
|
||||
Shared::Message msg = cnt->getMessage(id);
|
||||
if (msg.getState() == Shared::Message::State::error) {
|
||||
sendMessage(msg, false);
|
||||
if (msg.getEdited()) {
|
||||
QString originalId = msg.getId();
|
||||
msg.generateRandomId();
|
||||
sendMessage(msg, false, originalId);
|
||||
} else {
|
||||
sendMessage(msg, false);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping";
|
||||
}
|
||||
} catch (const Archive::NotFound& err) {
|
||||
} catch (const LMDBAL::NotFound& err) {
|
||||
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message wasn't found in history, skipping";
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -16,48 +16,50 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_MESSAGEHANDLER_H
|
||||
#define CORE_MESSAGEHANDLER_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include <QXmppMessage.h>
|
||||
#include <QXmppHttpUploadIq.h>
|
||||
#ifdef WITH_OMEMO
|
||||
#include <QXmppE2eeExtension.h>
|
||||
#endif
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <shared/messageinfo.h>
|
||||
#include <shared/pathcheck.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
|
||||
class Account;
|
||||
|
||||
class MessageHandler : public QObject
|
||||
{
|
||||
class MessageHandler : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessageHandler(Account* account);
|
||||
|
||||
public:
|
||||
void sendMessage(const Shared::Message& data, bool newMessage = true);
|
||||
void sendMessage(const Shared::Message& data, bool newMessage = true, QString originalId = "");
|
||||
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
||||
void resendMessage(const QString& jid, const QString& id);
|
||||
|
||||
public slots:
|
||||
void onMessageReceived(const QXmppMessage& message);
|
||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
|
||||
void onCarbonMessageReceived(const QXmppMessage& message);
|
||||
void onCarbonMessageSent(const QXmppMessage& message);
|
||||
#endif
|
||||
void onReceiptReceived(const QString& jid, const QString& id);
|
||||
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
||||
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
||||
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
|
||||
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
|
||||
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||
|
||||
|
@ -66,16 +68,22 @@ private:
|
|||
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
||||
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
|
||||
void performSending(Shared::Message data, bool newMessage = true);
|
||||
void performSending(Shared::Message data, const QString& originalId, bool newMessage = true);
|
||||
void prepareUpload(const Shared::Message& data, bool newMessage = true);
|
||||
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
|
||||
QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const;
|
||||
QMap<QString, QVariant> getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const;
|
||||
std::optional<Shared::MessageInfo> getOriginalPendingMessageId(const QString& id, bool clear = true);
|
||||
bool handlePendingMessageError(const QString& id, const QString& errorText);
|
||||
std::pair<Shared::Message::State, QString> scheduleSending(const Shared::Message& message, const QDateTime& sendTime, const QString& originalId);
|
||||
bool adjustPendingMessage(const QString& messageId, const QMap<QString, QVariant>& data, bool final);
|
||||
void initializeIDs(Shared::Message& target, const QXmppMessage& source) const;
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
|
||||
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
|
||||
std::map<QString, QString> pendingCorrectionMessages; //key is new mesage, value is originalOne
|
||||
std::deque<std::pair<QFileInfo, QString>> uploadingSlotsQueue;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_MESSAGEHANDLER_H
|
||||
|
|
285
core/handlers/omemohandler.cpp
Normal file
285
core/handlers/omemohandler.cpp
Normal file
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include "omemohandler.h"
|
||||
#include "core/account.h"
|
||||
#include "core/adapterfunctions.h"
|
||||
|
||||
Core::OmemoHandler::OmemoHandler(Account* account) :
|
||||
QObject(),
|
||||
QXmppOmemoStorage(),
|
||||
acc(account),
|
||||
ownDevice(std::nullopt),
|
||||
db(acc->getName() + "/omemo"),
|
||||
meta(db.addCache<QString, QVariant>("meta")),
|
||||
devices(db.addCache<QString, QHash<uint32_t, Device>>("devices")),
|
||||
preKeyPairs(db.addCache<uint32_t, QByteArray>("preKeyPairs")),
|
||||
signedPreKeyPairs(db.addCache<uint32_t, SignedPreKeyPair>("signedPreKeyPairs"))
|
||||
{
|
||||
db.open();
|
||||
try {
|
||||
QVariant own = meta->getRecord("ownDevice");
|
||||
ownDevice = own.value<OwnDevice>();
|
||||
qDebug() << "Successfully found own device omemo data for account" << acc->getName();
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
qDebug() << "No device omemo data was found for account" << acc->getName();
|
||||
}
|
||||
}
|
||||
|
||||
Core::OmemoHandler::~OmemoHandler() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
bool Core::OmemoHandler::hasOwnDevice() {
|
||||
return ownDevice.has_value();
|
||||
}
|
||||
|
||||
QXmppTask<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
|
||||
OmemoData data;
|
||||
data.ownDevice = ownDevice;
|
||||
|
||||
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
|
||||
std::map<uint32_t, QByteArray> pkeys = preKeyPairs->readAll(txn);
|
||||
for (const std::pair<const uint32_t, QByteArray>& pair : pkeys)
|
||||
data.preKeyPairs.insert(pair.first, pair.second);
|
||||
|
||||
std::map<uint32_t, SignedPreKeyPair> spre = signedPreKeyPairs->readAll(txn);
|
||||
for (const std::pair<const uint32_t, SignedPreKeyPair>& pair : spre) {
|
||||
QXmppOmemoStorage::SignedPreKeyPair qxpair = {pair.second.first, pair.second.second};
|
||||
data.signedPreKeyPairs.insert(pair.first, qxpair);
|
||||
}
|
||||
|
||||
std::map<QString, QHash<uint32_t, Device>> devs = devices->readAll(txn);
|
||||
for (const std::pair<const QString, QHash<uint32_t, Device>>& pair : devs)
|
||||
data.devices.insert(pair.first, pair.second);
|
||||
|
||||
return Core::makeReadyTask(std::move(data));
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t deviceId, const QXmppOmemoStorage::Device& device) {
|
||||
QHash<uint32_t, Device> devs;
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
bool had = true;
|
||||
try {
|
||||
devices->getRecord(jid, devs, txn);
|
||||
} catch (const LMDBAL::NotFound& error) {
|
||||
had = false;
|
||||
}
|
||||
|
||||
devs.insert(deviceId, device); //overwrites
|
||||
if (had)
|
||||
devices->changeRecord(jid, devs, txn);
|
||||
else
|
||||
devices->addRecord(jid, devs, txn);
|
||||
|
||||
txn.commit();
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArray>& keyPairs) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
for (QHash<uint32_t, QByteArray>::const_iterator itr = keyPairs.begin(), end = keyPairs.end(); itr != end; ++itr)
|
||||
preKeyPairs->forceRecord(itr.key(), itr.value(), txn);
|
||||
|
||||
txn.commit();
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair& keyPair) {
|
||||
signedPreKeyPairs->forceRecord(keyId, std::make_pair(keyPair.creationDate, keyPair.data));
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::removeDevice(const QString& jid, uint32_t deviceId) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
QHash<uint32_t, Device> devs = devices->getRecord(jid, txn);
|
||||
devs.remove(deviceId);
|
||||
if (devs.isEmpty())
|
||||
devices->removeRecord(jid, txn);
|
||||
else
|
||||
devices->changeRecord(jid, devs, txn);
|
||||
|
||||
txn.commit();
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::removeDevices(const QString& jid) {
|
||||
devices->removeRecord(jid);
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::removePreKeyPair(uint32_t keyId) {
|
||||
try {
|
||||
preKeyPairs->removeRecord(keyId);
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
qDebug() << "Couldn't remove preKeyPair " << e.what();
|
||||
}
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::removeSignedPreKeyPair(uint32_t keyId) {
|
||||
try {
|
||||
signedPreKeyPairs->removeRecord(keyId);
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::setOwnDevice(const std::optional<OwnDevice>& device) {
|
||||
bool had = ownDevice.has_value();
|
||||
ownDevice = device;
|
||||
if (ownDevice.has_value()) {
|
||||
if (had)
|
||||
meta->changeRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
|
||||
else
|
||||
meta->addRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
|
||||
} else if (had) {
|
||||
meta->removeRecord("ownDevice");
|
||||
}
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
QXmppTask<void> Core::OmemoHandler::resetAll() {
|
||||
ownDevice = std::nullopt;
|
||||
db.drop();
|
||||
|
||||
return Core::makeReadyTask();
|
||||
}
|
||||
|
||||
void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const {
|
||||
QHash<uint32_t, Device> devs;
|
||||
try {
|
||||
devices->getRecord(jid, devs);
|
||||
} catch (const LMDBAL::NotFound& error) {}
|
||||
|
||||
for (QHash<uint32_t, Device>::const_iterator itr = devs.begin(), end = devs.end(); itr != end; ++itr) {
|
||||
const Device& dev = itr.value();
|
||||
out.emplace_back(
|
||||
itr.key(),
|
||||
dev.keyId,
|
||||
dev.label,
|
||||
dev.removalFromDeviceListDate,
|
||||
Shared::TrustLevel::undecided,
|
||||
Shared::EncryptionProtocol::omemo2,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::OmemoHandler::requestBundles(const QString& jid) {
|
||||
QXmppTask<void> task = acc->om->buildMissingSessions({jid});
|
||||
Contact* cnt = acc->rh->getContact(jid);
|
||||
if (cnt)
|
||||
cnt->omemoBundles = Shared::Possible::discovering;
|
||||
|
||||
task.then(this, std::bind(&OmemoHandler::onBundlesReceived, this, jid));
|
||||
}
|
||||
|
||||
void Core::OmemoHandler::requestOwnBundles() {
|
||||
QXmppTask<void> task = acc->om->buildMissingSessions({acc->getBareJid()});
|
||||
task.then(this, std::bind(&OmemoHandler::onOwnBundlesReceived, this));
|
||||
}
|
||||
|
||||
void Core::OmemoHandler::onBundlesReceived(const QString& jid) {
|
||||
std::list<Shared::KeyInfo> keys = readKeys(jid);
|
||||
|
||||
Contact* cnt = acc->rh->getContact(jid);
|
||||
if (cnt)
|
||||
cnt->omemoBundles = Shared::Possible::present;
|
||||
|
||||
acc->delay->receivedBundles(jid, keys);
|
||||
}
|
||||
|
||||
void Core::OmemoHandler::onOwnBundlesReceived() {
|
||||
std::list<Shared::KeyInfo> keys = readKeys(acc->getBareJid());
|
||||
if (ownDevice)
|
||||
keys.emplace_front(
|
||||
ownDevice->id,
|
||||
ownDevice->publicIdentityKey,
|
||||
ownDevice->label,
|
||||
QDateTime::currentDateTime(),
|
||||
Shared::TrustLevel::authenticated,
|
||||
Shared::EncryptionProtocol::omemo2,
|
||||
true
|
||||
);
|
||||
|
||||
acc->delay->receivedOwnBundles(keys);
|
||||
}
|
||||
|
||||
std::list<Shared::KeyInfo> Core::OmemoHandler::readKeys(const QString& jid) {
|
||||
std::list<Shared::KeyInfo> keys;
|
||||
getDevices(jid, keys);
|
||||
std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(Shared::EncryptionProtocol::omemo2, jid);
|
||||
|
||||
for (Shared::KeyInfo& key : keys) {
|
||||
std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
|
||||
if (itr != trustLevels.end())
|
||||
key.trustLevel = itr->second;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
void Core::OmemoHandler::onOmemoDeviceAdded(const QString& jid, uint32_t id) {
|
||||
SHARED_UNUSED(id);
|
||||
qDebug() << "OMEMO device added for" << jid;
|
||||
}
|
||||
|
||||
QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
|
||||
in >> device.label;
|
||||
in >> device.keyId;
|
||||
in >> device.session;
|
||||
in >> device.unrespondedSentStanzasCount;
|
||||
in >> device.unrespondedReceivedStanzasCount;
|
||||
in >> device.removalFromDeviceListDate;
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::Device& device) {
|
||||
out << device.label;
|
||||
out << device.keyId;
|
||||
out << device.session;
|
||||
out << device.unrespondedSentStanzasCount;
|
||||
out << device.unrespondedReceivedStanzasCount;
|
||||
out << device.removalFromDeviceListDate;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::OwnDevice& device) {
|
||||
in >> device.id;
|
||||
in >> device.label;
|
||||
in >> device.privateIdentityKey;
|
||||
in >> device.publicIdentityKey;
|
||||
in >> device.latestSignedPreKeyId;
|
||||
in >> device.latestPreKeyId;
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::OwnDevice& device) {
|
||||
out << device.id;
|
||||
out << device.label;
|
||||
out << device.privateIdentityKey;
|
||||
out << device.publicIdentityKey;
|
||||
out << device.latestSignedPreKeyId;
|
||||
out << device.latestPreKeyId;
|
||||
|
||||
return out;
|
||||
}
|
90
core/handlers/omemohandler.h
Normal file
90
core/handlers/omemohandler.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
|
||||
#include <QXmppOmemoStorage.h>
|
||||
#include <cache.h>
|
||||
|
||||
#include <shared/keyinfo.h>
|
||||
#include <shared/enums.h>
|
||||
|
||||
Q_DECLARE_METATYPE(QXmppOmemoStorage::OwnDevice);
|
||||
Q_DECLARE_METATYPE(QXmppOmemoStorage::Device);
|
||||
|
||||
namespace Core {
|
||||
class Account;
|
||||
|
||||
class OmemoHandler : public QObject, public QXmppOmemoStorage {
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef std::pair<QDateTime, QByteArray> SignedPreKeyPair;
|
||||
|
||||
OmemoHandler(Account* account);
|
||||
~OmemoHandler() override;
|
||||
|
||||
virtual QXmppTask<OmemoData> allData() override;
|
||||
|
||||
virtual QXmppTask<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
|
||||
|
||||
virtual QXmppTask<void> addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair &keyPair) override;
|
||||
virtual QXmppTask<void> removeSignedPreKeyPair(uint32_t keyId) override;
|
||||
|
||||
virtual QXmppTask<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
|
||||
virtual QXmppTask<void> removePreKeyPair(uint32_t keyId) override;
|
||||
|
||||
virtual QXmppTask<void> addDevice(const QString &jid, uint32_t deviceId, const Device &device) override;
|
||||
virtual QXmppTask<void> removeDevice(const QString &jid, uint32_t deviceId) override;
|
||||
virtual QXmppTask<void> removeDevices(const QString &jid) override;
|
||||
|
||||
virtual QXmppTask<void> resetAll() override;
|
||||
|
||||
bool hasOwnDevice();
|
||||
|
||||
void requestBundles(const QString& jid);
|
||||
void requestOwnBundles();
|
||||
void getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const;
|
||||
|
||||
public slots:
|
||||
void onOmemoDeviceAdded(const QString& jid, uint32_t id);
|
||||
|
||||
private slots:
|
||||
void onBundlesReceived(const QString& jid);
|
||||
void onOwnBundlesReceived();
|
||||
std::list<Shared::KeyInfo> readKeys(const QString& jid);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
std::optional<OwnDevice> ownDevice;
|
||||
LMDBAL::Base db;
|
||||
LMDBAL::Cache<QString, QVariant>* meta;
|
||||
LMDBAL::Cache<QString, QHash<uint32_t, Device>>* devices;
|
||||
LMDBAL::Cache<uint32_t, QByteArray>* preKeyPairs;
|
||||
LMDBAL::Cache<uint32_t, SignedPreKeyPair>* signedPreKeyPairs;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::Device& device);
|
||||
QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::Device& device);
|
||||
|
||||
QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::OwnDevice& device);
|
||||
QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::OwnDevice& device);
|
|
@ -26,34 +26,41 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
|
|||
conferences(),
|
||||
groups(),
|
||||
queuedContacts(),
|
||||
outOfRosterContacts()
|
||||
{
|
||||
outOfRosterContacts() {}
|
||||
|
||||
void Core::RosterHandler::initialize() {
|
||||
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
|
||||
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
|
||||
connect(acc->rm, &QXmppRosterManager::itemRemoved, this, &RosterHandler::onRosterItemRemoved);
|
||||
connect(acc->rm, &QXmppRosterManager::itemChanged, this, &RosterHandler::onRosterItemChanged);
|
||||
|
||||
|
||||
|
||||
|
||||
connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded);
|
||||
connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
|
||||
|
||||
connect(acc, &Account::pepSupportChanged, this, &RosterHandler::onPepSupportedChanged);
|
||||
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
connect(acc->th, &TrustHandler::trustLevelsChanged, this, &RosterHandler::onTrustChanged);
|
||||
#endif
|
||||
}
|
||||
|
||||
Core::RosterHandler::~RosterHandler()
|
||||
{
|
||||
for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
|
||||
delete itr->second;
|
||||
}
|
||||
|
||||
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
|
||||
delete itr->second;
|
||||
}
|
||||
Core::RosterHandler::~RosterHandler() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onRosterReceived()
|
||||
{
|
||||
acc->vm->requestClientVCard(); //TODO need to make sure server actually supports vCards
|
||||
acc->ownVCardRequestInProgress = true;
|
||||
|
||||
void Core::RosterHandler::clear() {
|
||||
for (const std::pair<const QString, Contact*>& pair : contacts)
|
||||
delete pair.second;
|
||||
|
||||
for (const std::pair<const QString, Conference*>& pair : conferences)
|
||||
delete pair.second;
|
||||
|
||||
contacts.clear();
|
||||
conferences.clear();
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onRosterReceived() {
|
||||
QStringList bj = acc->rm->getRosterBareJids();
|
||||
for (int i = 0; i < bj.size(); ++i) {
|
||||
const QString& jid = bj[i];
|
||||
|
@ -61,8 +68,7 @@ void Core::RosterHandler::onRosterReceived()
|
|||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
|
||||
{
|
||||
void Core::RosterHandler::onRosterItemAdded(const QString& bareJid) {
|
||||
QString lcJid = bareJid.toLower();
|
||||
addedAccount(lcJid);
|
||||
std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid);
|
||||
|
@ -72,8 +78,7 @@ void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
|
|||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::addedAccount(const QString& jid)
|
||||
{
|
||||
void Core::RosterHandler::addedAccount(const QString& jid) {
|
||||
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
|
||||
QXmppRosterIq::Item re = acc->rm->getRosterEntry(jid);
|
||||
Contact* contact;
|
||||
|
@ -82,7 +87,6 @@ void Core::RosterHandler::addedAccount(const QString& jid)
|
|||
newContact = true;
|
||||
contact = new Contact(jid, acc->name);
|
||||
contacts.insert(std::make_pair(jid, contact));
|
||||
|
||||
} else {
|
||||
contact = itr->second;
|
||||
}
|
||||
|
@ -94,70 +98,44 @@ void Core::RosterHandler::addedAccount(const QString& jid)
|
|||
contact->setName(re.name());
|
||||
|
||||
if (newContact) {
|
||||
QMap<QString, QVariant> cData({
|
||||
{"name", re.name()},
|
||||
{"state", QVariant::fromValue(state)}
|
||||
});
|
||||
|
||||
careAboutAvatar(contact, cData);
|
||||
handleNewContact(contact);
|
||||
QMap<QString, QVariant> cData = contact->getInfo();
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
|
||||
cData.insert("trust", QVariant::fromValue(acc->th->getSummary(jid)));
|
||||
#endif
|
||||
int grCount = 0;
|
||||
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
|
||||
const QString& groupName = *itr;
|
||||
for (const QString& groupName : gr) {
|
||||
addToGroup(jid, groupName);
|
||||
emit acc->addContact(jid, groupName, cData);
|
||||
grCount++;
|
||||
}
|
||||
|
||||
if (grCount == 0) {
|
||||
if (grCount == 0)
|
||||
emit acc->addContact(jid, "", cData);
|
||||
|
||||
if (acc->pepSupport == Shared::Support::supported) {
|
||||
acc->dm->requestInfo(jid);
|
||||
//acc->dm->requestItems(jid);
|
||||
}
|
||||
handleNewContact(contact);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin)
|
||||
{
|
||||
void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin) {
|
||||
QXmppMucRoom* room = acc->mm->addRoom(jid);
|
||||
QString lNick = nick;
|
||||
if (lNick.size() == 0) {
|
||||
if (lNick.size() == 0)
|
||||
lNick = acc->getName();
|
||||
}
|
||||
|
||||
Conference* conf = new Conference(jid, acc->getName(), autoJoin, roomName, lNick, room);
|
||||
conferences.insert(std::make_pair(jid, conf));
|
||||
|
||||
handleNewConference(conf);
|
||||
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"autoJoin", conf->getAutoJoin()},
|
||||
{"joined", conf->getJoined()},
|
||||
{"nick", conf->getNick()},
|
||||
{"name", conf->getName()},
|
||||
{"avatars", conf->getAllAvatars()}
|
||||
};
|
||||
careAboutAvatar(conf, cData);
|
||||
QMap<QString, QVariant> cData = conf->getInfo();
|
||||
emit acc->addRoom(jid, cData);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data)
|
||||
{
|
||||
Archive::AvatarInfo info;
|
||||
bool hasAvatar = item->readAvatarInfo(info);
|
||||
if (hasAvatar) {
|
||||
if (info.autogenerated) {
|
||||
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
|
||||
} else {
|
||||
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
|
||||
}
|
||||
data.insert("avatarPath", item->avatarPath() + "." + info.type);
|
||||
} else {
|
||||
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
|
||||
data.insert("avatarPath", "");
|
||||
acc->requestVCard(item->jid);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
|
||||
{
|
||||
void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups) {
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
|
||||
if (itr != queuedContacts.end()) {
|
||||
|
@ -171,8 +149,7 @@ void Core::RosterHandler::addContactRequest(const QString& jid, const QString& n
|
|||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::removeContactRequest(const QString& jid)
|
||||
{
|
||||
void Core::RosterHandler::removeContactRequest(const QString& jid) {
|
||||
QString lcJid = jid.toLower();
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
std::set<QString>::const_iterator itr = outOfRosterContacts.find(lcJid);
|
||||
|
@ -187,25 +164,23 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
|
|||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
|
||||
{
|
||||
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact) {
|
||||
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
|
||||
connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
|
||||
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
|
||||
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
|
||||
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
|
||||
connect(contact, &RosterItem::encryptionChanged, this, &RosterHandler::onContactEncryptionChanged);
|
||||
connect(contact, &RosterItem::requestVCard, acc->delay, &DelayManager::Manager::getVCard);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::handleNewContact(Core::Contact* contact)
|
||||
{
|
||||
void Core::RosterHandler::handleNewContact(Core::Contact* contact) {
|
||||
handleNewRosterItem(contact);
|
||||
connect(contact, &Contact::groupAdded, this, &RosterHandler::onContactGroupAdded);
|
||||
connect(contact, &Contact::groupRemoved, this, &RosterHandler::onContactGroupRemoved);
|
||||
connect(contact, &Contact::subscriptionStateChanged, this, &RosterHandler::onContactSubscriptionStateChanged);
|
||||