Compare commits
225 Commits
Author | SHA1 | Date |
---|---|---|
antonpavanvo | 1af20e27f2 | |
antonpavanvo | ea7dcc5f18 | |
Blue | 645b92ba51 | |
Blue | 80c5e2f2b4 | |
Blue | 1f065f23e6 | |
Blue | 3c48577eee | |
Blue | 0340db7f2f | |
Blue | c3a45ec58c | |
Blue | 7ba94e9deb | |
Blue | eac87e713f | |
Blue | d86e2c28a0 | |
Blue | 2fcc432aef | |
Blue | e58213b294 | |
Blue | 3916aec358 | |
Blue | 721d3a1a89 | |
Blue | 83cb220175 | |
Blue | 18859cb960 | |
Blue | 4c20a314f0 | |
Blue | 51ac1ac709 | |
Blue | 8f949277f6 | |
Blue | ce686e121b | |
Blue | f64e5c2df0 | |
Blue | 2c26c7e264 | |
Blue | 69e0c88d8d | |
Blue | 82d54ba4df | |
Blue | 1b66fda318 | |
Blue | 9f746d203b | |
Blue | 27377e0ec5 | |
Blue | 4baa3bccbf | |
Blue | 4786388822 | |
Blue | 62f02c18d7 | |
Blue | 1fcd403dba | |
Blue | 5f6691067a | |
Blue | 788c6ca556 | |
Blue | bf4a27f35d | |
Blue | 0823b35148 | |
Blue | 73b1b58a96 | |
Blue | d8b5ccb2da | |
Blue | 243edff8bd | |
Blue | da19eb86bb | |
Blue | 0ff9f12157 | |
Blue | 802e2f11a1 | |
Blue | c708c33a92 | |
Blue | a8a7ce2538 | |
Blue | 841e526e59 | |
Blue | 6bee149e6b | |
Blue | 62a59eb7a1 | |
Blue | 296328f12d | |
Blue | 4d3ba6b11f | |
Blue | 8a2658e4fc | |
Blue | 9ac0ca10f3 | |
Blue | 7130e674c4 | |
Blue | e27ae1a82f | |
Blue | 1aa2b5a539 | |
Blue | 43bfaf9b7e | |
Blue | b19dafef33 | |
Blue | aeaa6b1b28 | |
Blue | e47ba603e0 | |
Blue | 8fece95aa2 | |
Blue | 893ff53aa8 | |
shunf4 | 39f2f3d975 | |
shunf4 | 52551c1ce0 | |
shunf4 | 50d710de04 | |
Blue | 332131796c | |
shunf4 | 3a70df21f8 | |
shunf4 | a24e8382d1 | |
shunf4 | d20fd84d39 | |
shunf4 | 5862f1552b | |
shunf4 | ebeb4089eb | |
shunf4 | a53126d8bc | |
shunf4 | 7db269acb5 | |
shunf4 | 67e5f9744e | |
shunf4 | d0bdb374a0 | |
shunf4 | 8b3752ef47 | |
shunf4 | 8c6ac1a21d | |
shunf4 | 1c0802c8ca | |
shunf4 | d84a33e144 | |
shunf4 | 4f6946a5fc | |
shunf4 | f3153ef1db | |
shunf4 | 41d9fa7a1c | |
shunf4 | 261b34b712 | |
shunf4 | d1f108e69d | |
shunf4 | 1e37aa762c | |
shunf4 | a1f3c00a54 | |
shunf4 | 923afe2420 | |
shunf4 | faa7d396a5 | |
shunf4 | c55b7c6baf | |
shunf4 | 6764d8ea11 | |
shunf4 | 5fbb96618f | |
Blue | d89c030e66 | |
Blue | 5787af8a4f | |
Blue | 1706b93b59 | |
Blue | 3f09b8f838 | |
Bruno F. Fontes | 87c216b491 | |
Blue | 5f925217fc | |
Blue | 3f1fba4de2 | |
Blue | ddfaa63a24 | |
Blue | 721f6daa36 | |
Blue | 0d584c5aba | |
Blue | 4307262f6e | |
Blue | bd07c49755 | |
vae | 8e99cc2969 | |
vae | a184ecafa3 | |
vae | 7d2688151c | |
vae | 0038aca1f6 | |
vae | 6e06a1d5bc | |
Blue | b7b70bc198 | |
Blue | ce047db787 | |
Blue | f45319de25 | |
Blue | ebf0c64ffb | |
Blue | d514db9c4a | |
Blue | f34289399e | |
Blue | 05d6761baa | |
Blue | 216dcd29e9 | |
Blue | 0973cb2991 | |
Blue | 50190f3eac | |
Blue | b44873d587 | |
Blue | 4c5efad9dc | |
Blue | 8310708c92 | |
Blue | d936c0302d | |
Blue | 0e937199b0 | |
Blue | 48e498be25 | |
Blue | 3a7735b192 | |
Blue | 8f914c02a7 | |
Blue | 50bb3f5fd7 | |
Blue | a0348b8fd2 | |
Blue | 85555da81f | |
Blue | ebe5addfb5 | |
Blue | b3c6860e25 | |
Blue | 00ffbac6b0 | |
Blue | ff4124d1f0 | |
Blue | 15342f3c53 | |
Blue | 270a32db9e | |
Blue | e0ef1ef797 | |
Blue | e1eea2f3a2 | |
Blue | e54cff0f0c | |
Blue | 4e6bd04b02 | |
Blue | 38159eafeb | |
Blue | ef1a9846bf | |
Blue | 3a120c773a | |
Blue | 5f64321c2a | |
Blue | a543eb1aef | |
Blue | 480c78cf61 | |
Blue | 0dcfc5eedc | |
Blue | 87426ee20f | |
Blue | 20bcae5ab2 | |
Blue | 6b65910ded | |
Blue | 9ca4aa29d4 | |
Blue | a625ecb47b | |
Blue | 55ae5858b5 | |
Blue | 9c855553c5 | |
Blue | 83a2e6af85 | |
Blue | 494afcf2b5 | |
Blue | a8698cc94f | |
Blue | b50ce146b8 | |
Blue | 6657dc79ce | |
Blue | bdca0aa6b1 | |
Blue | cb44b12a7e | |
Blue | 21c7d65027 | |
Blue | 29c7d31c89 | |
Blue | a77dfd191a | |
Blue | b95028e33e | |
Blue | 543538fc56 | |
Blue | 7ce27d1c11 | |
Blue | 95f0d4008a | |
Blue | 3477226367 | |
Blue | ddfb3419cc | |
Blue | b309100f99 | |
Blue | c793f56647 | |
Blue | ff2c9831cf | |
Blue | fe1ae8567a | |
Blue | 57d6e3adab | |
Blue | 91cc851bfc | |
Blue | 6d1b83d0f8 | |
Blue | ed56cca2e7 | |
Blue | a4136ff9fe | |
Blue | 565449f176 | |
Blue | 626227db93 | |
Blue | 13a1970047 | |
Blue | ad1977f05f | |
Blue | 0a4c4aa042 | |
Blue | 5e7c247bb4 | |
Blue | 5a59d54b18 | |
Blue | 52efc2b1a4 | |
Blue | 55703c2007 | |
Blue | efc90e18c3 | |
Blue | 3e594c7e13 | |
Blue | 0bcfd779b8 | |
Blue | dd62f84acc | |
Blue | f13b43d38b | |
Blue | 867c3a18e9 | |
Blue | f367502b8e | |
Blue | fe27955689 | |
Blue | 0c33d81c59 | |
Blue | 0cb25a25cf | |
Blue | 09a946c204 | |
Blue | ae3a1c97e3 | |
Blue | 326eef864b | |
Blue | 166a7ac83a | |
Blue | a6e48599aa | |
Blue | 3f710e26d0 | |
Blue | 09749bac51 | |
Blue | 0b78470aaa | |
Blue | dfa4d10c36 | |
Blue | 2c13f0d77c | |
Blue | c1c1de1b7b | |
Blue | 5bbacad84a | |
Blue | 0b57e6a77f | |
Blue | 9d491e9e93 | |
Blue | c067835b00 | |
Blue | e924715a57 | |
Blue | f3515f1104 | |
Blue | 566fc1f2fb | |
Blue | 36c71968bc | |
Blue | 652381b067 | |
Blue | 2a37f36b83 | |
Blue | c4d22c9c14 | |
Blue | e7be046e9f | |
Blue | d050cd82dd | |
Blue | 46e74ad5e8 | |
Blue | dc1ec1c9d4 | |
Blue | 64e33b6139 | |
Blue | c678a790e5 | |
Blue | 323a549eca | |
Blue | 7c32056918 |
|
@ -0,0 +1,153 @@
|
|||
# Changelog
|
||||
|
||||
## Squawk 0.2.2 (May 05, 2022)
|
||||
### Bug fixes
|
||||
- now when you remove an account it actually gets removed
|
||||
- segfault on unitialized Availability in some rare occesions
|
||||
- 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 passord from KWallet before asking you to input it
|
||||
- accounts now connect to the server asyncronously - 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 againg 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
|
||||
- requesting the history of the current chat after reconnection
|
||||
- global availability (in drop down list) gets restored after reconnection
|
||||
- status icon in active chat changes when presence of the pen pal changes
|
||||
- infinite progress when open the dialogue with something that has no history to show
|
||||
- fallback icons for buttons, when no supported theme is installed (shunf4)
|
||||
- better handling messages with no id (shunf4)
|
||||
- removed dependency: uuid, now it's on Qt (shunf4)
|
||||
- better requesting latest history (shunf4)
|
||||
|
||||
### Improvements
|
||||
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
||||
- completely rewritten message feed, now it works way faster and looks cooler
|
||||
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
|
||||
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
|
||||
- once uploaded local files don't get second time uploaded - the remote URL is reused
|
||||
- way better compilation time (vae)
|
||||
|
||||
### New features
|
||||
- pasting images from clipboard to attachment (shunf4)
|
||||
- possible compilation for windows and macOS (shunf4)
|
||||
|
||||
## Squawk 0.1.5 (Jul 29, 2020)
|
||||
### Bug fixes
|
||||
- error with sending attached files to the conference
|
||||
- error with building on lower versions of QXmpp
|
||||
- error on the first access to the conference database
|
||||
- quit now actually quits the app
|
||||
- history in MUC now works properly and requests messages from server
|
||||
- error with handling non lower cased JIDs
|
||||
- some workaround upon reconnection
|
||||
|
||||
|
||||
## Squawk 0.1.4 (Apr 14, 2020)
|
||||
### New features
|
||||
- message line now is in the same window with roster (new window dialog is still able to opened on context menu)
|
||||
- several new ways to manage your account password:
|
||||
- store it in plain text with the config (like it always was)
|
||||
- store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)
|
||||
- ask the account password on each program launch
|
||||
- store it in KWallet which is dynamically loaded
|
||||
- dragging into conversation now attach files
|
||||
|
||||
### Bug fixes
|
||||
- never updating MUC avatars now get updated
|
||||
- going offline related segfault fix
|
||||
- statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there
|
||||
- messages and statuses don't loose content if you use < ore > symbols
|
||||
- now avatars of those who are not in the MUC right now but was also display next to the message
|
||||
- fix crash on attempt to attach the same file for the second time
|
||||
|
||||
|
||||
## Squawk 0.1.3 (Mar 31, 2020)
|
||||
### New features
|
||||
- delivery states for the messages
|
||||
- delivery receipts now work for real
|
||||
- avatars in conferences
|
||||
- edited messages now display correctly
|
||||
- restyling to get better look with different desktop themes
|
||||
|
||||
### Bug fixes
|
||||
- clickable links now detects better
|
||||
- fixed lost messages that came with no ID
|
||||
- avatar related fixes
|
||||
- message carbons now get turned on only if the server supports them
|
||||
- progress spinner fix
|
||||
- files in dialog now have better comment
|
||||
|
||||
|
||||
## Squawk 0.1.2 (Dec 25, 2019)
|
||||
### New features
|
||||
- icons in roster for conferences
|
||||
- pal avatar in dialog window
|
||||
- avatars next to every message in dialog windows (not in conferences yet)
|
||||
- roster window position and size now are stored in config
|
||||
- expanded accounts and groups are stored in config
|
||||
- availability (from top combobox) now is stored in config
|
||||
|
||||
### Bug fixes
|
||||
- segfault when sending more then one attached file
|
||||
- wrong path and name of saving file
|
||||
- wrong message syntax when attaching file and writing text in the save message
|
||||
- problem with links highlighting in dialog
|
||||
- project building fixes
|
||||
|
||||
|
||||
## Squawk 0.1.1 (Nov 16, 2019)
|
||||
A lot of bug fixes, memory leaks fixes
|
||||
### New features
|
||||
- exchange files via HTTP File Upload
|
||||
- download VCards of your contacts
|
||||
- upload your VCard with information about your contact phones email addresses, names, career information and avatar
|
||||
- avatars of your contacts in roster and in notifications
|
||||
|
||||
|
||||
## Squawk 0.0.5 (Oct 10, 2019)
|
||||
### Features
|
||||
- chat directly
|
||||
- have multiple accounts
|
||||
- add contacts
|
||||
- remove contacts
|
||||
- assign contact to different groups
|
||||
- chat in MUCs
|
||||
- join MUCs, leave them, keep them subscribed or unsubscribed
|
||||
- download attachmets
|
||||
- local history
|
||||
- desktop notifications of new messages
|
210
CMakeLists.txt
|
@ -1,65 +1,173 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(squawk)
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
project(squawk VERSION 0.2.2 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
||||
find_package(Qt5LinguistTools)
|
||||
|
||||
set(squawk_SRC
|
||||
main.cpp
|
||||
global.cpp
|
||||
exception.cpp
|
||||
signalcatcher.cpp
|
||||
)
|
||||
|
||||
configure_file(resources/images/logo.svg squawk.svg COPYONLY)
|
||||
execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
configure_file(packaging/squawk.desktop squawk.desktop COPYONLY)
|
||||
|
||||
set(TS_FILES
|
||||
translations/squawk.ru.ts
|
||||
)
|
||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
add_custom_target(translations ALL DEPENDS ${QM_FILES})
|
||||
|
||||
qt5_add_resources(RCC resources/resources.qrc)
|
||||
|
||||
add_executable(squawk ${squawk_SRC} ${RCC})
|
||||
target_link_libraries(squawk Qt5::Widgets)
|
||||
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(core)
|
||||
|
||||
option(SYSTEM_QXMPP "Use system qxmpp lib" OFF)
|
||||
|
||||
if(NOT SYSTEM_QXMPP)
|
||||
add_subdirectory(external/qxmpp)
|
||||
set(WIN32_FLAG "")
|
||||
set(MACOSX_BUNDLE_FLAG "")
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if (WIN32)
|
||||
set(WIN32_FLAG WIN32)
|
||||
endif(WIN32)
|
||||
if (APPLE)
|
||||
set(MACOSX_BUNDLE_FLAG MACOSX_BUNDLE)
|
||||
endif(APPLE)
|
||||
endif()
|
||||
|
||||
target_link_libraries(squawk squawkUI)
|
||||
target_link_libraries(squawk squawkCORE)
|
||||
target_link_libraries(squawk uuid)
|
||||
add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
|
||||
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
|
||||
|
||||
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
||||
option(SYSTEM_QXMPP "Use system qxmpp 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)
|
||||
|
||||
# Dependencies
|
||||
## Qt
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
|
||||
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")
|
||||
else ()
|
||||
message("Building with system QXmpp")
|
||||
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)
|
||||
|
||||
if (NOT KF5KIO_FOUND)
|
||||
set(WITH_KIO OFF)
|
||||
message("KIO package wasn't found, KIO support modules wouldn't be built")
|
||||
else ()
|
||||
target_compile_definitions(squawk PRIVATE WITH_KIO)
|
||||
message("Building with support of KIO")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
## KWallet
|
||||
if (WITH_KWALLET)
|
||||
find_package(KF5Wallet CONFIG)
|
||||
|
||||
if (NOT KF5Wallet_FOUND)
|
||||
set(WITH_KWALLET OFF)
|
||||
message("KWallet package wasn't found, KWallet support module wouldn't be built")
|
||||
else ()
|
||||
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
|
||||
message("Building with support of KWallet")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (WITH_KCONFIG)
|
||||
find_package(KF5Config CONFIG)
|
||||
if (NOT KF5Config_FOUND)
|
||||
set(WITH_KCONFIG OFF)
|
||||
message("KConfig package wasn't found, KConfig support modules wouldn't be built")
|
||||
else()
|
||||
find_package(KF5ConfigWidgets CONFIG)
|
||||
if (NOT KF5ConfigWidgets_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()
|
||||
|
||||
## Signal (TODO)
|
||||
# find_package(Signal REQUIRED)
|
||||
|
||||
## LMDB
|
||||
find_package(LMDB REQUIRED)
|
||||
|
||||
# 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(UNIX AND NOT APPLE)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(squawk PRIVATE Threads::Threads)
|
||||
endif()
|
||||
|
||||
# Build type
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif ()
|
||||
|
||||
message("Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
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 -g)
|
||||
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)
|
||||
|
||||
add_subdirectory(main)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(external/simpleCrypt)
|
||||
add_subdirectory(packaging)
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(resources)
|
||||
add_subdirectory(shared)
|
||||
add_subdirectory(translations)
|
||||
add_subdirectory(ui)
|
||||
|
||||
# Install the executable
|
||||
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/Macaw/Squawk/l10n)
|
||||
install(FILES squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||
install(FILES squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
|
||||
install(FILES squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
|
||||
install(FILES squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
|
||||
install(FILES squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
|
||||
install(FILES squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
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"
|
||||
)
|
||||
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/>.
|
||||
|
||||
|
|
94
README.md
|
@ -1,31 +1,101 @@
|
|||
# Sqwawk
|
||||
# Squawk - a compact XMPP desktop messenger
|
||||
|
||||
A compact XMPP desktop messenger
|
||||
[![AUR license](https://img.shields.io/aur/license/squawk?style=flat-square)](https://git.macaw.me/blue/squawk/raw/branch/master/LICENSE.md)
|
||||
[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
|
||||
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
|
||||
|
||||
![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- QT 5.12 *(lower versions might work but it wasn't tested)*
|
||||
- uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_
|
||||
- lmdb
|
||||
- CMake 3.0 or higher
|
||||
- CMake 3.4 or higher
|
||||
- qxmpp 1.1.0 or higher
|
||||
- KDE Frameworks: kwallet (optional)
|
||||
- KDE Frameworks: KIO (optional)
|
||||
- KDE Frameworks: KConfig (optional)
|
||||
- KDE Frameworks: KConfigWidgets (optional)
|
||||
- Boost (just one little hpp from there)
|
||||
- Imagemagick (for compilation, to rasterize an SVG logo)
|
||||
|
||||
### Getting
|
||||
|
||||
The easiest way to get the Squawk is to install it from AUR (if you use Archlinux like distribution)
|
||||
|
||||
Here is the [link](https://aur.archlinux.org/packages/squawk/) for the AUR package
|
||||
|
||||
You can also install it from console if you use some AUR wrapper. Here what it's going to look like with *pacaur*
|
||||
|
||||
```
|
||||
$ pacaur -S squawk
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
You can also clone the repo and build it from source
|
||||
|
||||
Squawk requires Qt with SSL enabled. It uses CMake as build system.
|
||||
|
||||
Squawk uses upstream version of QXmpp library so first we need to pull it
|
||||
Please check the prerequisites and install them before installation.
|
||||
|
||||
#### For Windows (Mingw-w64) build
|
||||
|
||||
You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
|
||||
|
||||
The best way to acquire library `lmdb` and `boost` is through Msys2.
|
||||
|
||||
First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86_64-boost` by pacman.
|
||||
|
||||
Then you need to provide the cmake cache entry when calling cmake for configuration:
|
||||
|
||||
```
|
||||
git submodule update --init --recursive
|
||||
cmake .. -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
|
||||
```
|
||||
Then create a folder for the build, go there and build the project using CMake
|
||||
|
||||
|
||||
`<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
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
$ 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 --build .
|
||||
```
|
||||
|
||||
You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
|
||||
|
||||
### List of keys
|
||||
|
||||
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`)
|
||||
- `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`)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
image:
|
||||
- Visual Studio 2019
|
||||
- "Previous Ubuntu1804"
|
||||
- macOS-Mojave
|
||||
|
||||
branches:
|
||||
except:
|
||||
- gh-pages
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Visual Studio 2019
|
||||
|
||||
environment:
|
||||
QTDIR: C:\Qt\5.15.2\mingw81_64
|
||||
QTTOOLDIR: C:\Qt\Tools\mingw810_64\bin
|
||||
QTNINJADIR: C:\Qt\Tools\Ninja
|
||||
|
||||
install:
|
||||
- set PATH=%QTTOOLDIR%;%QTNINJADIR%;%QTDIR%\bin;%PATH%
|
||||
- git submodule update --init --recursive
|
||||
|
||||
before_build:
|
||||
- choco install --yes zstandard
|
||||
- choco install --yes --version=7.1.0.2 imagemagick.app
|
||||
- set PATH=C:\Program Files\ImageMagick-7.1.0-Q16-HDRI;%PATH%
|
||||
|
||||
- mkdir lmdb
|
||||
- cd lmdb
|
||||
- curl -OL https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
|
||||
- zstd -d ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
|
||||
- tar -xvf ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar
|
||||
- cd ..
|
||||
|
||||
- mkdir boost
|
||||
- cd boost
|
||||
- curl -OL https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
|
||||
- zstd -d ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
|
||||
- tar -xvf ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar
|
||||
- cd ..
|
||||
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -GNinja -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=%QTDIR% -DLMDB_ROOT_DIR:PATH=C:/projects/squawk/lmdb/mingw64 -DBOOST_ROOT:PATH=C:/projects/squawk/boost/mingw64 ..
|
||||
|
||||
build_script:
|
||||
- cmake --build .
|
||||
- mkdir deploy
|
||||
- cd deploy
|
||||
- copy ..\squawk.exe .\
|
||||
- copy ..\external\qxmpp\src\libqxmpp.dll .\
|
||||
- windeployqt .\squawk.exe
|
||||
- windeployqt .\libqxmpp.dll
|
||||
- cd ..\..
|
||||
|
||||
artifacts:
|
||||
- path: build/deploy/squawk.exe
|
||||
name: Squawk executable (Qt 5.15.2)
|
||||
|
||||
- path: build/deploy
|
||||
name: Squawk deployment with Qt Framework
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: macOS-Mojave
|
||||
|
||||
install:
|
||||
- brew install lmdb imagemagick boost
|
||||
- git submodule update --init --recursive
|
||||
|
||||
before_build:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.15.2/clang_64 ..
|
||||
|
||||
build_script:
|
||||
- cmake --build .
|
||||
|
||||
after_build:
|
||||
- zip -r squawk.app.zip squawk.app
|
||||
|
||||
artifacts:
|
||||
- path: build/squawk.app/Contents/MacOS/squawk
|
||||
name: Squawk executable (Qt 5.15.2)
|
||||
- path: build/external/qxmpp/src/
|
||||
name: QXMPP
|
||||
- path: build/squawk.app.zip
|
||||
name: Squawk Bundle with Qt Framework (Qt 5.15.2)
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: "Previous Ubuntu1804"
|
||||
|
||||
install:
|
||||
- ls $HOME/Qt
|
||||
- sudo apt update
|
||||
- sudo apt install -y liblmdb-dev liblmdb0 imagemagick mesa-common-dev libglu1-mesa-dev libboost-all-dev
|
||||
- git submodule update --init --recursive
|
||||
|
||||
before_build:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 -DCMAKE_BUILD_RPATH="\$ORIGIN" ..
|
||||
|
||||
build_script:
|
||||
- cmake --build .
|
||||
|
||||
after_build:
|
||||
- zip -r squawk.zip squawk -j external/qxmpp/src/libqxmpp*
|
||||
|
||||
artifacts:
|
||||
- path: build/squawk.zip
|
||||
name: Squawk executable and libraries (Qt 5.12)
|
|
@ -0,0 +1,52 @@
|
|||
#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
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
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 ()
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,34 +1,29 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(squawkCORE)
|
||||
set(SIGNALCATCHER_SOURCE signalcatcher.cpp)
|
||||
if(WIN32)
|
||||
set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
|
||||
endif(WIN32)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
target_sources(squawk PRIVATE
|
||||
account.cpp
|
||||
account.h
|
||||
adapterfunctions.cpp
|
||||
adapterfunctions.h
|
||||
conference.cpp
|
||||
conference.h
|
||||
contact.cpp
|
||||
contact.h
|
||||
networkaccess.cpp
|
||||
networkaccess.h
|
||||
rosteritem.cpp
|
||||
rosteritem.h
|
||||
${SIGNALCATCHER_SOURCE}
|
||||
signalcatcher.h
|
||||
squawk.cpp
|
||||
squawk.h
|
||||
)
|
||||
|
||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
||||
find_package(Qt5Network CONFIG REQUIRED)
|
||||
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
|
||||
|
||||
set(squawkCORE_SRC
|
||||
squawk.cpp
|
||||
account.cpp
|
||||
archive.cpp
|
||||
rosteritem.cpp
|
||||
contact.cpp
|
||||
conference.cpp
|
||||
storage.cpp
|
||||
networkaccess.cpp
|
||||
)
|
||||
|
||||
# Tell CMake to create the helloworld executable
|
||||
add_library(squawkCORE ${squawkCORE_SRC})
|
||||
|
||||
if(SYSTEM_QXMPP)
|
||||
find_package(QXmpp CONFIG REQUIRED)
|
||||
get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
|
||||
target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
endif()
|
||||
|
||||
|
||||
# Use the Widgets module from Qt 5.
|
||||
target_link_libraries(squawkCORE Qt5::Core)
|
||||
target_link_libraries(squawkCORE Qt5::Network)
|
||||
target_link_libraries(squawkCORE qxmpp)
|
||||
target_link_libraries(squawkCORE lmdb)
|
||||
add_subdirectory(handlers)
|
||||
add_subdirectory(storage)
|
||||
add_subdirectory(passwordStorageEngines)
|
||||
|
|
1352
core/account.cpp
160
core/account.h
|
@ -19,20 +19,37 @@
|
|||
#ifndef CORE_ACCOUNT_H
|
||||
#define CORE_ACCOUNT_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QObject>
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
#include <QMimeDatabase>
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QTimer>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <QXmppRosterManager.h>
|
||||
#include <QXmppCarbonManager.h>
|
||||
#include <QXmppDiscoveryManager.h>
|
||||
#include <QXmppMamManager.h>
|
||||
#include <QXmppMucManager.h>
|
||||
#include <QXmppClient.h>
|
||||
#include <QXmppBookmarkManager.h>
|
||||
#include <QXmppBookmarkSet.h>
|
||||
#include "../global.h"
|
||||
#include <QXmppUploadRequestManager.h>
|
||||
#include <QXmppVCardManager.h>
|
||||
#include <QXmppMessageReceiptManager.h>
|
||||
|
||||
#include "shared/shared.h"
|
||||
#include "contact.h"
|
||||
#include "conference.h"
|
||||
#include "networkaccess.h"
|
||||
|
||||
#include "handlers/messagehandler.h"
|
||||
#include "handlers/rosterhandler.h"
|
||||
#include "handlers/vcardhandler.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
|
@ -40,21 +57,39 @@ namespace Core
|
|||
class Account : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class MessageHandler;
|
||||
friend class RosterHandler;
|
||||
friend class VCardHandler;
|
||||
public:
|
||||
Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, QObject* parent = 0);
|
||||
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();
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
void reconnect();
|
||||
|
||||
Shared::ConnectionState getState() const;
|
||||
QString getName() const;
|
||||
QString getLogin() const;
|
||||
QString getServer() const;
|
||||
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);
|
||||
|
@ -62,10 +97,10 @@ public:
|
|||
void setPassword(const QString& p_password);
|
||||
void setResource(const QString& p_resource);
|
||||
void setAvailability(Shared::Availability avail);
|
||||
QString getFullJid() const;
|
||||
void setPasswordType(Shared::AccountPassword pt);
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void setActive(bool p_active);
|
||||
void requestArchive(const QString& jid, int count, const QString& before);
|
||||
void setReconnectTimes(unsigned int times);
|
||||
void subscribeToContact(const QString& jid, const QString& reason);
|
||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||
void removeContactRequest(const QString& jid);
|
||||
|
@ -73,15 +108,27 @@ public:
|
|||
void addContactToGroupRequest(const QString& jid, const QString& groupName);
|
||||
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 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 resendMessage(const QString& jid, const QString& id);
|
||||
void replaceMessage(const QString& originalId, const Shared::Message& data);
|
||||
void invalidatePassword();
|
||||
|
||||
public slots:
|
||||
void connect();
|
||||
void disconnect();
|
||||
void reconnect();
|
||||
void requestVCard(const QString& jid);
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(int);
|
||||
void availabilityChanged(int);
|
||||
void changed(const QMap<QString, QVariant>& data);
|
||||
void connectionStateChanged(Shared::ConnectionState);
|
||||
void availabilityChanged(Shared::Availability);
|
||||
void addGroup(const QString& name);
|
||||
void removeGroup(const QString& name);
|
||||
void addRoom(const QString& jid, const QMap<QString, QVariant>& data);
|
||||
|
@ -94,90 +141,67 @@ signals:
|
|||
void addPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& jid, const QString& name);
|
||||
void message(const Shared::Message& data);
|
||||
void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
||||
void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void error(const QString& text);
|
||||
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 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();
|
||||
|
||||
private:
|
||||
QString name;
|
||||
std::map<QString, QString> achiveQueries;
|
||||
std::map<QString, QString> archiveQueries;
|
||||
QXmppClient client;
|
||||
QXmppConfiguration config;
|
||||
QXmppPresence presence;
|
||||
Shared::ConnectionState state;
|
||||
std::map<QString, std::set<QString>> groups;
|
||||
QXmppCarbonManager* cm;
|
||||
QXmppMamManager* am;
|
||||
QXmppMucManager* mm;
|
||||
QXmppBookmarkManager* bm;
|
||||
std::map<QString, Contact*> contacts;
|
||||
std::map<QString, Conference*> conferences;
|
||||
unsigned int maxReconnectTimes;
|
||||
unsigned int reconnectTimes;
|
||||
QXmppRosterManager* rm;
|
||||
QXmppVCardManager* vm;
|
||||
QXmppUploadRequestManager* um;
|
||||
QXmppDiscoveryManager* dm;
|
||||
QXmppMessageReceiptManager* rcpm;
|
||||
bool reconnectScheduled;
|
||||
QTimer* reconnectTimer;
|
||||
|
||||
std::map<QString, QString> queuedContacts;
|
||||
std::set<QString> outOfRosterContacts;
|
||||
NetworkAccess* network;
|
||||
Shared::AccountPassword passwordType;
|
||||
Error lastError;
|
||||
bool pepSupport;
|
||||
bool active;
|
||||
bool notReadyPassword;
|
||||
|
||||
MessageHandler* mh;
|
||||
RosterHandler* rh;
|
||||
VCardHandler* vh;
|
||||
|
||||
private slots:
|
||||
void onClientConnected();
|
||||
void onClientDisconnected();
|
||||
void onClientStateChange(QXmppClient::State state);
|
||||
void onClientError(QXmppClient::Error err);
|
||||
|
||||
void onRosterReceived();
|
||||
void onRosterItemAdded(const QString& bareJid);
|
||||
void onRosterItemChanged(const QString& bareJid);
|
||||
void onRosterItemRemoved(const QString& bareJid);
|
||||
void onRosterPresenceChanged(const QString& bareJid, const QString& resource);
|
||||
|
||||
void onPresenceReceived(const QXmppPresence& presence);
|
||||
void onMessageReceived(const QXmppMessage& message);
|
||||
|
||||
void onCarbonMessageReceived(const QXmppMessage& message);
|
||||
void onCarbonMessageSent(const QXmppMessage& message);
|
||||
|
||||
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
|
||||
|
||||
void onMamMessageReceived(const QString& bareJid, const QXmppMessage& message);
|
||||
void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
|
||||
|
||||
void onMucRoomAdded(QXmppMucRoom* room);
|
||||
void onMucJoinedChanged(bool joined);
|
||||
void onMucAutoJoinChanged(bool autoJoin);
|
||||
void onMucNickNameChanged(const QString& nickName);
|
||||
void onMucSubjectChanged(const QString& subject);
|
||||
void onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void onMucRemoveParticipant(const QString& nickName);
|
||||
|
||||
void bookmarksReceived(const QXmppBookmarkSet& bookmarks);
|
||||
|
||||
void onContactGroupAdded(const QString& group);
|
||||
void onContactGroupRemoved(const QString& group);
|
||||
void onContactNameChanged(const QString& name);
|
||||
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list);
|
||||
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
|
||||
|
||||
|
||||
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
||||
|
||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
private:
|
||||
void addedAccount(const QString &bareJid);
|
||||
void handleNewContact(Contact* contact);
|
||||
void handleNewRosterItem(RosterItem* contact);
|
||||
void handleNewConference(Conference* contact);
|
||||
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
|
||||
void addToGroup(const QString& jid, const QString& group);
|
||||
void removeFromGroup(const QString& jid, const QString& group);
|
||||
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
||||
Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const;
|
||||
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
||||
void storeConferences();
|
||||
void clearConferences();
|
||||
|
||||
void handleDisconnection();
|
||||
void onReconnectTimer();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* 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 "adapterfunctions.h"
|
||||
|
||||
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
|
||||
{
|
||||
vCard.setFullName(card.fullName());
|
||||
vCard.setFirstName(card.firstName());
|
||||
vCard.setMiddleName(card.middleName());
|
||||
vCard.setLastName(card.lastName());
|
||||
vCard.setBirthday(card.birthday());
|
||||
vCard.setNickName(card.nickName());
|
||||
vCard.setDescription(card.description());
|
||||
vCard.setUrl(card.url());
|
||||
QXmppVCardOrganization org = card.organization();
|
||||
vCard.setOrgName(org.organization());
|
||||
vCard.setOrgRole(org.role());
|
||||
vCard.setOrgUnit(org.unit());
|
||||
vCard.setOrgTitle(org.title());
|
||||
|
||||
QList<QXmppVCardEmail> emails = card.emails();
|
||||
std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
|
||||
for (const QXmppVCardEmail& em : emails) {
|
||||
QString addr = em.address();
|
||||
if (addr.size() != 0) {
|
||||
QXmppVCardEmail::Type et = em.type();
|
||||
bool prefered = false;
|
||||
bool accounted = false;
|
||||
if (et & QXmppVCardEmail::Preferred) {
|
||||
prefered = true;
|
||||
}
|
||||
if (et & QXmppVCardEmail::Home) {
|
||||
myEmails.emplace_back(addr, Shared::VCard::Email::home, prefered);
|
||||
accounted = true;
|
||||
}
|
||||
if (et & QXmppVCardEmail::Work) {
|
||||
myEmails.emplace_back(addr, Shared::VCard::Email::work, prefered);
|
||||
accounted = true;
|
||||
}
|
||||
if (!accounted) {
|
||||
myEmails.emplace_back(addr, Shared::VCard::Email::none, prefered);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QList<QXmppVCardPhone> phones = card.phones();
|
||||
std::deque<Shared::VCard::Phone>& myPhones = vCard.getPhones();
|
||||
for (const QXmppVCardPhone& ph : phones) {
|
||||
QString num = ph.number();
|
||||
if (num.size() != 0) {
|
||||
QXmppVCardPhone::Type pt = ph.type();
|
||||
bool prefered = false;
|
||||
bool accounted = false;
|
||||
if (pt & QXmppVCardPhone::Preferred) {
|
||||
prefered = true;
|
||||
}
|
||||
|
||||
bool home = false;
|
||||
bool work = false;
|
||||
|
||||
if (pt & QXmppVCardPhone::Home) {
|
||||
home = true;
|
||||
}
|
||||
if (pt & QXmppVCardPhone::Work) {
|
||||
work = true;
|
||||
}
|
||||
|
||||
|
||||
if (pt & QXmppVCardPhone::Fax) {
|
||||
if (home || work) {
|
||||
if (home) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered);
|
||||
}
|
||||
if (work) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered);
|
||||
}
|
||||
} else {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered);
|
||||
}
|
||||
accounted = true;
|
||||
}
|
||||
if (pt & QXmppVCardPhone::Voice) {
|
||||
if (home || work) {
|
||||
if (home) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered);
|
||||
}
|
||||
if (work) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered);
|
||||
}
|
||||
} else {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered);
|
||||
}
|
||||
accounted = true;
|
||||
}
|
||||
if (pt & QXmppVCardPhone::Pager) {
|
||||
if (home || work) {
|
||||
if (home) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered);
|
||||
}
|
||||
if (work) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered);
|
||||
}
|
||||
} else {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered);
|
||||
}
|
||||
accounted = true;
|
||||
}
|
||||
if (pt & QXmppVCardPhone::Cell) {
|
||||
if (home || work) {
|
||||
if (home) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered);
|
||||
}
|
||||
if (work) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered);
|
||||
}
|
||||
} else {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered);
|
||||
}
|
||||
accounted = true;
|
||||
}
|
||||
if (pt & QXmppVCardPhone::Video) {
|
||||
if (home || work) {
|
||||
if (home) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered);
|
||||
}
|
||||
if (work) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered);
|
||||
}
|
||||
} else {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered);
|
||||
}
|
||||
accounted = true;
|
||||
}
|
||||
if (pt & QXmppVCardPhone::Modem) {
|
||||
if (home || work) {
|
||||
if (home) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered);
|
||||
}
|
||||
if (work) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered);
|
||||
}
|
||||
} else {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered);
|
||||
}
|
||||
accounted = true;
|
||||
}
|
||||
if (!accounted) {
|
||||
if (home || work) {
|
||||
if (home) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered);
|
||||
}
|
||||
if (work) {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered);
|
||||
}
|
||||
} else {
|
||||
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
|
||||
iq.setFullName(card.getFullName());
|
||||
iq.setFirstName(card.getFirstName());
|
||||
iq.setMiddleName(card.getMiddleName());
|
||||
iq.setLastName(card.getLastName());
|
||||
iq.setNickName(card.getNickName());
|
||||
iq.setBirthday(card.getBirthday());
|
||||
iq.setDescription(card.getDescription());
|
||||
iq.setUrl(card.getUrl());
|
||||
QXmppVCardOrganization org;
|
||||
org.setOrganization(card.getOrgName());
|
||||
org.setUnit(card.getOrgUnit());
|
||||
org.setRole(card.getOrgRole());
|
||||
org.setTitle(card.getOrgTitle());
|
||||
iq.setOrganization(org);
|
||||
|
||||
const std::deque<Shared::VCard::Email>& myEmails = card.getEmails();
|
||||
QList<QXmppVCardEmail> emails;
|
||||
for (const Shared::VCard::Email& mEm : myEmails) {
|
||||
QXmppVCardEmail em;
|
||||
QXmppVCardEmail::Type t = QXmppVCardEmail::Internet;
|
||||
if (mEm.prefered) {
|
||||
t = t | QXmppVCardEmail::Preferred;
|
||||
}
|
||||
if (mEm.role == Shared::VCard::Email::home) {
|
||||
t = t | QXmppVCardEmail::Home;
|
||||
} else if (mEm.role == Shared::VCard::Email::work) {
|
||||
t = t | QXmppVCardEmail::Work;
|
||||
}
|
||||
em.setType(t);
|
||||
em.setAddress(mEm.address);
|
||||
|
||||
emails.push_back(em);
|
||||
}
|
||||
|
||||
std::map<QString, QXmppVCardPhone> phones;
|
||||
QList<QXmppVCardPhone> phs;
|
||||
const std::deque<Shared::VCard::Phone>& myPhones = card.getPhones();
|
||||
for (const Shared::VCard::Phone& mPh : myPhones) {
|
||||
std::map<QString, QXmppVCardPhone>::iterator itr = phones.find(mPh.number);
|
||||
if (itr == phones.end()) {
|
||||
itr = phones.emplace(mPh.number, QXmppVCardPhone()).first;
|
||||
}
|
||||
QXmppVCardPhone& phone = itr->second;
|
||||
phone.setNumber(mPh.number);
|
||||
|
||||
switch (mPh.type) {
|
||||
case Shared::VCard::Phone::fax:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Fax);
|
||||
break;
|
||||
case Shared::VCard::Phone::pager:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Pager);
|
||||
break;
|
||||
case Shared::VCard::Phone::voice:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Voice);
|
||||
break;
|
||||
case Shared::VCard::Phone::cell:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Cell);
|
||||
break;
|
||||
case Shared::VCard::Phone::video:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Video);
|
||||
break;
|
||||
case Shared::VCard::Phone::modem:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Modem);
|
||||
break;
|
||||
case Shared::VCard::Phone::other:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::PCS); //loss of information, but I don't even know what the heck is this type of phone!
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mPh.role) {
|
||||
case Shared::VCard::Phone::home:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Home);
|
||||
break;
|
||||
case Shared::VCard::Phone::work:
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Work);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (mPh.prefered) {
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Preferred);
|
||||
}
|
||||
}
|
||||
for (const std::pair<const QString, QXmppVCardPhone>& phone : phones) {
|
||||
phs.push_back(phone.second);
|
||||
}
|
||||
|
||||
iq.setEmails(emails);
|
||||
iq.setPhones(phs);
|
||||
}
|
|
@ -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/>.
|
||||
*/
|
||||
#ifndef CORE_ADAPTER_FUNCTIONS_H
|
||||
#define CORE_ADAPTER_FUNCTIONS_H
|
||||
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <shared/vcard.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
||||
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // CORE_ADAPTER_FUNCTIONS_H
|
510
core/archive.cpp
|
@ -1,510 +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/>.
|
||||
*/
|
||||
|
||||
#include "archive.h"
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <QStandardPaths>
|
||||
#include <QDebug>
|
||||
#include <QDataStream>
|
||||
#include <QDir>
|
||||
|
||||
Core::Archive::Archive(const QString& p_jid, QObject* parent):
|
||||
QObject(parent),
|
||||
jid(p_jid),
|
||||
opened(false),
|
||||
fromTheBeginning(false),
|
||||
environment(),
|
||||
main(),
|
||||
order(),
|
||||
stats()
|
||||
{
|
||||
}
|
||||
|
||||
Core::Archive::~Archive()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Core::Archive::open(const QString& account)
|
||||
{
|
||||
if (!opened) {
|
||||
mdb_env_create(&environment);
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + account + "/" + jid;
|
||||
QDir cache(path);
|
||||
|
||||
if (!cache.exists()) {
|
||||
bool res = cache.mkpath(path);
|
||||
if (!res) {
|
||||
throw Directory(path.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(environment, 4);
|
||||
mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL);
|
||||
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
mdb_dbi_open(txn, "main", MDB_CREATE, &main);
|
||||
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
|
||||
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
|
||||
mdb_txn_commit(txn);
|
||||
fromTheBeginning = _isFromTheBeginning();
|
||||
opened = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::close()
|
||||
{
|
||||
if (opened) {
|
||||
mdb_dbi_close(environment, stats);
|
||||
mdb_dbi_close(environment, order);
|
||||
mdb_dbi_close(environment, main);
|
||||
mdb_env_close(environment);
|
||||
opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::addElement(const Shared::Message& message)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("addElement", jid.toStdString());
|
||||
}
|
||||
QByteArray ba;
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
message.serialize(ds);
|
||||
quint64 stamp = message.getTime().toMSecsSinceEpoch();
|
||||
const std::string& id = message.getId().toStdString();
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
lmdbData.mv_size = ba.size();
|
||||
lmdbData.mv_data = (uint8_t*)ba.data();
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
int rc;
|
||||
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
|
||||
if (rc == 0) {
|
||||
MDB_val orderKey;
|
||||
orderKey.mv_size = 8;
|
||||
orderKey.mv_data = (uint8_t*) &stamp;
|
||||
|
||||
rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
|
||||
if (rc) {
|
||||
qDebug() << "An element couldn't be inserted into the index" << mdb_strerror(rc);
|
||||
mdb_txn_abort(txn);
|
||||
return false;
|
||||
} else {
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
qDebug() << "A transaction error: " << mdb_strerror(rc);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "An element couldn't been added to the archive, skipping" << mdb_strerror(rc);
|
||||
mdb_txn_abort(txn);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::clear()
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("clear", jid.toStdString());
|
||||
}
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
mdb_drop(txn, main, 0);
|
||||
mdb_drop(txn, order, 0);
|
||||
mdb_drop(txn, stats, 0);
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::getElement(const QString& id)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("getElement", jid.toStdString());
|
||||
}
|
||||
|
||||
std::string strKey = id.toStdString();
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = strKey.size();
|
||||
lmdbKey.mv_data = (char*)strKey.c_str();
|
||||
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
|
||||
if (rc) {
|
||||
qDebug() <<"Get error: " << mdb_strerror(rc);
|
||||
mdb_txn_abort(txn);
|
||||
throw NotFound(id.toStdString(), jid.toStdString());
|
||||
} else {
|
||||
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
QDataStream ds(&ba, QIODevice::ReadOnly);
|
||||
|
||||
Shared::Message msg;
|
||||
msg.deserialize(ds);
|
||||
mdb_txn_abort(txn);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::newest()
|
||||
{
|
||||
QString id = newestId();
|
||||
return getElement(id);
|
||||
}
|
||||
|
||||
QString Core::Archive::newestId()
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("newestId", jid.toStdString());
|
||||
}
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
MDB_cursor* cursor;
|
||||
rc = mdb_cursor_open(txn, order, &cursor);
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
|
||||
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
|
||||
if (rc) {
|
||||
qDebug() << "Error geting newestId " << mdb_strerror(rc);
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
throw Empty(jid.toStdString());
|
||||
} else {
|
||||
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
return sId.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Archive::oldestId()
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("oldestId", jid.toStdString());
|
||||
}
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
MDB_cursor* cursor;
|
||||
rc = mdb_cursor_open(txn, order, &cursor);
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
|
||||
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
|
||||
if (rc) {
|
||||
qDebug() << "Error geting oldestId " << mdb_strerror(rc);
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
throw Empty(jid.toStdString());
|
||||
} else {
|
||||
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
return sId.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::oldest()
|
||||
{
|
||||
return getElement(oldestId());
|
||||
}
|
||||
|
||||
unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("addElements", jid.toStdString());
|
||||
}
|
||||
|
||||
int success = 0;
|
||||
int rc = 0;
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
std::list<Shared::Message>::const_iterator itr = messages.begin();
|
||||
while (rc == 0 && itr != messages.end()) {
|
||||
const Shared::Message& message = *itr;
|
||||
|
||||
QByteArray ba;
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
message.serialize(ds);
|
||||
quint64 stamp = message.getTime().toMSecsSinceEpoch();
|
||||
const std::string& id = message.getId().toStdString();
|
||||
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
lmdbData.mv_size = ba.size();
|
||||
lmdbData.mv_data = (uint8_t*)ba.data();
|
||||
|
||||
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
|
||||
if (rc == 0) {
|
||||
MDB_val orderKey;
|
||||
orderKey.mv_size = 8;
|
||||
orderKey.mv_data = (uint8_t*) &stamp;
|
||||
|
||||
rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
|
||||
if (rc) {
|
||||
qDebug() << "An element couldn't be inserted into the index, aborting the transaction" << mdb_strerror(rc);
|
||||
} else {
|
||||
//qDebug() << "element added with id" << message.getId() << "stamp" << message.getTime();
|
||||
success++;
|
||||
}
|
||||
} else {
|
||||
if (rc == MDB_KEYEXIST) {
|
||||
rc = 0;
|
||||
} else {
|
||||
qDebug() << "An element couldn't been added to the archive, aborting the transaction" << mdb_strerror(rc);
|
||||
}
|
||||
}
|
||||
itr++;
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
mdb_txn_abort(txn);
|
||||
success = 0;
|
||||
} else {
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
long unsigned int Core::Archive::size() const
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("size", jid.toStdString());
|
||||
}
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
MDB_stat stat;
|
||||
mdb_stat(txn, order, &stat);
|
||||
mdb_txn_abort(txn);
|
||||
return stat.ms_entries;
|
||||
}
|
||||
|
||||
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("getBefore", jid.toStdString());
|
||||
}
|
||||
std::list<Shared::Message> res;
|
||||
MDB_cursor* cursor;
|
||||
MDB_txn *txn;
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
int rc;
|
||||
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
if (id == "") {
|
||||
rc = mdb_cursor_open(txn, order, &cursor);
|
||||
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
|
||||
if (rc) {
|
||||
qDebug() << "Error getting before" << mdb_strerror(rc) << ", id:" << id;
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
throw Empty(jid.toStdString());
|
||||
}
|
||||
} else {
|
||||
std::string stdId(id.toStdString());
|
||||
lmdbKey.mv_size = stdId.size();
|
||||
lmdbKey.mv_data = (char*)stdId.c_str();
|
||||
rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
|
||||
if (rc) {
|
||||
qDebug() <<"Error getting before: no reference message" << mdb_strerror(rc) << ", id:" << id;
|
||||
mdb_txn_abort(txn);
|
||||
throw NotFound(stdId, jid.toStdString());
|
||||
} else {
|
||||
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
QDataStream ds(&ba, QIODevice::ReadOnly);
|
||||
|
||||
Shared::Message msg;
|
||||
msg.deserialize(ds);
|
||||
quint64 stamp = msg.getTime().toMSecsSinceEpoch();
|
||||
lmdbKey.mv_data = (quint8*)&stamp;
|
||||
lmdbKey.mv_size = 8;
|
||||
|
||||
rc = mdb_cursor_open(txn, order, &cursor);
|
||||
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET);
|
||||
|
||||
if (rc) {
|
||||
qDebug() << "Error getting before: couldn't set " << mdb_strerror(rc);
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
throw NotFound(stdId, jid.toStdString());
|
||||
} else {
|
||||
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV);
|
||||
if (rc) {
|
||||
qDebug() << "Error getting before, couldn't prev " << mdb_strerror(rc);
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
throw NotFound(stdId, jid.toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
MDB_val dKey, dData;
|
||||
dKey.mv_size = lmdbData.mv_size;
|
||||
dKey.mv_data = lmdbData.mv_data;
|
||||
rc = mdb_get(txn, main, &dKey, &dData);
|
||||
if (rc) {
|
||||
qDebug() <<"Get error: " << mdb_strerror(rc);
|
||||
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
mdb_txn_abort(txn);
|
||||
throw NotFound(sId, jid.toStdString());
|
||||
} else {
|
||||
QByteArray ba((char*)dData.mv_data, dData.mv_size);
|
||||
QDataStream ds(&ba, QIODevice::ReadOnly);
|
||||
|
||||
res.emplace_back();
|
||||
Shared::Message& msg = res.back();
|
||||
msg.deserialize(ds);
|
||||
}
|
||||
|
||||
--count;
|
||||
|
||||
} while (count > 0 && mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV) == 0);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Core::Archive::_isFromTheBeginning()
|
||||
{
|
||||
std::string strKey = "beginning";
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = strKey.size();
|
||||
lmdbKey.mv_data = (char*)strKey.c_str();
|
||||
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
|
||||
if (rc == MDB_NOTFOUND) {
|
||||
mdb_txn_abort(txn);
|
||||
return false;
|
||||
} else if (rc) {
|
||||
qDebug() <<"isFromTheBeginning error: " << mdb_strerror(rc);
|
||||
mdb_txn_abort(txn);
|
||||
throw NotFound(strKey, jid.toStdString());
|
||||
} else {
|
||||
uint8_t value = *(uint8_t*)(lmdbData.mv_data);
|
||||
bool is;
|
||||
if (value == 144) {
|
||||
is = false;
|
||||
} else if (value == 72) {
|
||||
is = true;
|
||||
} else {
|
||||
qDebug() <<"isFromTheBeginning error: stored value doesn't match any magic number, the answer is most probably wrong";
|
||||
}
|
||||
mdb_txn_abort(txn);
|
||||
return is;
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::isFromTheBeginning()
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("isFromTheBeginning", jid.toStdString());
|
||||
}
|
||||
return fromTheBeginning;
|
||||
}
|
||||
|
||||
void Core::Archive::setFromTheBeginning(bool is)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Closed("setFromTheBeginning", jid.toStdString());
|
||||
}
|
||||
if (fromTheBeginning != is) {
|
||||
fromTheBeginning = is;
|
||||
const std::string& id = "beginning";
|
||||
uint8_t value = 144;
|
||||
if (is) {
|
||||
value = 72;
|
||||
}
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
lmdbData.mv_size = sizeof value;
|
||||
lmdbData.mv_data = &value;
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
int rc;
|
||||
rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
|
||||
if (rc != 0) {
|
||||
qDebug() << "Couldn't store beginning key into stat database:" << mdb_strerror(rc);
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::printOrder()
|
||||
{
|
||||
qDebug() << "Printing order";
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
MDB_cursor* cursor;
|
||||
rc = mdb_cursor_open(txn, order, &cursor);
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
|
||||
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
|
||||
|
||||
do {
|
||||
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
qDebug() << QString(sId.c_str());
|
||||
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
|
||||
void Core::Archive::printKeys()
|
||||
{
|
||||
MDB_txn *txn;
|
||||
int rc;
|
||||
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
MDB_cursor* cursor;
|
||||
rc = mdb_cursor_open(txn, main, &cursor);
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
|
||||
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
|
||||
|
||||
do {
|
||||
std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
|
||||
qDebug() << QString(sId.c_str());
|
||||
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
}
|
|
@ -25,25 +25,28 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
|
|||
nick(p_nick),
|
||||
room(p_room),
|
||||
joined(false),
|
||||
autoJoin(p_autoJoin)
|
||||
autoJoin(p_autoJoin),
|
||||
exParticipants()
|
||||
{
|
||||
muc = true;
|
||||
name = p_name;
|
||||
|
||||
connect(room, SIGNAL(joined()), this, SLOT(onRoomJoined()));
|
||||
connect(room, SIGNAL(left()), this, SLOT(onRoomLeft()));
|
||||
connect(room, SIGNAL(nameChanged(const QString&)), this, SLOT(onRoomNameChanged(const QString&)));
|
||||
connect(room, SIGNAL(subjectChanged(const QString&)), this, SLOT(onRoomSubjectChanged(const QString&)));
|
||||
connect(room, SIGNAL(participantAdded(const QString&)), this, SLOT(onRoomParticipantAdded(const QString&)));
|
||||
connect(room, SIGNAL(participantChanged(const QString&)), this, SLOT(onRoomParticipantChanged(const QString&)));
|
||||
connect(room, SIGNAL(participantRemoved(const QString&)), this, SLOT(onRoomParticipantRemoved(const QString&)));
|
||||
connect(room, SIGNAL(nickNameChanged(const QString&)), this, SLOT(onRoomNickNameChanged(const QString&)));
|
||||
connect(room, SIGNAL(error(const QXmppStanza::Error&)), this, SLOT(onRoomError(const QXmppStanza::Error&)));
|
||||
connect(room, &QXmppMucRoom::joined, this, &Conference::onRoomJoined);
|
||||
connect(room, &QXmppMucRoom::left, this, &Conference::onRoomLeft);
|
||||
connect(room, &QXmppMucRoom::nameChanged, this, &Conference::onRoomNameChanged);
|
||||
connect(room, &QXmppMucRoom::subjectChanged, this, &Conference::onRoomSubjectChanged);
|
||||
connect(room, &QXmppMucRoom::participantAdded, this, &Conference::onRoomParticipantAdded);
|
||||
connect(room, &QXmppMucRoom::participantChanged, this, &Conference::onRoomParticipantChanged);
|
||||
connect(room, &QXmppMucRoom::participantRemoved, this, &Conference::onRoomParticipantRemoved);
|
||||
connect(room, &QXmppMucRoom::nickNameChanged, this, &Conference::onRoomNickNameChanged);
|
||||
connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError);
|
||||
|
||||
room->setNickName(nick);
|
||||
if (autoJoin) {
|
||||
room->join();
|
||||
}
|
||||
|
||||
archive->readAllResourcesAvatars(exParticipants);
|
||||
}
|
||||
|
||||
Core::Conference::~Conference()
|
||||
|
@ -134,23 +137,66 @@ 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) {
|
||||
qDebug() << "Room" << jid << "is reporting of adding itself to the list participants. Not sure what to do with that yet, skipping";
|
||||
} else {
|
||||
QXmppPresence pres = room->participantPresence(p_name);
|
||||
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()) {
|
||||
lastInteraction = QDateTime::currentDateTime();
|
||||
lastInteraction = QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
QXmppMucItem mi = pres.mucItem();
|
||||
|
||||
emit addParticipant(resource, {
|
||||
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));
|
||||
}
|
||||
cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type);
|
||||
} else {
|
||||
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
|
||||
cData.insert("avatarPath", "");
|
||||
requestVCard(p_name);
|
||||
}
|
||||
|
||||
emit addParticipant(resource, cData);
|
||||
}
|
||||
|
||||
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) {
|
||||
setAutoGeneratedAvatar(resource);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
emit requestVCard(p_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,15 +204,14 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
|
|||
{
|
||||
QStringList comps = p_name.split("/");
|
||||
QString resource = comps.back();
|
||||
if (resource == jid) {
|
||||
qDebug() << "Room" << jid << "is reporting of changing his own presence. Not sure what to do with that yet, skipping";
|
||||
} else {
|
||||
QXmppPresence pres = room->participantPresence(p_name);
|
||||
QXmppPresence pres = room->participantPresence(p_name);
|
||||
QXmppMucItem mi = pres.mucItem();
|
||||
handlePresence(pres);
|
||||
if (resource != jid) {
|
||||
QDateTime lastInteraction = pres.lastUserInteraction();
|
||||
if (!lastInteraction.isValid()) {
|
||||
lastInteraction = QDateTime::currentDateTime();
|
||||
lastInteraction = QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
QXmppMucItem mi = pres.mucItem();
|
||||
|
||||
emit changeParticipant(resource, {
|
||||
{"lastActivity", lastInteraction},
|
||||
|
@ -202,3 +247,117 @@ void Core::Conference::onRoomSubjectChanged(const QString& p_name)
|
|||
{
|
||||
emit subjectChanged(p_name);
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
exParticipants.insert(std::make_pair(resource, newInfo));
|
||||
} else {
|
||||
itr->second = newInfo;
|
||||
}
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
|
||||
{"avatarPath", avatarPath(resource) + "." + newInfo.type}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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()) {
|
||||
exParticipants.insert(std::make_pair(resource, info));
|
||||
} else {
|
||||
itr->second = info;
|
||||
}
|
||||
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
|
||||
{"avatarPath", avatarPath(resource) + "." + info.type}
|
||||
});
|
||||
} else {
|
||||
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
|
||||
if (itr != exParticipants.end()) {
|
||||
exParticipants.erase(itr);
|
||||
}
|
||||
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(Shared::Avatar::empty)},
|
||||
{"avatarPath", ""}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource)
|
||||
{
|
||||
Shared::VCard result = RosterItem::handleResponseVCard(card, resource);
|
||||
|
||||
if (resource.size() > 0) {
|
||||
emit changeParticipant(resource, {
|
||||
{"avatarState", static_cast<uint>(result.getAvatarType())},
|
||||
{"avatarPath", result.getAvatarPath()}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -19,9 +19,15 @@
|
|||
#ifndef CORE_CONFERENCE_H
|
||||
#define CORE_CONFERENCE_H
|
||||
|
||||
#include "rosteritem.h"
|
||||
#include <QDir>
|
||||
|
||||
#include <QXmppMucManager.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "rosteritem.h"
|
||||
#include "shared/global.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
|
||||
|
@ -44,6 +50,10 @@ public:
|
|||
|
||||
bool getAutoJoin();
|
||||
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;
|
||||
QMap<QString, QVariant> getAllAvatars() const;
|
||||
|
||||
signals:
|
||||
void nickChanged(const QString& nick);
|
||||
|
@ -54,11 +64,16 @@ signals:
|
|||
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeParticipant(const QString& name);
|
||||
|
||||
protected:
|
||||
bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override;
|
||||
|
||||
private:
|
||||
QString nick;
|
||||
QXmppMucRoom* room;
|
||||
bool joined;
|
||||
bool autoJoin;
|
||||
std::map<QString, Archive::AvatarInfo> exParticipants;
|
||||
static const std::set<QString> supportedList;
|
||||
|
||||
private slots:
|
||||
void onRoomJoined();
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent):
|
||||
RosterItem(pJid, account, parent),
|
||||
groups(),
|
||||
subscriptionState(Shared::unknown)
|
||||
subscriptionState(Shared::SubscriptionState::unknown)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -68,3 +68,33 @@ void Core::Contact::setSubscriptionState(Shared::SubscriptionState state)
|
|||
emit subscriptionStateChanged(subscriptionState);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Contact::handlePresence(const QXmppPresence& pres)
|
||||
{
|
||||
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);
|
||||
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()) {
|
||||
emit requestVCard(jid);
|
||||
}
|
||||
} else {
|
||||
emit requestVCard(jid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ public:
|
|||
|
||||
void setSubscriptionState(Shared::SubscriptionState state);
|
||||
Shared::SubscriptionState getSubscriptionState() const;
|
||||
void handlePresence(const QXmppPresence & pres) override;
|
||||
|
||||
signals:
|
||||
void groupAdded(const QString& name);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
target_sources(squawk PRIVATE
|
||||
messagehandler.cpp
|
||||
messagehandler.h
|
||||
rosterhandler.cpp
|
||||
rosterhandler.h
|
||||
vcardhandler.cpp
|
||||
vcardhandler.h
|
||||
)
|
|
@ -0,0 +1,595 @@
|
|||
/*
|
||||
* 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 "messagehandler.h"
|
||||
#include "core/account.h"
|
||||
|
||||
Core::MessageHandler::MessageHandler(Core::Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
pendingStateMessages(),
|
||||
uploadingSlotsQueue()
|
||||
{
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
||||
{
|
||||
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:
|
||||
handled = handleChatMessage(msg);
|
||||
break;
|
||||
case QXmppMessage::GroupChat:
|
||||
handled = handleGroupMessage(msg);
|
||||
break;
|
||||
case QXmppMessage::Error: {
|
||||
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
|
||||
if (std::get<0>(ids)) {
|
||||
QString id = std::get<1>(ids);
|
||||
QString jid = std::get<2>(ids);
|
||||
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);
|
||||
handled = true;
|
||||
} else {
|
||||
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QXmppMessage::Headline:
|
||||
qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
|
||||
break;
|
||||
}
|
||||
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);
|
||||
qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
|
||||
}
|
||||
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::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
|
||||
{
|
||||
const QString& body(msg.body());
|
||||
if (body.size() != 0) {
|
||||
|
||||
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::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
|
||||
if (std::get<0>(ids)) {
|
||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||
cnt->changeMessage(std::get<1>(ids), cData);
|
||||
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), 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;
|
||||
}
|
||||
|
||||
|
||||
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());
|
||||
qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
|
||||
#else
|
||||
id = source.id();
|
||||
#endif
|
||||
target.setId(id);
|
||||
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);
|
||||
|
||||
if (guessing) {
|
||||
if (target.getFromJid() == acc->getBareJid()) {
|
||||
outgoing = true;
|
||||
} else {
|
||||
outgoing = false;
|
||||
}
|
||||
}
|
||||
target.setOutgoing(outgoing);
|
||||
if (time.isValid()) {
|
||||
target.setTime(time);
|
||||
} else {
|
||||
target.setCurrentTime();
|
||||
}
|
||||
|
||||
QString oob = source.outOfBandUrl();
|
||||
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)
|
||||
{
|
||||
qDebug() << reason;
|
||||
qDebug() << "- from: " << msg.from();
|
||||
qDebug() << "- to: " << msg.to();
|
||||
qDebug() << "- body: " << msg.body();
|
||||
qDebug() << "- type: " << msg.type();
|
||||
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)
|
||||
{
|
||||
handleChatMessage(msg, false, true);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
|
||||
{
|
||||
handleChatMessage(msg, true, true);
|
||||
}
|
||||
|
||||
std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id)
|
||||
{
|
||||
std::tuple<bool, QString, QString> result({false, "", ""});
|
||||
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
||||
if (itr != pendingStateMessages.end()) {
|
||||
std::get<0>(result) = true;
|
||||
std::get<2>(result) = itr->second;
|
||||
|
||||
std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
|
||||
if (itrC != pendingCorrectionMessages.end()) {
|
||||
if (itrC->second.size() > 0) {
|
||||
std::get<1>(result) = itrC->second;
|
||||
} else {
|
||||
std::get<1>(result) = itr->first;
|
||||
}
|
||||
pendingCorrectionMessages.erase(itrC);
|
||||
} else {
|
||||
std::get<1>(result) = itr->first;
|
||||
}
|
||||
|
||||
pendingStateMessages.erase(itr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
|
||||
{
|
||||
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
|
||||
if (std::get<0>(ids)) {
|
||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||
RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
|
||||
|
||||
if (ri != 0) {
|
||||
ri->changeMessage(std::get<1>(ids), cData);
|
||||
}
|
||||
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
|
||||
}
|
||||
}
|
||||
|
||||
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, originalId, newMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage)
|
||||
{
|
||||
QString jid = data.getPenPalJid();
|
||||
QString id = data.getId();
|
||||
qDebug() << "Sending message with id:" << id;
|
||||
if (originalId.size() > 0) {
|
||||
qDebug() << "To replace one with id:" << originalId;
|
||||
}
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
bool sent = false;
|
||||
if (newMessage && originalId.size() > 0) {
|
||||
newMessage = false;
|
||||
}
|
||||
QDateTime sendTime = QDateTime::currentDateTimeUtc();
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QXmppMessage msg(createPacket(data, sendTime, originalId));
|
||||
|
||||
sent = acc->client.sendPacket(msg);
|
||||
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");
|
||||
}
|
||||
|
||||
QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
|
||||
|
||||
QString realId;
|
||||
if (originalId.size() > 0) {
|
||||
realId = originalId;
|
||||
} else {
|
||||
realId = id;
|
||||
}
|
||||
if (ri != 0) {
|
||||
if (newMessage) {
|
||||
ri->appendMessageToArchive(data);
|
||||
} else {
|
||||
ri->changeMessage(realId, changes);
|
||||
}
|
||||
if (sent) {
|
||||
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, realId, changes);
|
||||
}
|
||||
|
||||
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(acc->getFullJid(), 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) {
|
||||
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";
|
||||
}
|
||||
} 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
|
||||
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
|
||||
}
|
||||
}
|
||||
|
||||
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 QString& mId = pair.second;
|
||||
QString palJid = pendingStateMessages.at(mId);
|
||||
acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
||||
|
||||
uploadingSlotsQueue.pop_front();
|
||||
if (uploadingSlotsQueue.size() > 0) {
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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) {
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
|
||||
{
|
||||
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
|
||||
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& 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);
|
||||
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)
|
||||
{
|
||||
msg.setOutOfBandUrl(url);
|
||||
if (msg.getBody().size() == 0) { //not sure why, but most messages 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, pendingCorrectionMessages.at(msg.getId()), newMessage);
|
||||
//TODO removal/progress update
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
if (cnt != 0) {
|
||||
bool allSupported = true;
|
||||
QString unsupportedString;
|
||||
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);
|
||||
emit acc->changeMessage(jid, messageId, data);
|
||||
} else {
|
||||
qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
|
||||
qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
|
||||
{
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
if (cnt != 0) {
|
||||
try {
|
||||
Shared::Message msg = cnt->getMessage(id);
|
||||
if (msg.getState() == Shared::Message::State::error) {
|
||||
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) {
|
||||
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message wasn't found in history, skipping";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this jid isn't present in account roster, skipping";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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_MESSAGEHANDLER_H
|
||||
#define CORE_MESSAGEHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
|
||||
#include <QXmppMessage.h>
|
||||
#include <QXmppHttpUploadIq.h>
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <shared/messageinfo.h>
|
||||
#include <shared/pathcheck.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
|
||||
class Account;
|
||||
|
||||
class MessageHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessageHandler(Account* account);
|
||||
|
||||
public:
|
||||
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);
|
||||
void onCarbonMessageReceived(const QXmppMessage& message);
|
||||
void onCarbonMessageSent(const QXmppMessage& message);
|
||||
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& 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);
|
||||
|
||||
private:
|
||||
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
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, 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::tuple<bool, QString, QString> getOriginalPendingMessageId(const QString& id);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
|
||||
std::map<QString, QString> pendingCorrectionMessages; //key is new mesage, value is originalOne
|
||||
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_MESSAGEHANDLER_H
|
|
@ -0,0 +1,600 @@
|
|||
/*
|
||||
* 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 "rosterhandler.h"
|
||||
#include "core/account.h"
|
||||
|
||||
Core::RosterHandler::RosterHandler(Core::Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
contacts(),
|
||||
conferences(),
|
||||
groups(),
|
||||
queuedContacts(),
|
||||
outOfRosterContacts(),
|
||||
pepSupport(false)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onRosterReceived()
|
||||
{
|
||||
acc->requestVCard(acc->getBareJid()); //TODO need to make sure server actually supports vCards
|
||||
|
||||
QStringList bj = acc->rm->getRosterBareJids();
|
||||
for (int i = 0; i < bj.size(); ++i) {
|
||||
const QString& jid = bj[i];
|
||||
addedAccount(jid.toLower());
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
|
||||
{
|
||||
QString lcJid = bareJid.toLower();
|
||||
addedAccount(lcJid);
|
||||
std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid);
|
||||
if (itr != queuedContacts.end()) {
|
||||
acc->rm->subscribe(lcJid, itr->second);
|
||||
queuedContacts.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
bool newContact = false;
|
||||
if (itr == contacts.end()) {
|
||||
newContact = true;
|
||||
contact = new Contact(jid, acc->name);
|
||||
contacts.insert(std::make_pair(jid, contact));
|
||||
|
||||
} else {
|
||||
contact = itr->second;
|
||||
}
|
||||
|
||||
QSet<QString> gr = re.groups();
|
||||
Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
|
||||
contact->setGroups(gr);
|
||||
contact->setSubscriptionState(state);
|
||||
contact->setName(re.name());
|
||||
|
||||
if (newContact) {
|
||||
QMap<QString, QVariant> cData({
|
||||
{"name", re.name()},
|
||||
{"state", QVariant::fromValue(state)}
|
||||
});
|
||||
|
||||
careAboutAvatar(contact, cData);
|
||||
int grCount = 0;
|
||||
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
|
||||
const QString& groupName = *itr;
|
||||
addToGroup(jid, groupName);
|
||||
emit acc->addContact(jid, groupName, cData);
|
||||
grCount++;
|
||||
}
|
||||
|
||||
if (grCount == 0) {
|
||||
emit acc->addContact(jid, "", cData);
|
||||
}
|
||||
handleNewContact(contact);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
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)
|
||||
{
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
|
||||
if (itr != queuedContacts.end()) {
|
||||
qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is already queued for adding, skipping";
|
||||
} else {
|
||||
queuedContacts.insert(std::make_pair(jid, "")); //TODO need to add reason here;
|
||||
acc->rm->addItem(jid, name, groups);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is not in the connected state, skipping";
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (itr != outOfRosterContacts.end()) {
|
||||
outOfRosterContacts.erase(itr);
|
||||
onRosterItemRemoved(lcJid);
|
||||
} else {
|
||||
acc->rm->removeItem(lcJid);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "An attempt to remove contact " << lcJid << " from account " << acc->name << " but the account is not in the connected state, skipping";
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::handleNewConference(Core::Conference* contact)
|
||||
{
|
||||
handleNewRosterItem(contact);
|
||||
connect(contact, &Conference::nickChanged, this, &RosterHandler::onMucNickNameChanged);
|
||||
connect(contact, &Conference::subjectChanged, this, &RosterHandler::onMucSubjectChanged);
|
||||
connect(contact, &Conference::joinedChanged, this, &RosterHandler::onMucJoinedChanged);
|
||||
connect(contact, &Conference::autoJoinChanged, this, &RosterHandler::onMucAutoJoinChanged);
|
||||
connect(contact, &Conference::addParticipant, this, &RosterHandler::onMucAddParticipant);
|
||||
connect(contact, &Conference::changeParticipant, this, &RosterHandler::onMucChangeParticipant);
|
||||
connect(contact, &Conference::removeParticipant, this, &RosterHandler::onMucRemoveParticipant);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Conference* room = static_cast<Conference*>(sender());
|
||||
emit acc->addRoomParticipant(room->jid, nickName, data);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Conference* room = static_cast<Conference*>(sender());
|
||||
emit acc->changeRoomParticipant(room->jid, nickName, data);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName)
|
||||
{
|
||||
Conference* room = static_cast<Conference*>(sender());
|
||||
emit acc->removeRoomParticipant(room->jid, nickName);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucSubjectChanged(const QString& subject)
|
||||
{
|
||||
Conference* room = static_cast<Conference*>(sender());
|
||||
emit acc->changeRoom(room->jid, {
|
||||
{"subject", subject}
|
||||
});
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onContactGroupAdded(const QString& group)
|
||||
{
|
||||
Contact* contact = static_cast<Contact*>(sender());
|
||||
if (contact->groupsCount() == 1) {
|
||||
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
|
||||
}
|
||||
|
||||
QMap<QString, QVariant> cData({
|
||||
{"name", contact->getName()},
|
||||
{"state", QVariant::fromValue(contact->getSubscriptionState())}
|
||||
});
|
||||
addToGroup(contact->jid, group);
|
||||
emit acc->addContact(contact->jid, group, cData);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onContactGroupRemoved(const QString& group)
|
||||
{
|
||||
Contact* contact = static_cast<Contact*>(sender());
|
||||
if (contact->groupsCount() == 0) {
|
||||
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
|
||||
}
|
||||
|
||||
emit acc->removeContact(contact->jid, group);
|
||||
removeFromGroup(contact->jid, group);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onContactNameChanged(const QString& cname)
|
||||
{
|
||||
Contact* contact = static_cast<Contact*>(sender());
|
||||
QMap<QString, QVariant> cData({
|
||||
{"name", cname},
|
||||
});
|
||||
emit acc->changeContact(contact->jid, cData);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate)
|
||||
{
|
||||
Contact* contact = static_cast<Contact*>(sender());
|
||||
QMap<QString, QVariant> cData({
|
||||
{"state", QVariant::fromValue(cstate)},
|
||||
});
|
||||
emit acc->changeContact(contact->jid, cData);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
|
||||
{
|
||||
std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
|
||||
if (gItr == groups.end()) {
|
||||
gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
|
||||
emit acc->addGroup(group);
|
||||
}
|
||||
gItr->second.insert(jid.toLower());
|
||||
}
|
||||
|
||||
void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group)
|
||||
{
|
||||
QSet<QString> toRemove;
|
||||
std::map<QString, std::set<QString>>::iterator itr = groups.find(group);
|
||||
if (itr == groups.end()) {
|
||||
qDebug() << "An attempt to remove contact" << jid << "of account" << acc->name << "from non existing group" << group << ", skipping";
|
||||
return;
|
||||
}
|
||||
std::set<QString> contacts = itr->second;
|
||||
std::set<QString>::const_iterator cItr = contacts.find(jid.toLower());
|
||||
if (cItr != contacts.end()) {
|
||||
contacts.erase(cItr);
|
||||
if (contacts.size() == 0) {
|
||||
emit acc->removeGroup(group);
|
||||
groups.erase(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
|
||||
{
|
||||
RosterItem* item = 0;
|
||||
QString lcJid = jid.toLower();
|
||||
std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid);
|
||||
if (citr != contacts.end()) {
|
||||
item = citr->second;
|
||||
} else {
|
||||
std::map<QString, Conference*>::const_iterator coitr = conferences.find(lcJid);
|
||||
if (coitr != conferences.end()) {
|
||||
item = coitr->second;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
Core::Conference * Core::RosterHandler::getConference(const QString& jid)
|
||||
{
|
||||
Conference* item = 0;
|
||||
std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
|
||||
if (coitr != conferences.end()) {
|
||||
item = coitr->second;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
Core::Contact * Core::RosterHandler::getContact(const QString& jid)
|
||||
{
|
||||
Contact* item = 0;
|
||||
std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
|
||||
if (citr != contacts.end()) {
|
||||
item = citr->second;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid)
|
||||
{
|
||||
QString lcJid = jid.toLower();
|
||||
Contact* cnt = new Contact(lcJid, acc->name);
|
||||
contacts.insert(std::make_pair(lcJid, cnt));
|
||||
outOfRosterContacts.insert(lcJid);
|
||||
cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
|
||||
emit acc->addContact(lcJid, "", QMap<QString, QVariant>({
|
||||
{"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
|
||||
}));
|
||||
handleNewContact(cnt);
|
||||
return cnt;
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
|
||||
{
|
||||
QString lcJid = bareJid.toLower();
|
||||
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
|
||||
if (itr == contacts.end()) {
|
||||
qDebug() << "An attempt to change non existing contact" << lcJid << "from account" << acc->name << ", skipping";
|
||||
return;
|
||||
}
|
||||
Contact* contact = itr->second;
|
||||
QXmppRosterIq::Item re = acc->rm->getRosterEntry(lcJid);
|
||||
|
||||
Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
|
||||
|
||||
contact->setGroups(re.groups());
|
||||
contact->setSubscriptionState(state);
|
||||
contact->setName(re.name());
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid)
|
||||
{
|
||||
QString lcJid = bareJid.toLower();
|
||||
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
|
||||
if (itr == contacts.end()) {
|
||||
qDebug() << "An attempt to remove non existing contact" << lcJid << "from account" << acc->name << ", skipping";
|
||||
return;
|
||||
}
|
||||
Contact* contact = itr->second;
|
||||
contacts.erase(itr);
|
||||
QSet<QString> cGroups = contact->getGroups();
|
||||
for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) {
|
||||
removeFromGroup(lcJid, *itr);
|
||||
}
|
||||
emit acc->removeContact(lcJid);
|
||||
|
||||
contact->deleteLater();
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucRoomAdded(QXmppMucRoom* room)
|
||||
{
|
||||
qDebug() << "room" << room->jid() << "added with name" << room->name()
|
||||
<< ", account" << acc->getName() << "joined:" << room->isJoined();
|
||||
}
|
||||
|
||||
void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
|
||||
{
|
||||
QList<QXmppBookmarkConference> confs = bookmarks.conferences();
|
||||
for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) {
|
||||
const QXmppBookmarkConference& c = *itr;
|
||||
|
||||
QString jid = c.jid().toLower();
|
||||
std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
|
||||
if (cItr == conferences.end()) {
|
||||
addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
|
||||
} else {
|
||||
if (c.autoJoin()) {
|
||||
cItr->second->setJoined(true);
|
||||
} else {
|
||||
cItr->second->setAutoJoin(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucJoinedChanged(bool joined)
|
||||
{
|
||||
Conference* room = static_cast<Conference*>(sender());
|
||||
emit acc->changeRoom(room->jid, {
|
||||
{"joined", joined}
|
||||
});
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucAutoJoinChanged(bool autoJoin)
|
||||
{
|
||||
storeConferences();
|
||||
Conference* room = static_cast<Conference*>(sender());
|
||||
emit acc->changeRoom(room->jid, {
|
||||
{"autoJoin", autoJoin}
|
||||
});
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onMucNickNameChanged(const QString& nickName)
|
||||
{
|
||||
storeConferences();
|
||||
Conference* room = static_cast<Conference*>(sender());
|
||||
emit acc->changeRoom(room->jid, {
|
||||
{"nick", nickName}
|
||||
});
|
||||
}
|
||||
|
||||
Shared::SubscriptionState Core::RosterHandler::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs)
|
||||
{
|
||||
Shared::SubscriptionState state;
|
||||
if (qs == QXmppRosterIq::Item::NotSet) {
|
||||
state = Shared::SubscriptionState::unknown;
|
||||
} else {
|
||||
state = static_cast<Shared::SubscriptionState>(qs);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
void Core::RosterHandler::storeConferences()
|
||||
{
|
||||
QXmppBookmarkSet bms = acc->bm->bookmarks();
|
||||
QList<QXmppBookmarkConference> confs;
|
||||
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
|
||||
Conference* conference = itr->second;
|
||||
QXmppBookmarkConference conf;
|
||||
conf.setJid(conference->jid);
|
||||
conf.setName(conference->getName());
|
||||
conf.setNickName(conference->getNick());
|
||||
conf.setAutoJoin(conference->getAutoJoin());
|
||||
confs.push_back(conf);
|
||||
}
|
||||
bms.setConferences(confs);
|
||||
acc->bm->setBookmarks(bms);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::clearConferences()
|
||||
{
|
||||
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) {
|
||||
itr->second->deleteLater();
|
||||
emit acc->removeRoom(itr->first);
|
||||
}
|
||||
conferences.clear();
|
||||
}
|
||||
|
||||
void Core::RosterHandler::removeRoomRequest(const QString& jid)
|
||||
{
|
||||
QString lcJid = jid.toLower();
|
||||
std::map<QString, Conference*>::const_iterator itr = conferences.find(lcJid);
|
||||
if (itr == conferences.end()) {
|
||||
qDebug() << "An attempt to remove non existing room" << lcJid << "from account" << acc->name << ", skipping";
|
||||
}
|
||||
itr->second->deleteLater();
|
||||
conferences.erase(itr);
|
||||
emit acc->removeRoom(lcJid);
|
||||
storeConferences();
|
||||
}
|
||||
|
||||
void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin)
|
||||
{
|
||||
QString lcJid = jid.toLower();
|
||||
std::map<QString, Conference*>::const_iterator cItr = conferences.find(lcJid);
|
||||
if (cItr == conferences.end()) {
|
||||
addNewRoom(lcJid, nick, "", autoJoin);
|
||||
storeConferences();
|
||||
} else {
|
||||
qDebug() << "An attempt to add a MUC " << lcJid << " which is already present in the rester, skipping";
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName)
|
||||
{
|
||||
QString lcJid = jid.toLower();
|
||||
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
|
||||
if (itr == contacts.end()) {
|
||||
qDebug() << "An attempt to add non existing contact" << lcJid << "of account"
|
||||
<< acc->name << "to the group" << groupName << ", skipping";
|
||||
} else {
|
||||
QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
|
||||
QSet<QString> groups = item.groups();
|
||||
if (groups.find(groupName) == groups.end()) { //TODO need to change it, I guess that sort of code is better in qxmpp lib
|
||||
groups.insert(groupName);
|
||||
item.setGroups(groups);
|
||||
|
||||
QXmppRosterIq iq;
|
||||
iq.setType(QXmppIq::Set);
|
||||
iq.addItem(item);
|
||||
acc->client.sendPacket(iq);
|
||||
} else {
|
||||
qDebug() << "An attempt to add contact" << jid << "of account"
|
||||
<< acc->name << "to the group" << groupName << "but it's already in that group, skipping";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName)
|
||||
{
|
||||
QString lcJid = jid.toLower();
|
||||
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
|
||||
if (itr == contacts.end()) {
|
||||
qDebug() << "An attempt to remove non existing contact" << lcJid << "of account"
|
||||
<< acc->name << "from the group" << groupName << ", skipping";
|
||||
} else {
|
||||
QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
|
||||
QSet<QString> groups = item.groups();
|
||||
QSet<QString>::const_iterator gItr = groups.find(groupName);
|
||||
if (gItr != groups.end()) {
|
||||
groups.erase(gItr);
|
||||
item.setGroups(groups);
|
||||
|
||||
QXmppRosterIq iq;
|
||||
iq.setType(QXmppIq::Set);
|
||||
iq.addItem(item);
|
||||
acc->client.sendPacket(iq);
|
||||
} else {
|
||||
qDebug() << "An attempt to remove contact" << lcJid << "of account"
|
||||
<< acc->name << "from the group" << groupName << "but it's not in that group, skipping";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QString& path)
|
||||
{
|
||||
RosterItem* item = static_cast<RosterItem*>(sender());
|
||||
QMap<QString, QVariant> cData({
|
||||
{"avatarState", static_cast<uint>(type)},
|
||||
{"avatarPath", path}
|
||||
});
|
||||
|
||||
emit acc->changeContact(item->jid, cData);
|
||||
}
|
||||
|
||||
void Core::RosterHandler::handleOffline()
|
||||
{
|
||||
for (const std::pair<const QString, Conference*>& pair : conferences) {
|
||||
pair.second->clearArchiveRequests();
|
||||
pair.second->downgradeDatabaseState();
|
||||
}
|
||||
for (const std::pair<const QString, Contact*>& pair : contacts) {
|
||||
pair.second->clearArchiveRequests();
|
||||
pair.second->downgradeDatabaseState();
|
||||
}
|
||||
setPepSupport(false);
|
||||
}
|
||||
|
||||
|
||||
void Core::RosterHandler::setPepSupport(bool support)
|
||||
{
|
||||
if (pepSupport != support) {
|
||||
pepSupport = support;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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_ROSTERHANDLER_H
|
||||
#define CORE_ROSTERHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <QXmppBookmarkSet.h>
|
||||
#include <QXmppMucManager.h>
|
||||
#include <QXmppRosterIq.h>
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <core/contact.h>
|
||||
#include <core/conference.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
||||
class Account;
|
||||
|
||||
class RosterHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RosterHandler(Account* account);
|
||||
~RosterHandler();
|
||||
|
||||
void addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||
void removeContactRequest(const QString& jid);
|
||||
void addContactToGroupRequest(const QString& jid, const QString& groupName);
|
||||
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
|
||||
|
||||
void removeRoomRequest(const QString& jid);
|
||||
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void handleOffline();
|
||||
|
||||
Core::Contact* getContact(const QString& jid);
|
||||
Core::Conference* getConference(const QString& jid);
|
||||
Core::RosterItem* getRosterItem(const QString& jid);
|
||||
Core::Contact* addOutOfRosterContact(const QString& jid);
|
||||
|
||||
void storeConferences();
|
||||
void clearConferences();
|
||||
void setPepSupport(bool support);
|
||||
|
||||
private slots:
|
||||
void onRosterReceived();
|
||||
void onRosterItemAdded(const QString& bareJid);
|
||||
void onRosterItemChanged(const QString& bareJid);
|
||||
void onRosterItemRemoved(const QString& bareJid);
|
||||
|
||||
void onMucRoomAdded(QXmppMucRoom* room);
|
||||
void onMucJoinedChanged(bool joined);
|
||||
void onMucAutoJoinChanged(bool autoJoin);
|
||||
void onMucNickNameChanged(const QString& nickName);
|
||||
void onMucSubjectChanged(const QString& subject);
|
||||
void onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void onMucRemoveParticipant(const QString& nickName);
|
||||
|
||||
void bookmarksReceived(const QXmppBookmarkSet& bookmarks);
|
||||
|
||||
void onContactGroupAdded(const QString& group);
|
||||
void onContactGroupRemoved(const QString& group);
|
||||
void onContactNameChanged(const QString& name);
|
||||
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
||||
void onContactAvatarChanged(Shared::Avatar, const QString& path);
|
||||
|
||||
private:
|
||||
void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
|
||||
void addToGroup(const QString& jid, const QString& group);
|
||||
void removeFromGroup(const QString& jid, const QString& group);
|
||||
void addedAccount(const QString &bareJid);
|
||||
void handleNewRosterItem(Core::RosterItem* contact);
|
||||
void handleNewContact(Core::Contact* contact);
|
||||
void handleNewConference(Core::Conference* contact);
|
||||
void careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data);
|
||||
|
||||
static Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
std::map<QString, Contact*> contacts;
|
||||
std::map<QString, Conference*> conferences;
|
||||
std::map<QString, std::set<QString>> groups;
|
||||
std::map<QString, QString> queuedContacts;
|
||||
std::set<QString> outOfRosterContacts;
|
||||
bool pepSupport;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_ROSTERHANDLER_H
|
|
@ -0,0 +1,312 @@
|
|||
// 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 "vcardhandler.h"
|
||||
#include "core/account.h"
|
||||
|
||||
Core::VCardHandler::VCardHandler(Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
ownVCardRequestInProgress(false),
|
||||
pendingVCardRequests(),
|
||||
avatarHash(),
|
||||
avatarType()
|
||||
{
|
||||
connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived);
|
||||
//for some reason it doesn't work, launching from common handler
|
||||
//connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived);
|
||||
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + acc->name;
|
||||
QDir dir(path);
|
||||
|
||||
if (!dir.exists()) {
|
||||
bool res = dir.mkpath(path);
|
||||
if (!res) {
|
||||
qDebug() << "Couldn't create a cache directory for account" << acc->name;
|
||||
throw 22;
|
||||
}
|
||||
}
|
||||
|
||||
QFile* avatar = new QFile(path + "/avatar.png");
|
||||
QString type = "png";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpg");
|
||||
type = "jpg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpeg");
|
||||
type = "jpeg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.gif");
|
||||
type = "gif";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avatar->exists()) {
|
||||
if (avatar->open(QFile::ReadOnly)) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(avatar);
|
||||
avatarHash = sha1.result();
|
||||
avatarType = type;
|
||||
}
|
||||
}
|
||||
if (avatarType.size() != 0) {
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||
acc->presence.setPhotoHash(avatarHash.toUtf8());
|
||||
} else {
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
|
||||
}
|
||||
}
|
||||
|
||||
Core::VCardHandler::~VCardHandler()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QString id = card.from();
|
||||
QStringList comps = id.split("/");
|
||||
QString jid = comps.front().toLower();
|
||||
QString resource("");
|
||||
if (comps.size() > 1) {
|
||||
resource = comps.back();
|
||||
}
|
||||
pendingVCardRequests.erase(id);
|
||||
RosterItem* item = acc->rh->getRosterItem(jid);
|
||||
|
||||
if (item == 0) {
|
||||
if (jid == acc->getBareJid()) {
|
||||
onOwnVCardReceived(card);
|
||||
} else {
|
||||
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Shared::VCard vCard = item->handleResponseVCard(card, resource);
|
||||
|
||||
emit acc->receivedVCard(jid, vCard);
|
||||
}
|
||||
|
||||
void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QByteArray ava = card.photo();
|
||||
bool avaChanged = false;
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/";
|
||||
if (ava.size() > 0) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(ava);
|
||||
QString newHash(sha1.result());
|
||||
QMimeDatabase db;
|
||||
QMimeType newType = db.mimeTypeForData(ava);
|
||||
if (avatarType.size() > 0) {
|
||||
if (avatarHash != newHash) {
|
||||
QString oldPath = path + "avatar." + avatarType;
|
||||
QFile oldAvatar(oldPath);
|
||||
bool oldToRemove = false;
|
||||
if (oldAvatar.exists()) {
|
||||
if (oldAvatar.rename(oldPath + ".bak")) {
|
||||
oldToRemove = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << acc->name << "but can't get rid of the old one, doing nothing";
|
||||
}
|
||||
}
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
|
||||
if (oldToRemove) {
|
||||
qDebug() << "rolling back to the old avatar";
|
||||
if (!oldAvatar.rename(oldPath)) {
|
||||
qDebug() << "Couldn't roll back to the old avatar in account" << acc->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oldAvatar(path + "avatar." + avatarType);
|
||||
if (!oldAvatar.remove()) {
|
||||
qDebug() << "Received vCard for account" << acc->name << "without avatar, but can't get rid of the file, doing nothing";
|
||||
} else {
|
||||
avatarType = "";
|
||||
avatarHash = "";
|
||||
avaChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avaChanged) {
|
||||
QMap<QString, QVariant> change;
|
||||
if (avatarType.size() > 0) {
|
||||
acc->presence.setPhotoHash(avatarHash.toUtf8());
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||
change.insert("avatarPath", path + "avatar." + avatarType);
|
||||
} else {
|
||||
acc->presence.setPhotoHash("");
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
|
||||
change.insert("avatarPath", "");
|
||||
}
|
||||
acc->client.setClientPresence(acc->presence);
|
||||
emit acc->changed(change);
|
||||
}
|
||||
|
||||
ownVCardRequestInProgress = false;
|
||||
|
||||
Shared::VCard vCard;
|
||||
initializeVCard(vCard, card);
|
||||
|
||||
if (avatarType.size() > 0) {
|
||||
vCard.setAvatarType(Shared::Avatar::valid);
|
||||
vCard.setAvatarPath(path + "avatar." + avatarType);
|
||||
} else {
|
||||
vCard.setAvatarType(Shared::Avatar::empty);
|
||||
}
|
||||
|
||||
emit acc->receivedVCard(acc->getBareJid(), vCard);
|
||||
}
|
||||
|
||||
void Core::VCardHandler::handleOffline()
|
||||
{
|
||||
pendingVCardRequests.clear();
|
||||
Shared::VCard vCard; //just to show, that there is now more pending request
|
||||
for (const QString& jid : pendingVCardRequests) {
|
||||
emit acc->receivedVCard(jid, vCard); //need to show it better in the future, like with an error
|
||||
}
|
||||
pendingVCardRequests.clear();
|
||||
ownVCardRequestInProgress = false;
|
||||
}
|
||||
|
||||
void Core::VCardHandler::requestVCard(const QString& jid)
|
||||
{
|
||||
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
|
||||
qDebug() << "requesting vCard" << jid;
|
||||
if (jid == acc->getBareJid()) {
|
||||
if (!ownVCardRequestInProgress) {
|
||||
acc->vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
} else {
|
||||
acc->vm->requestVCard(jid);
|
||||
pendingVCardRequests.insert(jid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::VCardHandler::handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence)
|
||||
{
|
||||
if (!ownVCardRequestInProgress) {
|
||||
switch (p_presence.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 (avatarType.size() > 0) {
|
||||
acc->vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
|
||||
if (avatarHash != p_presence.photoHash()) {
|
||||
acc->vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::VCardHandler::uploadVCard(const Shared::VCard& card)
|
||||
{
|
||||
QXmppVCardIq iq;
|
||||
initializeQXmppVCard(iq, card);
|
||||
|
||||
if (card.getAvatarType() != Shared::Avatar::empty) {
|
||||
QString newPath = card.getAvatarPath();
|
||||
QString oldPath = getAvatarPath();
|
||||
QByteArray data;
|
||||
QString type;
|
||||
if (newPath != oldPath) {
|
||||
QFile avatar(newPath);
|
||||
if (!avatar.open(QFile::ReadOnly)) {
|
||||
qDebug() << "An attempt to upload new vCard to account" << acc->name
|
||||
<< "but it wasn't possible to read file" << newPath
|
||||
<< "which was supposed to be new avatar, uploading old avatar";
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oA(oldPath);
|
||||
if (!oA.open(QFile::ReadOnly)) {
|
||||
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
|
||||
} else {
|
||||
data = oA.readAll();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = avatar.readAll();
|
||||
}
|
||||
} else {
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oA(oldPath);
|
||||
if (!oA.open(QFile::ReadOnly)) {
|
||||
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
|
||||
} else {
|
||||
data = oA.readAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.size() > 0) {
|
||||
QMimeDatabase db;
|
||||
type = db.mimeTypeForData(data).name();
|
||||
iq.setPhoto(data);
|
||||
iq.setPhotoType(type);
|
||||
}
|
||||
}
|
||||
|
||||
acc->vm->setClientVCard(iq);
|
||||
onOwnVCardReceived(iq);
|
||||
}
|
||||
|
||||
QString Core::VCardHandler::getAvatarPath() const
|
||||
{
|
||||
if (avatarType.size() == 0) {
|
||||
return "";
|
||||
} else {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// 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_VCARDHANDLER_H
|
||||
#define CORE_VCARDHANDLER_H
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppPresence.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <shared/vcard.h>
|
||||
#include <core/adapterfunctions.h>
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Account;
|
||||
|
||||
class VCardHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VCardHandler(Account* account);
|
||||
~VCardHandler();
|
||||
|
||||
void handleOffline();
|
||||
void requestVCard(const QString& jid);
|
||||
void handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence);
|
||||
void uploadVCard(const Shared::VCard& card);
|
||||
QString getAvatarPath() const;
|
||||
|
||||
private slots:
|
||||
void onVCardReceived(const QXmppVCardIq& card);
|
||||
void onOwnVCardReceived(const QXmppVCardIq& card);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
|
||||
bool ownVCardRequestInProgress;
|
||||
std::set<QString> pendingVCardRequests;
|
||||
QString avatarHash;
|
||||
QString avatarType;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // CORE_VCARDHANDLER_H
|
|
@ -16,15 +16,23 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include "networkaccess.h"
|
||||
|
||||
Core::NetworkAccess::NetworkAccess(QObject* parent):
|
||||
QObject(parent),
|
||||
running(false),
|
||||
manager(0),
|
||||
files("files"),
|
||||
downloads()
|
||||
storage("fileURLStorage"),
|
||||
downloads(),
|
||||
uploads(),
|
||||
currentPath()
|
||||
{
|
||||
QSettings settings;
|
||||
currentPath = settings.value("downloadsPath").toString();
|
||||
}
|
||||
|
||||
Core::NetworkAccess::~NetworkAccess()
|
||||
|
@ -32,60 +40,31 @@ Core::NetworkAccess::~NetworkAccess()
|
|||
stop();
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
|
||||
void Core::NetworkAccess::downladFile(const QString& url)
|
||||
{
|
||||
std::map<QString, Download*>::iterator itr = downloads.find(url);
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
Download* dwn = itr->second;
|
||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
||||
if (mItr == dwn->messages.end()) {
|
||||
dwn->messages.insert(messageId);
|
||||
}
|
||||
emit downloadFileProgress(messageId, dwn->progress);
|
||||
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
|
||||
} else {
|
||||
try {
|
||||
QString path = files.getRecord(url);
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
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()) {
|
||||
emit downloadFileComplete(p.second, p.first);
|
||||
} else {
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} catch (Archive::NotFound e) {
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
} catch (Archive::Unknown e) {
|
||||
} catch (const Archive::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) {
|
||||
qDebug() << "Error requesting file path:" << e.what();
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
|
||||
{
|
||||
std::map<QString, Download*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
Download* dwn = itr->second;
|
||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
||||
if (mItr == dwn->messages.end()) {
|
||||
dwn->messages.insert(messageId);
|
||||
}
|
||||
emit downloadFileProgress(messageId, dwn->progress);
|
||||
} else {
|
||||
try {
|
||||
QString path = files.getRecord(url);
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
startDownload(messageId, url);
|
||||
}
|
||||
} catch (Archive::NotFound e) {
|
||||
startDownload(messageId, url);
|
||||
} catch (Archive::Unknown e) {
|
||||
qDebug() << "Error requesting file path:" << e.what();
|
||||
startDownload(messageId, url);
|
||||
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +73,10 @@ void Core::NetworkAccess::start()
|
|||
{
|
||||
if (!running) {
|
||||
manager = new QNetworkAccessManager();
|
||||
files.open();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
manager->setTransferTimeout();
|
||||
#endif
|
||||
storage.open();
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
|
@ -102,12 +84,12 @@ void Core::NetworkAccess::start()
|
|||
void Core::NetworkAccess::stop()
|
||||
{
|
||||
if (running) {
|
||||
files.close();
|
||||
storage.close();
|
||||
manager->deleteLater();
|
||||
manager = 0;
|
||||
running = false;
|
||||
|
||||
for (std::map<QString, Download*>::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) {
|
||||
for (std::map<QString, Transfer*>::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) {
|
||||
itr->second->success = false;
|
||||
itr->second->reply->abort(); //assuming it's gonna call onRequestFinished slot
|
||||
}
|
||||
|
@ -118,215 +100,490 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
|||
{
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request had some progress but seems like noone is waiting for it, skipping";
|
||||
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
Download* dwn = itr->second;
|
||||
qreal received = bytesReceived;
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
dwn->progress = progress;
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit downloadFileProgress(*mItr, progress);
|
||||
Transfer* dwn = itr->second;
|
||||
if (dwn->success) {
|
||||
qreal received = bytesReceived;
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
dwn->progress = progress;
|
||||
emit loadFileProgress(dwn->messages, progress, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code)
|
||||
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
||||
{
|
||||
qDebug() << "DEBUG: DOWNLOAD ERROR";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
qDebug() << rpl->errorString();
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
|
||||
qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
QString errorText;
|
||||
switch (code) {
|
||||
case QNetworkReply::NoError:
|
||||
//this never is supposed to happen
|
||||
break;
|
||||
|
||||
// network layer errors [relating to the destination server] (1-99):
|
||||
case QNetworkReply::ConnectionRefusedError:
|
||||
errorText = "Connection refused";
|
||||
break;
|
||||
case QNetworkReply::RemoteHostClosedError:
|
||||
errorText = "Remote server closed the connection";
|
||||
break;
|
||||
case QNetworkReply::HostNotFoundError:
|
||||
errorText = "Remote host is not found";
|
||||
break;
|
||||
case QNetworkReply::TimeoutError:
|
||||
errorText = "Connection was closed because it timed out";
|
||||
break;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
//this means I closed it myself by abort() or close(), don't think I need to notify here
|
||||
break;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
|
||||
break;
|
||||
case QNetworkReply::TemporaryNetworkFailureError:
|
||||
//this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify
|
||||
break;
|
||||
case QNetworkReply::NetworkSessionFailedError:
|
||||
errorText = "Outgoing connection problem";
|
||||
break;
|
||||
case QNetworkReply::BackgroundRequestNotAllowedError:
|
||||
errorText = "Background request is not allowed";
|
||||
break;
|
||||
case QNetworkReply::TooManyRedirectsError:
|
||||
errorText = "The request was redirected too many times";
|
||||
break;
|
||||
case QNetworkReply::InsecureRedirectError:
|
||||
errorText = "The request was redirected to insecure connection";
|
||||
break;
|
||||
case QNetworkReply::UnknownNetworkError:
|
||||
errorText = "Unknown network error";
|
||||
break;
|
||||
|
||||
// proxy errors (101-199):
|
||||
case QNetworkReply::ProxyConnectionRefusedError:
|
||||
errorText = "The connection to the proxy server was refused";
|
||||
break;
|
||||
case QNetworkReply::ProxyConnectionClosedError:
|
||||
errorText = "Proxy server closed the connection";
|
||||
break;
|
||||
case QNetworkReply::ProxyNotFoundError:
|
||||
errorText = "Proxy host was not found";
|
||||
break;
|
||||
case QNetworkReply::ProxyTimeoutError:
|
||||
errorText = "Connection to the proxy server was closed because it timed out";
|
||||
break;
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
errorText = "Couldn't connect to proxy server, authentication is required";
|
||||
break;
|
||||
case QNetworkReply::UnknownProxyError:
|
||||
errorText = "Unknown proxy error";
|
||||
break;
|
||||
|
||||
// content errors (201-299):
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
errorText = "The access to file is denied";
|
||||
break;
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
errorText = "The operation over requesting file is not permitted";
|
||||
break;
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
errorText = "The file was not found";
|
||||
break;
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
errorText = "Couldn't access the file, authentication is required";
|
||||
break;
|
||||
case QNetworkReply::ContentReSendError:
|
||||
errorText = "Sending error, one more attempt will probably solve this problem";
|
||||
break;
|
||||
case QNetworkReply::ContentConflictError:
|
||||
errorText = "The request could not be completed due to a conflict with the current state of the resource";
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError:
|
||||
errorText = "The requested resource is no longer available at the server";
|
||||
break;
|
||||
case QNetworkReply::UnknownContentError:
|
||||
errorText = "Unknown content error";
|
||||
break;
|
||||
|
||||
// protocol errors
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
errorText = "Unknown protocol error";
|
||||
break;
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
errorText = "Requested operation is not permitted in this protocol";
|
||||
break;
|
||||
case QNetworkReply::ProtocolFailure:
|
||||
errorText = "Low level protocol error";
|
||||
break;
|
||||
|
||||
// Server side errors (401-499)
|
||||
case QNetworkReply::InternalServerError:
|
||||
errorText = "Internal server error";
|
||||
break;
|
||||
case QNetworkReply::OperationNotImplementedError:
|
||||
errorText = "Server doesn't support requested operation";
|
||||
break;
|
||||
case QNetworkReply::ServiceUnavailableError:
|
||||
errorText = "The server is not available for this operation right now";
|
||||
break;
|
||||
case QNetworkReply::UnknownServerError:
|
||||
errorText = "Unknown server error";
|
||||
break;
|
||||
}
|
||||
if (errorText.size() > 0) {
|
||||
QString errorText = getErrorText(code);
|
||||
//if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Download* dwn = itr->second;
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit downloadFileError(*mItr, errorText);
|
||||
}
|
||||
}
|
||||
Transfer* dwn = itr->second;
|
||||
emit loadFileError(dwn->messages, errorText, false);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onRequestFinished()
|
||||
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
|
||||
{
|
||||
QString path("");
|
||||
qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
|
||||
for (const QSslError& err : errors) {
|
||||
qDebug() << err.errorString();
|
||||
}
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
|
||||
qDebug() << "an SSL error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
Download* dwn = itr->second;
|
||||
//if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* dwn = itr->second;
|
||||
emit loadFileError(dwn->messages, "SSL errors occured", false);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
{
|
||||
QString errorText("");
|
||||
switch (code) {
|
||||
case QNetworkReply::NoError:
|
||||
//this never is supposed to happen
|
||||
break;
|
||||
|
||||
// network layer errors [relating to the destination server] (1-99):
|
||||
case QNetworkReply::ConnectionRefusedError:
|
||||
errorText = "Connection refused";
|
||||
break;
|
||||
case QNetworkReply::RemoteHostClosedError:
|
||||
errorText = "Remote server closed the connection";
|
||||
break;
|
||||
case QNetworkReply::HostNotFoundError:
|
||||
errorText = "Remote host is not found";
|
||||
break;
|
||||
case QNetworkReply::TimeoutError:
|
||||
errorText = "Connection was closed because it timed out";
|
||||
break;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
//this means I closed it myself by abort() or close()
|
||||
//I don't call them directory, but this is the error code
|
||||
//Qt returns when it can not resume donwload after the network failure
|
||||
//or when the download is canceled by the timout;
|
||||
errorText = "Connection lost";
|
||||
break;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
|
||||
break;
|
||||
case QNetworkReply::TemporaryNetworkFailureError:
|
||||
//this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify
|
||||
break;
|
||||
case QNetworkReply::NetworkSessionFailedError:
|
||||
errorText = "Outgoing connection problem";
|
||||
break;
|
||||
case QNetworkReply::BackgroundRequestNotAllowedError:
|
||||
errorText = "Background request is not allowed";
|
||||
break;
|
||||
case QNetworkReply::TooManyRedirectsError:
|
||||
errorText = "The request was redirected too many times";
|
||||
break;
|
||||
case QNetworkReply::InsecureRedirectError:
|
||||
errorText = "The request was redirected to insecure connection";
|
||||
break;
|
||||
case QNetworkReply::UnknownNetworkError:
|
||||
errorText = "Unknown network error";
|
||||
break;
|
||||
|
||||
// proxy errors (101-199):
|
||||
case QNetworkReply::ProxyConnectionRefusedError:
|
||||
errorText = "The connection to the proxy server was refused";
|
||||
break;
|
||||
case QNetworkReply::ProxyConnectionClosedError:
|
||||
errorText = "Proxy server closed the connection";
|
||||
break;
|
||||
case QNetworkReply::ProxyNotFoundError:
|
||||
errorText = "Proxy host was not found";
|
||||
break;
|
||||
case QNetworkReply::ProxyTimeoutError:
|
||||
errorText = "Connection to the proxy server was closed because it timed out";
|
||||
break;
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
errorText = "Couldn't connect to proxy server, authentication is required";
|
||||
break;
|
||||
case QNetworkReply::UnknownProxyError:
|
||||
errorText = "Unknown proxy error";
|
||||
break;
|
||||
|
||||
// content errors (201-299):
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
errorText = "The access to file is denied";
|
||||
break;
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
errorText = "The operation over requesting file is not permitted";
|
||||
break;
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
errorText = "The file was not found";
|
||||
break;
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
errorText = "Couldn't access the file, authentication is required";
|
||||
break;
|
||||
case QNetworkReply::ContentReSendError:
|
||||
errorText = "Sending error, one more attempt will probably solve this problem";
|
||||
break;
|
||||
case QNetworkReply::ContentConflictError:
|
||||
errorText = "The request could not be completed due to a conflict with the current state of the resource";
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError:
|
||||
errorText = "The requested resource is no longer available at the server";
|
||||
break;
|
||||
case QNetworkReply::UnknownContentError:
|
||||
errorText = "Unknown content error";
|
||||
break;
|
||||
|
||||
// protocol errors
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
errorText = "Unknown protocol error";
|
||||
break;
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
errorText = "Requested operation is not permitted in this protocol";
|
||||
break;
|
||||
case QNetworkReply::ProtocolFailure:
|
||||
errorText = "Low level protocol error";
|
||||
break;
|
||||
|
||||
// Server side errors (401-499)
|
||||
case QNetworkReply::InternalServerError:
|
||||
errorText = "Internal server error";
|
||||
break;
|
||||
case QNetworkReply::OperationNotImplementedError:
|
||||
errorText = "Server doesn't support requested operation";
|
||||
break;
|
||||
case QNetworkReply::ServiceUnavailableError:
|
||||
errorText = "The server is not available for this operation right now";
|
||||
break;
|
||||
case QNetworkReply::UnknownServerError:
|
||||
errorText = "Unknown server error";
|
||||
break;
|
||||
}
|
||||
return errorText;
|
||||
}
|
||||
|
||||
|
||||
void Core::NetworkAccess::onDownloadFinished()
|
||||
{
|
||||
qDebug() << "DEBUG: DOWNLOAD FINISHED";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
|
||||
} else {
|
||||
Transfer* dwn = itr->second;
|
||||
if (dwn->success) {
|
||||
qDebug() << "download success for" << url;
|
||||
QString err;
|
||||
QStringList hops = url.split("/");
|
||||
QString fileName = hops.back();
|
||||
QStringList parts = fileName.split(".");
|
||||
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
QString suffix("");
|
||||
QString realName = parts.front();
|
||||
for (QStringList::const_iterator sItr = (parts.begin()++), sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
||||
suffix += "." + (*sItr);
|
||||
QString jid;
|
||||
if (dwn->messages.size() > 0) {
|
||||
jid = dwn->messages.front().jid;
|
||||
} 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 postfix("");
|
||||
QFileInfo proposedName(path + realName + postfix + suffix);
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
suffix = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(path + realName + postfix + suffix);
|
||||
QString path = prepareDirectory(jid);
|
||||
if (path.size() > 0) {
|
||||
path = checkFileName(fileName, path);
|
||||
|
||||
QFile file(Shared::resolvePath(path));
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dwn->reply->readAll());
|
||||
file.close();
|
||||
storage.setPath(url, path);
|
||||
qDebug() << "file" << path << "was successfully downloaded";
|
||||
} else {
|
||||
qDebug() << "couldn't save file" << path;
|
||||
err = "Error opening file to write:" + file.errorString();
|
||||
}
|
||||
} else {
|
||||
err = "Couldn't prepare a directory for file";
|
||||
}
|
||||
|
||||
path = proposedName.absoluteFilePath();
|
||||
QFile file(path);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dwn->reply->readAll());
|
||||
file.close();
|
||||
files.addRecord(url, path);
|
||||
qDebug() << "file" << path << "was successfully downloaded";
|
||||
if (path.size() > 0) {
|
||||
emit downloadFileComplete(dwn->messages, path);
|
||||
} else {
|
||||
qDebug() << "couldn't save file" << path;
|
||||
path = "";
|
||||
emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit fileLocalPathResponse(*mItr, path);
|
||||
}
|
||||
|
||||
dwn->reply->deleteLater();
|
||||
delete dwn;
|
||||
downloads.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
|
||||
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
|
||||
{
|
||||
Download* dwn = new Download({{messageId}, 0, 0, true});
|
||||
Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
|
||||
QNetworkRequest req(url);
|
||||
dwn->reply = manager->get(req);
|
||||
connect(dwn->reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64)));
|
||||
connect(dwn->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError)));
|
||||
connect(dwn->reply, SIGNAL(finished()), SLOT(onRequestFinished()));
|
||||
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
|
||||
connect(dwn->reply, &QNetworkReply::sslErrors, this, &NetworkAccess::onDownloadSSLError);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
|
||||
#else
|
||||
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onDownloadError);
|
||||
#endif
|
||||
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
|
||||
downloads.insert(std::make_pair(url, dwn));
|
||||
emit downloadFileProgress(messageId, 0);
|
||||
emit loadFileProgress(dwn->messages, 0, false);
|
||||
}
|
||||
|
||||
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);
|
||||
if (itr == uploads.end()) {
|
||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
|
||||
} else {
|
||||
QString errorText = getErrorText(code);
|
||||
//if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* upl = itr->second;
|
||||
emit loadFileError(upl->messages, errorText, true);
|
||||
//}
|
||||
|
||||
//TODO deletion?
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
|
||||
} else {
|
||||
Transfer* upl = itr->second;
|
||||
if (upl->success) {
|
||||
qDebug() << "upload success for" << url;
|
||||
|
||||
// Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
|
||||
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 + 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, Shared::resolvePath(newPath));
|
||||
|
||||
if (copyResult) {
|
||||
// Change storage
|
||||
upl->path = newPath;
|
||||
} else {
|
||||
err = "copying to " + newPath + " failed";
|
||||
}
|
||||
} else {
|
||||
err = "Couldn't prepare a directory for file";
|
||||
}
|
||||
|
||||
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();
|
||||
upl->file->close();
|
||||
upl->file->deleteLater();
|
||||
delete upl;
|
||||
uploads.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (itr == uploads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
Transfer* upl = itr->second;
|
||||
if (upl->success) {
|
||||
qreal received = bytesReceived;
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
upl->progress = progress;
|
||||
emit loadFileProgress(upl->messages, progress, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
||||
{
|
||||
QString p = Shared::squawkifyPath(path);
|
||||
|
||||
try {
|
||||
p = storage.getUrl(p);
|
||||
} catch (const Archive::NotFound& err) {
|
||||
p = "";
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
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);
|
||||
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
|
||||
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
|
||||
}
|
||||
if (file->open(QIODevice::ReadOnly)) {
|
||||
upl->reply = manager->put(req, file);
|
||||
|
||||
connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
|
||||
#else
|
||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
|
||||
#endif
|
||||
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
||||
uploads.insert(std::make_pair(put.toString(), upl));
|
||||
emit loadFileProgress(upl->messages, 0, true);
|
||||
} else {
|
||||
qDebug() << "couldn't upload file" << path;
|
||||
emit loadFileError(upl->messages, "Error opening file", true);
|
||||
delete file;
|
||||
delete upl;
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
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)
|
||||
{
|
||||
storage.addFile(url, path, account, jid, id);
|
||||
}
|
||||
|
||||
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) {
|
||||
std::list<Shared::MessageInfo>& messages = info->messages;
|
||||
bool dup = false;
|
||||
for (const Shared::MessageInfo& info : messages) {
|
||||
if (info.account == acc && info.jid == jid && info.messageId == id) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup) {
|
||||
info->messages.emplace_back(acc, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||
{
|
||||
QString path = currentPath;
|
||||
QString addition;
|
||||
if (jid.size() > 0) {
|
||||
addition = jid;
|
||||
path += QDir::separator() + jid;
|
||||
}
|
||||
|
||||
QDir location(path);
|
||||
|
||||
if (!location.exists()) {
|
||||
bool res = location.mkpath(path);
|
||||
if (!res) {
|
||||
return "";
|
||||
} else {
|
||||
return "squawk://" + addition;
|
||||
}
|
||||
}
|
||||
return "squawk://" + addition;
|
||||
}
|
||||
|
||||
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) {
|
||||
suffix += "." + (*sItr);
|
||||
}
|
||||
QString postfix("");
|
||||
QString resolvedPath = Shared::resolvePath(path);
|
||||
QString count("");
|
||||
QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||
}
|
||||
|
||||
return path + QDir::separator() + realName + count + suffix;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return storage.deletedFile(path);
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
|
||||
{
|
||||
QDir dir(currentPath);
|
||||
bool success = true;
|
||||
qDebug() << "moving" << currentPath << "to" << newPath;
|
||||
for (QFileInfo fileInfo : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
|
||||
QString fileName = fileInfo.fileName();
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -26,20 +26,24 @@
|
|||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "storage.h"
|
||||
#include "storage/urlstorage.h"
|
||||
#include "shared/pathcheck.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
|
||||
{
|
||||
Q_OBJECT
|
||||
struct Download;
|
||||
struct Transfer;
|
||||
public:
|
||||
NetworkAccess(QObject* parent = nullptr);
|
||||
virtual ~NetworkAccess();
|
||||
|
@ -47,34 +51,55 @@ public:
|
|||
void start();
|
||||
void stop();
|
||||
|
||||
QString getFileRemoteUrl(const QString& path);
|
||||
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
|
||||
bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
|
||||
std::list<Shared::MessageInfo> reportPathInvalid(const QString& path);
|
||||
|
||||
signals:
|
||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||
void downloadFileProgress(const QString& messageId, qreal value);
|
||||
void downloadFileError(const QString& messageId, const QString& path);
|
||||
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
|
||||
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
|
||||
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
|
||||
public slots:
|
||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downladFileRequest(const QString& messageId, const QString& url);
|
||||
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 QString& messageId, const QString& url);
|
||||
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
QString getErrorText(QNetworkReply::NetworkError code);
|
||||
QString prepareDirectory(const QString& jid);
|
||||
QString checkFileName(const QString& name, const QString& path);
|
||||
|
||||
private slots:
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onRequestError(QNetworkReply::NetworkError code);
|
||||
void onRequestFinished();
|
||||
void onDownloadError(QNetworkReply::NetworkError code);
|
||||
void onDownloadSSLError(const QList<QSslError> &errors);
|
||||
void onDownloadFinished();
|
||||
void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onUploadError(QNetworkReply::NetworkError code);
|
||||
void onUploadFinished();
|
||||
|
||||
private:
|
||||
bool running;
|
||||
QNetworkAccessManager* manager;
|
||||
Storage files;
|
||||
std::map<QString, Download*> downloads;
|
||||
UrlStorage storage;
|
||||
std::map<QString, Transfer*> downloads;
|
||||
std::map<QString, Transfer*> uploads;
|
||||
QString currentPath;
|
||||
|
||||
struct Download {
|
||||
std::set<QString> messages;
|
||||
struct Transfer {
|
||||
std::list<Shared::MessageInfo> messages;
|
||||
qreal progress;
|
||||
QNetworkReply* reply;
|
||||
bool success;
|
||||
QString path;
|
||||
QString url;
|
||||
QFile* file;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
if (WITH_KWALLET)
|
||||
target_sources(squawk PRIVATE
|
||||
kwallet.cpp
|
||||
kwallet.h
|
||||
)
|
||||
|
||||
add_subdirectory(wrappers)
|
||||
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
endif ()
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* 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 "kwallet.h"
|
||||
|
||||
Core::PSE::KWallet::OpenWallet Core::PSE::KWallet::openWallet = 0;
|
||||
Core::PSE::KWallet::NetworkWallet Core::PSE::KWallet::networkWallet = 0;
|
||||
Core::PSE::KWallet::DeleteWallet Core::PSE::KWallet::deleteWallet = 0;
|
||||
Core::PSE::KWallet::ReadPassword Core::PSE::KWallet::readPassword = 0;
|
||||
Core::PSE::KWallet::WritePassword Core::PSE::KWallet::writePassword = 0;
|
||||
Core::PSE::KWallet::HasFolder Core::PSE::KWallet::hasFolder = 0;
|
||||
Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0;
|
||||
Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0;
|
||||
|
||||
Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial;
|
||||
QLibrary Core::PSE::KWallet::lib("kwalletWrapper");
|
||||
|
||||
Core::PSE::KWallet::KWallet():
|
||||
QObject(),
|
||||
cState(disconnected),
|
||||
everError(false),
|
||||
wallet(0),
|
||||
readRequest(),
|
||||
writeRequest()
|
||||
{
|
||||
if (sState == initial) {
|
||||
lib.load();
|
||||
|
||||
if (lib.isLoaded()) {
|
||||
openWallet = (OpenWallet) lib.resolve("openWallet");
|
||||
networkWallet = (NetworkWallet) lib.resolve("networkWallet");
|
||||
deleteWallet = (DeleteWallet) lib.resolve("deleteWallet");
|
||||
readPassword = (ReadPassword) lib.resolve("readPassword");
|
||||
writePassword = (WritePassword) lib.resolve("writePassword");
|
||||
hasFolder = (HasFolder) lib.resolve("hasFolder");
|
||||
createFolder = (CreateFolder) lib.resolve("createFolder");
|
||||
setFolder = (SetFolder) lib.resolve("setFolder");
|
||||
|
||||
if (openWallet
|
||||
&& networkWallet
|
||||
&& deleteWallet
|
||||
&& readPassword
|
||||
&& writePassword
|
||||
) {
|
||||
sState = success;
|
||||
} else {
|
||||
sState = failure;
|
||||
}
|
||||
} else {
|
||||
sState = failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core::PSE::KWallet::~KWallet()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::open()
|
||||
{
|
||||
if (sState == success) {
|
||||
if (cState == disconnected) {
|
||||
QString name;
|
||||
networkWallet(name);
|
||||
wallet = openWallet(name, 0, ::KWallet::Wallet::Asynchronous);
|
||||
if (wallet) {
|
||||
cState = connecting;
|
||||
connect(wallet, SIGNAL(walletOpened(bool)), this, SLOT(onWalletOpened(bool)));
|
||||
connect(wallet, SIGNAL(walletClosed()), this, SLOT(onWalletClosed()));
|
||||
} else {
|
||||
everError = true;
|
||||
emit opened(false);
|
||||
rejectPending();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core::PSE::KWallet::ConnectionState Core::PSE::KWallet::connectionState()
|
||||
{
|
||||
return cState;
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::close()
|
||||
{
|
||||
if (sState == success) {
|
||||
if (cState != disconnected) {
|
||||
deleteWallet(wallet);
|
||||
wallet = 0;
|
||||
}
|
||||
rejectPending();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::onWalletClosed()
|
||||
{
|
||||
cState = disconnected;
|
||||
deleteWallet(wallet);
|
||||
wallet = 0;
|
||||
emit closed();
|
||||
rejectPending();
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::onWalletOpened(bool success)
|
||||
{
|
||||
emit opened(success);
|
||||
if (success) {
|
||||
QString appName = QCoreApplication::applicationName();
|
||||
if (!hasFolder(wallet, appName)) {
|
||||
createFolder(wallet, appName);
|
||||
}
|
||||
setFolder(wallet, appName);
|
||||
cState = connected;
|
||||
readPending();
|
||||
} else {
|
||||
everError = true;
|
||||
cState = disconnected;
|
||||
deleteWallet(wallet);
|
||||
wallet = 0;
|
||||
rejectPending();
|
||||
}
|
||||
}
|
||||
|
||||
Core::PSE::KWallet::SupportState Core::PSE::KWallet::supportState()
|
||||
{
|
||||
return sState;
|
||||
}
|
||||
|
||||
bool Core::PSE::KWallet::everHadError() const
|
||||
{
|
||||
return everError;
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::resetEverHadError()
|
||||
{
|
||||
everError = false;
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::requestReadPassword(const QString& login, bool askAgain)
|
||||
{
|
||||
if (sState == success) {
|
||||
readRequest.insert(login);
|
||||
readSwitch(askAgain);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::requestWritePassword(const QString& login, const QString& password, bool askAgain)
|
||||
{
|
||||
if (sState == success) {
|
||||
std::map<QString, QString>::iterator itr = writeRequest.find(login);
|
||||
if (itr == writeRequest.end()) {
|
||||
writeRequest.insert(std::make_pair(login, password));
|
||||
} else {
|
||||
itr->second = password;
|
||||
}
|
||||
readSwitch(askAgain);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::readSwitch(bool askAgain)
|
||||
{
|
||||
switch (cState) {
|
||||
case connected:
|
||||
readPending();
|
||||
break;
|
||||
case connecting:
|
||||
break;
|
||||
case disconnected:
|
||||
if (!everError || askAgain) {
|
||||
open();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::rejectPending()
|
||||
{
|
||||
writeRequest.clear();
|
||||
std::set<QString>::const_iterator i = readRequest.begin();
|
||||
while (i != readRequest.end()) {
|
||||
emit rejectPassword(*i);
|
||||
readRequest.erase(i);
|
||||
i = readRequest.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::PSE::KWallet::readPending()
|
||||
{
|
||||
std::map<QString, QString>::const_iterator itr = writeRequest.begin();
|
||||
while (itr != writeRequest.end()) {
|
||||
int result = writePassword(wallet, itr->first, itr->second);
|
||||
if (result == 0) {
|
||||
qDebug() << "Successfully saved password for user" << itr->first;
|
||||
} else {
|
||||
qDebug() << "Error writing password for user" << itr->first << ":" << result;
|
||||
}
|
||||
writeRequest.erase(itr);
|
||||
itr = writeRequest.begin();
|
||||
}
|
||||
|
||||
std::set<QString>::const_iterator i = readRequest.begin();
|
||||
while (i != readRequest.end()) {
|
||||
QString password;
|
||||
int result = readPassword(wallet, *i, password);
|
||||
if (result == 0 && password.size() > 0) { //even though it's written that the error is supposed to be returned in case there were no password
|
||||
emit responsePassword(*i, password); //it doesn't do so. I assume empty password as a lack of password in KWallet
|
||||
} else {
|
||||
emit rejectPassword(*i);
|
||||
}
|
||||
readRequest.erase(i);
|
||||
i = readRequest.begin();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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_PSE_KWALLET_H
|
||||
#define CORE_PSE_KWALLET_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QLibrary>
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <KF5/KWallet/KWallet>
|
||||
|
||||
namespace Core {
|
||||
namespace PSE {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class KWallet : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum SupportState {
|
||||
initial,
|
||||
success,
|
||||
failure
|
||||
};
|
||||
enum ConnectionState {
|
||||
disconnected,
|
||||
connecting,
|
||||
connected
|
||||
};
|
||||
|
||||
KWallet();
|
||||
~KWallet();
|
||||
|
||||
static SupportState supportState();
|
||||
void open();
|
||||
void close();
|
||||
ConnectionState connectionState();
|
||||
bool everHadError() const;
|
||||
void resetEverHadError();
|
||||
void requestReadPassword(const QString& login, bool askAgain = false);
|
||||
void requestWritePassword(const QString& login, const QString& password, bool askAgain = false);
|
||||
|
||||
signals:
|
||||
void opened(bool success);
|
||||
void closed();
|
||||
void responsePassword(const QString& login, const QString& password);
|
||||
void rejectPassword(const QString& login);
|
||||
|
||||
private slots:
|
||||
void onWalletOpened(bool success);
|
||||
void onWalletClosed();
|
||||
|
||||
private:
|
||||
void readSwitch(bool askAgain);
|
||||
void readPending();
|
||||
void rejectPending();
|
||||
|
||||
private:
|
||||
typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType);
|
||||
typedef void (*NetworkWallet)(QString&);
|
||||
typedef void (*DeleteWallet)(::KWallet::Wallet*);
|
||||
typedef int (*ReadPassword)(::KWallet::Wallet*, const QString&, QString&);
|
||||
typedef int (*WritePassword)(::KWallet::Wallet*, const QString&, const QString&);
|
||||
typedef bool (*HasFolder)(::KWallet::Wallet* w, const QString &f);
|
||||
typedef bool (*CreateFolder)(::KWallet::Wallet* w, const QString &f);
|
||||
typedef bool (*SetFolder)(::KWallet::Wallet* w, const QString &f);
|
||||
|
||||
static OpenWallet openWallet;
|
||||
static NetworkWallet networkWallet;
|
||||
static DeleteWallet deleteWallet;
|
||||
static ReadPassword readPassword;
|
||||
static WritePassword writePassword;
|
||||
static HasFolder hasFolder;
|
||||
static CreateFolder createFolder;
|
||||
static SetFolder setFolder;
|
||||
|
||||
static SupportState sState;
|
||||
static QLibrary lib;
|
||||
|
||||
ConnectionState cState;
|
||||
bool everError;
|
||||
::KWallet::Wallet* wallet;
|
||||
|
||||
std::set<QString> readRequest;
|
||||
std::map<QString, QString> writeRequest;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // CORE_PSE_KWALLET_H
|
|
@ -0,0 +1,4 @@
|
|||
add_library(kwalletWrapper SHARED kwallet.cpp)
|
||||
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
|
||||
|
||||
install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 <KF5/KWallet/KWallet>
|
||||
|
||||
extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
|
||||
return KWallet::Wallet::openWallet(name, w, ot);
|
||||
}
|
||||
|
||||
extern "C" void deleteWallet(KWallet::Wallet* w) {
|
||||
w->deleteLater();
|
||||
}
|
||||
|
||||
extern "C" void networkWallet(QString& str) {
|
||||
str = KWallet::Wallet::NetworkWallet();
|
||||
}
|
||||
|
||||
extern "C" int readPassword(KWallet::Wallet* w, const QString &key, QString &value) {
|
||||
return w->readPassword(key, value);
|
||||
}
|
||||
|
||||
extern "C" int writePassword(KWallet::Wallet* w, const QString &key, const QString &value) {
|
||||
return w->writePassword(key, value);
|
||||
}
|
||||
|
||||
extern "C" bool hasFolder(KWallet::Wallet* w, const QString &f) {
|
||||
return w->hasFolder(f);
|
||||
}
|
||||
|
||||
extern "C" bool createFolder(KWallet::Wallet* w, const QString &f) {
|
||||
return w->createFolder(f);
|
||||
}
|
||||
|
||||
extern "C" bool setFolder(KWallet::Wallet* w, const QString &f) {
|
||||
return w->setFolder(f);
|
||||
}
|
|
@ -17,12 +17,14 @@
|
|||
*/
|
||||
|
||||
#include "rosteritem.h"
|
||||
#include "account.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Core::RosterItem::RosterItem(const QString& pJid, const QString& account, QObject* parent):
|
||||
Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObject* parent):
|
||||
QObject(parent),
|
||||
jid(pJid),
|
||||
account(pAccount),
|
||||
name(),
|
||||
archiveState(empty),
|
||||
archive(new Archive(jid)),
|
||||
|
@ -33,6 +35,7 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& account, QObjec
|
|||
appendCache(),
|
||||
responseCache(),
|
||||
requestCache(),
|
||||
toCorrect(),
|
||||
muc(false)
|
||||
{
|
||||
archive->open(account);
|
||||
|
@ -73,6 +76,36 @@ void Core::RosterItem::addMessageToArchive(const Shared::Message& msg)
|
|||
{
|
||||
if (msg.storable()) {
|
||||
hisoryCache.push_back(msg);
|
||||
std::map<QString, Shared::Message>::iterator itr = toCorrect.find(msg.getId());
|
||||
if (itr != toCorrect.end()) {
|
||||
hisoryCache.back().change({
|
||||
{"body", itr->second.getBody()},
|
||||
{"stamp", itr->second.getTime()}
|
||||
});
|
||||
toCorrect.erase(itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterItem::correctMessageInArchive(const QString& originalId, const Shared::Message& msg)
|
||||
{
|
||||
if (msg.storable()) {
|
||||
QDateTime thisTime = msg.getTime();
|
||||
std::map<QString, Shared::Message>::iterator itr = toCorrect.find(originalId);
|
||||
if (itr != toCorrect.end()) {
|
||||
if (itr->second.getTime() < thisTime) {
|
||||
itr->second = msg;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool found = changeMessage(originalId, {
|
||||
{"body", msg.getBody()},
|
||||
{"stamp", thisTime}
|
||||
});
|
||||
if (!found) {
|
||||
toCorrect.insert(std::make_pair(originalId, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +122,26 @@ void Core::RosterItem::nextRequest()
|
|||
{
|
||||
if (syncronizing) {
|
||||
if (requestedCount != -1) {
|
||||
emit historyResponse(responseCache);
|
||||
bool last = false;
|
||||
if (archiveState == beginning || archiveState == complete) {
|
||||
try {
|
||||
QString firstId = archive->oldestId();
|
||||
if (responseCache.size() == 0) {
|
||||
if (requestedBefore == firstId) {
|
||||
last = true;
|
||||
}
|
||||
} else {
|
||||
if (responseCache.front().getId() == firstId) {
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
} catch (const Archive::Empty& e) {
|
||||
last = true;
|
||||
}
|
||||
} else if (archiveState == empty && responseCache.size() == 0) {
|
||||
last = true;
|
||||
}
|
||||
emit historyResponse(responseCache, last);
|
||||
}
|
||||
}
|
||||
if (requestCache.size() > 0) {
|
||||
|
@ -123,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before)
|
|||
requestCache.emplace_back(requestedCount, before);
|
||||
requestedCount = -1;
|
||||
}
|
||||
Shared::Message msg = archive->newest();
|
||||
emit needHistory("", msg.getId(), msg.getTime());
|
||||
try {
|
||||
Shared::Message msg = archive->newest();
|
||||
emit needHistory("", getId(msg), msg.getTime());
|
||||
} catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example)
|
||||
emit needHistory(before, "");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case end:
|
||||
|
@ -140,39 +196,49 @@ void Core::RosterItem::performRequest(int count, const QString& before)
|
|||
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), lBefore);
|
||||
responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
|
||||
found = true;
|
||||
} catch (Archive::NotFound e) {
|
||||
} catch (const Archive::NotFound& e) {
|
||||
requestCache.emplace_back(requestedCount, before);
|
||||
requestedCount = -1;
|
||||
emit needHistory(archive->oldestId(), "");
|
||||
} catch (Archive::Empty e) {
|
||||
emit needHistory(getId(archive->oldest()), "");
|
||||
} catch (const Archive::Empty& e) {
|
||||
requestCache.emplace_back(requestedCount, before);
|
||||
requestedCount = -1;
|
||||
emit needHistory(archive->oldestId(), "");
|
||||
emit needHistory(getId(archive->oldest()), "");
|
||||
}
|
||||
|
||||
if (found) {
|
||||
int rSize = responseCache.size();
|
||||
if (rSize < count) {
|
||||
if (rSize != 0) {
|
||||
emit needHistory(responseCache.front().getId(), "");
|
||||
emit needHistory(getId(responseCache.front()), "");
|
||||
} else {
|
||||
emit needHistory(before, "");
|
||||
QString bf;
|
||||
if (muc) {
|
||||
bf = archive->stanzaIdById(before);
|
||||
if (bf.size() < 0) {
|
||||
qDebug() << "Didn't find stanzaId for id requesting history for" << jid << ", falling back to requesting by id";
|
||||
bf = before;
|
||||
}
|
||||
} else {
|
||||
bf = before;
|
||||
}
|
||||
emit needHistory(bf, "");
|
||||
}
|
||||
} else {
|
||||
nextRequest();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emit needHistory(archive->oldestId(), "");
|
||||
emit needHistory(getId(archive->oldest()), "");
|
||||
}
|
||||
break;
|
||||
case complete:
|
||||
try {
|
||||
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
|
||||
responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
|
||||
} catch (Archive::NotFound e) {
|
||||
} catch (const Archive::NotFound& e) {
|
||||
qDebug("requesting id hasn't been found in archive, skipping");
|
||||
} catch (Archive::Empty e) {
|
||||
} catch (const Archive::Empty& e) {
|
||||
qDebug("requesting id hasn't been found in archive, skipping");
|
||||
}
|
||||
nextRequest();
|
||||
|
@ -180,10 +246,20 @@ void Core::RosterItem::performRequest(int count, const QString& before)
|
|||
}
|
||||
}
|
||||
|
||||
QString Core::RosterItem::getId(const Shared::Message& msg)
|
||||
{
|
||||
QString id;
|
||||
if (muc) {
|
||||
id = msg.getStanzaId();
|
||||
} else {
|
||||
id = msg.getId();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
|
||||
{
|
||||
const QString& id = msg.getId();
|
||||
if (id.size() > 0) {
|
||||
if (msg.getId().size() > 0) {
|
||||
if (msg.storable()) {
|
||||
switch (archiveState) {
|
||||
case empty:
|
||||
|
@ -191,22 +267,26 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
|
|||
archiveState = end;
|
||||
}
|
||||
if (!syncronizing) {
|
||||
requestHistory(-1, id);
|
||||
requestHistory(-1, getId(msg));
|
||||
}
|
||||
break;
|
||||
case beginning:
|
||||
appendCache.push_back(msg);
|
||||
if (!syncronizing) {
|
||||
requestHistory(-1, id);
|
||||
if (!archive->hasElement(msg.getId())) {
|
||||
appendCache.push_back(msg);
|
||||
if (!syncronizing) {
|
||||
requestHistory(-1, getId(msg));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case end:
|
||||
archive->addElement(msg);
|
||||
break;
|
||||
case chunk:
|
||||
appendCache.push_back(msg);
|
||||
if (!syncronizing) {
|
||||
requestHistory(-1, id);
|
||||
if (!archive->hasElement(msg.getId())) {
|
||||
appendCache.push_back(msg);
|
||||
if (!syncronizing) {
|
||||
requestHistory(-1, getId(msg));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case complete:
|
||||
|
@ -214,11 +294,53 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
|
|||
break;
|
||||
}
|
||||
} else if (!syncronizing && archiveState == empty) {
|
||||
requestHistory(-1, id);
|
||||
requestHistory(-1, getId(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
bool found = false;
|
||||
for (Shared::Message& msg : appendCache) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
for (Shared::Message& msg : hisoryCache) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
try {
|
||||
archive->changeMessage(id, data);
|
||||
found = true;
|
||||
} catch (const Archive::NotFound& e) {
|
||||
qDebug() << "An attempt to change state to the message" << id << "but it couldn't be found";
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
for (Shared::Message& msg : responseCache) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId)
|
||||
{
|
||||
unsigned int added(0);
|
||||
|
@ -253,6 +375,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
|||
case empty:
|
||||
wasEmpty = true;
|
||||
archiveState = end;
|
||||
[[fallthrough]];
|
||||
case end:
|
||||
added += archive->addElements(appendCache);
|
||||
appendCache.clear();
|
||||
|
@ -260,6 +383,11 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
|||
archiveState = complete;
|
||||
archive->setFromTheBeginning(true);
|
||||
}
|
||||
if (added == 0 && wasEmpty) {
|
||||
archiveState = empty;
|
||||
nextRequest();
|
||||
break;
|
||||
}
|
||||
if (requestedCount != -1) {
|
||||
QString before;
|
||||
if (responseCache.size() > 0) {
|
||||
|
@ -273,12 +401,12 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
|||
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
|
||||
responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
|
||||
found = true;
|
||||
} catch (Archive::NotFound e) {
|
||||
} catch (const Archive::NotFound& e) {
|
||||
|
||||
} catch (Archive::Empty e) {
|
||||
} catch (const Archive::Empty& e) {
|
||||
|
||||
}
|
||||
if (!found || requestedCount > responseCache.size()) {
|
||||
if (!found || requestedCount > int(responseCache.size())) {
|
||||
if (archiveState == complete) {
|
||||
nextRequest();
|
||||
} else {
|
||||
|
@ -301,26 +429,6 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
|||
}
|
||||
}
|
||||
|
||||
void Core::RosterItem::requestFromEmpty(int count, const QString& before)
|
||||
{
|
||||
if (syncronizing) {
|
||||
qDebug("perform from empty didn't work, another request queued");
|
||||
} else {
|
||||
if (archiveState != empty) {
|
||||
qDebug("perform from empty didn't work, the state is not empty");
|
||||
requestHistory(count, before);
|
||||
} else {
|
||||
syncronizing = true;
|
||||
requestedCount = count;
|
||||
requestedBefore = "";
|
||||
hisoryCache.clear();
|
||||
responseCache.clear();
|
||||
|
||||
emit needHistory(before, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::RosterItem::getServer() const
|
||||
{
|
||||
QStringList lst = jid.split("@");
|
||||
|
@ -331,3 +439,158 @@ bool Core::RosterItem::isMuc() const
|
|||
{
|
||||
return muc;
|
||||
}
|
||||
|
||||
QString Core::RosterItem::avatarPath(const QString& resource) const
|
||||
{
|
||||
QString path = folderPath() + "/" + (resource.size() == 0 ? jid : resource);
|
||||
return path;
|
||||
}
|
||||
|
||||
QString Core::RosterItem::folderPath() const
|
||||
{
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + account + "/" + jid;
|
||||
return path;
|
||||
}
|
||||
|
||||
bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
|
||||
{
|
||||
bool result = archive->setAvatar(data, info, false, resource);
|
||||
if (resource.size() == 0 && result) {
|
||||
if (data.size() == 0) {
|
||||
emit avatarChanged(Shared::Avatar::empty, "");
|
||||
} else {
|
||||
emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + info.type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource)
|
||||
{
|
||||
Archive::AvatarInfo info;
|
||||
return setAutoGeneratedAvatar(info, resource);
|
||||
}
|
||||
|
||||
bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource)
|
||||
{
|
||||
QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
|
||||
QPainter painter(&image);
|
||||
quint8 colorIndex = rand() % Shared::colorPalette.size();
|
||||
const QColor& bg = Shared::colorPalette[colorIndex];
|
||||
painter.fillRect(image.rect(), bg);
|
||||
QFont f;
|
||||
f.setBold(true);
|
||||
f.setPixelSize(72);
|
||||
painter.setFont(f);
|
||||
if (bg.lightnessF() > 0.5) {
|
||||
painter.setPen(Qt::black);
|
||||
} else {
|
||||
painter.setPen(Qt::white);
|
||||
}
|
||||
painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, resource.size() == 0 ? jid.at(0).toUpper() : resource.at(0).toUpper());
|
||||
QByteArray arr;
|
||||
QBuffer stream(&arr);
|
||||
stream.open(QBuffer::WriteOnly);
|
||||
image.save(&stream, "PNG");
|
||||
stream.close();
|
||||
bool result = archive->setAvatar(arr, info, true, resource);
|
||||
if (resource.size() == 0 && result) {
|
||||
emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const
|
||||
{
|
||||
return archive->readAvatarInfo(target, resource);
|
||||
}
|
||||
|
||||
Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource)
|
||||
{
|
||||
Archive::AvatarInfo info;
|
||||
Archive::AvatarInfo newInfo;
|
||||
bool hasAvatar = readAvatarInfo(info, resource);
|
||||
|
||||
QByteArray ava = card.photo();
|
||||
Shared::VCard vCard;
|
||||
initializeVCard(vCard, card);
|
||||
Shared::Avatar type = Shared::Avatar::empty;
|
||||
QString path = "";
|
||||
|
||||
if (ava.size() > 0) {
|
||||
bool changed = setAvatar(ava, newInfo, resource);
|
||||
if (changed) {
|
||||
type = Shared::Avatar::valid;
|
||||
path = avatarPath(resource) + "." + newInfo.type;
|
||||
} else if (hasAvatar) {
|
||||
if (info.autogenerated) {
|
||||
type = Shared::Avatar::autocreated;
|
||||
path = avatarPath(resource) + ".png";
|
||||
} else {
|
||||
type = Shared::Avatar::valid;
|
||||
path = avatarPath(resource) + "." + info.type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!hasAvatar || !info.autogenerated) {
|
||||
setAutoGeneratedAvatar(resource);
|
||||
}
|
||||
type = Shared::Avatar::autocreated;
|
||||
path = avatarPath(resource) + ".png";
|
||||
}
|
||||
|
||||
vCard.setAvatarType(type);
|
||||
vCard.setAvatarPath(path);
|
||||
|
||||
if (resource.size() == 0) {
|
||||
emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath());
|
||||
}
|
||||
|
||||
return vCard;
|
||||
}
|
||||
|
||||
void Core::RosterItem::clearArchiveRequests()
|
||||
{
|
||||
syncronizing = false;
|
||||
requestedCount = 0;
|
||||
requestedBefore = "";
|
||||
for (const std::pair<int, QString>& pair : requestCache) {
|
||||
if (pair.first != -1) {
|
||||
emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet
|
||||
}
|
||||
responseCache.clear();
|
||||
}
|
||||
hisoryCache.clear();
|
||||
responseCache.clear(); //in case the cycle never runned
|
||||
appendCache.clear();
|
||||
requestCache.clear();
|
||||
}
|
||||
|
||||
void Core::RosterItem::downgradeDatabaseState()
|
||||
{
|
||||
if (archiveState == ArchiveState::complete) {
|
||||
archiveState = ArchiveState::beginning;
|
||||
}
|
||||
|
||||
if (archiveState == ArchiveState::end) {
|
||||
archiveState = ArchiveState::chunk;
|
||||
}
|
||||
}
|
||||
|
||||
Shared::Message Core::RosterItem::getMessage(const QString& id)
|
||||
{
|
||||
for (const Shared::Message& msg : appendCache) {
|
||||
if (msg.getId() == id) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
for (Shared::Message& msg : hisoryCache) {
|
||||
if (msg.getId() == id) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
return archive->getElement(id);
|
||||
}
|
||||
|
|
|
@ -21,11 +21,21 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStandardPaths>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QBuffer>
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "../global.h"
|
||||
#include "archive.h"
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppPresence.h>
|
||||
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/vcard.h"
|
||||
#include "storage/archive.h"
|
||||
#include "adapterfunctions.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
@ -54,19 +64,38 @@ public:
|
|||
bool isMuc() const;
|
||||
|
||||
void addMessageToArchive(const Shared::Message& msg);
|
||||
void correctMessageInArchive(const QString& originalId, const Shared::Message& msg);
|
||||
void appendMessageToArchive(const Shared::Message& msg);
|
||||
void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId);
|
||||
void requestHistory(int count, const QString& before);
|
||||
void requestFromEmpty(int count, const QString& before);
|
||||
QString avatarPath(const QString& resource = "") const;
|
||||
QString folderPath() const;
|
||||
bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
|
||||
virtual bool setAutoGeneratedAvatar(const QString& resource = "");
|
||||
virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
|
||||
virtual void handlePresence(const QXmppPresence& pres) = 0;
|
||||
|
||||
bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
void clearArchiveRequests();
|
||||
void downgradeDatabaseState();
|
||||
|
||||
Shared::Message getMessage(const QString& id);
|
||||
|
||||
signals:
|
||||
void nameChanged(const QString& name);
|
||||
void subscriptionStateChanged(Shared::SubscriptionState state);
|
||||
void historyResponse(const std::list<Shared::Message>& messages);
|
||||
void historyResponse(const std::list<Shared::Message>& messages, bool last);
|
||||
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
|
||||
void avatarChanged(Shared::Avatar, const QString& path);
|
||||
void requestVCard(const QString& jid);
|
||||
|
||||
public:
|
||||
const QString jid;
|
||||
const QString account;
|
||||
|
||||
protected:
|
||||
virtual bool setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource = "");
|
||||
virtual bool setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource = "");
|
||||
|
||||
protected:
|
||||
QString name;
|
||||
|
@ -80,11 +109,13 @@ protected:
|
|||
std::list<Shared::Message> appendCache;
|
||||
std::list<Shared::Message> responseCache;
|
||||
std::list<std::pair<int, QString>> requestCache;
|
||||
std::map<QString, Shared::Message> toCorrect;
|
||||
bool muc;
|
||||
|
||||
|
||||
private:
|
||||
void nextRequest();
|
||||
void performRequest(int count, const QString& before);
|
||||
QString getId(const Shared::Message& msg);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
|
|||
}
|
||||
|
||||
snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
|
||||
connect(snInt, SIGNAL(activated(int)), this, SLOT(handleSigInt()));
|
||||
connect(snInt, &QSocketNotifier::activated, this, &SignalCatcher::handleSigInt);
|
||||
}
|
||||
|
||||
SignalCatcher::~SignalCatcher()
|
||||
|
@ -48,9 +48,9 @@ void SignalCatcher::handleSigInt()
|
|||
{
|
||||
snInt->setEnabled(false);
|
||||
char tmp;
|
||||
::read(sigintFd[1], &tmp, sizeof(tmp));
|
||||
ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
|
||||
|
||||
app->quit();
|
||||
emit interrupt();
|
||||
|
||||
snInt->setEnabled(true);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ void SignalCatcher::handleSigInt()
|
|||
void SignalCatcher::intSignalHandler(int unused)
|
||||
{
|
||||
char a = 1;
|
||||
::write(sigintFd[0], &a, sizeof(a));
|
||||
ssize_t s = ::write(sigintFd[0], &a, sizeof(a));
|
||||
}
|
||||
|
||||
int SignalCatcher::setup_unix_signal_handlers()
|
|
@ -33,6 +33,9 @@ public:
|
|||
|
||||
static void intSignalHandler(int unused);
|
||||
|
||||
signals:
|
||||
void interrupt();
|
||||
|
||||
public slots:
|
||||
void handleSigInt();
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2021 Shunf4 <shun1048576@gmail.com>
|
||||
*
|
||||
* 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 "signalcatcher.h"
|
||||
#include <unistd.h>
|
||||
|
||||
SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
|
||||
QObject(parent),
|
||||
app(p_app)
|
||||
{
|
||||
}
|
||||
|
||||
SignalCatcher::~SignalCatcher()
|
||||
{}
|
||||
|
||||
void SignalCatcher::handleSigInt()
|
||||
{
|
||||
}
|
||||
|
||||
void SignalCatcher::intSignalHandler(int unused)
|
||||
{
|
||||
}
|
||||
|
||||
int SignalCatcher::setup_unix_signal_handlers()
|
||||
{
|
||||
return 0;
|
||||
}
|
513
core/squawk.cpp
|
@ -26,11 +26,27 @@ Core::Squawk::Squawk(QObject* parent):
|
|||
QObject(parent),
|
||||
accounts(),
|
||||
amap(),
|
||||
network()
|
||||
state(Shared::Availability::offline),
|
||||
network(),
|
||||
isInitialized(false)
|
||||
#ifdef WITH_KWALLET
|
||||
,kwallet()
|
||||
#endif
|
||||
{
|
||||
connect(&network, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), this, SIGNAL(fileLocalPathResponse(const QString&, const QString&)));
|
||||
connect(&network, SIGNAL(downloadFileProgress(const QString&, qreal)), this, SIGNAL(downloadFileProgress(const QString&, qreal)));
|
||||
connect(&network, SIGNAL(downloadFileError(const QString&, const QString&)), this, SIGNAL(downloadFileError(const QString&, const QString&)));
|
||||
connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
|
||||
connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
|
||||
connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
|
||||
connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
|
||||
connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
|
||||
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword);
|
||||
|
||||
Shared::Global::setSupported("KWallet", true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Core::Squawk::~Squawk()
|
||||
|
@ -42,26 +58,52 @@ Core::Squawk::~Squawk()
|
|||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::onWalletOpened(bool success)
|
||||
{
|
||||
qDebug() << "KWallet opened: " << success;
|
||||
}
|
||||
|
||||
void Core::Squawk::stop()
|
||||
{
|
||||
qDebug("Stopping squawk core..");
|
||||
network.stop();
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
settings.beginWriteArray("accounts");
|
||||
for (int i = 0; i < accounts.size(); ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
Account* acc = accounts[i];
|
||||
settings.setValue("name", acc->getName());
|
||||
settings.setValue("server", acc->getServer());
|
||||
settings.setValue("login", acc->getLogin());
|
||||
settings.setValue("password", acc->getPassword());
|
||||
settings.setValue("resource", acc->getResource());
|
||||
|
||||
if (isInitialized) {
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
settings.beginWriteArray("accounts");
|
||||
SimpleCrypt crypto(passwordHash);
|
||||
for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
Account* acc = accounts[i];
|
||||
|
||||
Shared::AccountPassword ap = acc->getPasswordType();
|
||||
QString password;
|
||||
|
||||
switch (ap) {
|
||||
case Shared::AccountPassword::plain:
|
||||
password = acc->getPassword();
|
||||
break;
|
||||
case Shared::AccountPassword::jammed:
|
||||
password = crypto.encryptToString(acc->getPassword());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
settings.setValue("name", acc->getName());
|
||||
settings.setValue("server", acc->getServer());
|
||||
settings.setValue("login", acc->getLogin());
|
||||
settings.setValue("password", password);
|
||||
settings.setValue("resource", acc->getResource());
|
||||
settings.setValue("passwordType", static_cast<int>(ap));
|
||||
settings.setValue("active", acc->getActive());
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
|
||||
settings.sync();
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
|
||||
settings.sync();
|
||||
|
||||
emit quit();
|
||||
}
|
||||
|
@ -70,21 +112,8 @@ void Core::Squawk::start()
|
|||
{
|
||||
qDebug("Starting squawk core..");
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
int size = settings.beginReadArray("accounts");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
addAccount(
|
||||
settings.value("login").toString(),
|
||||
settings.value("server").toString(),
|
||||
settings.value("password").toString(),
|
||||
settings.value("name").toString(),
|
||||
settings.value("resource").toString()
|
||||
);
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
readSettings();
|
||||
isInitialized = true;
|
||||
network.start();
|
||||
}
|
||||
|
||||
|
@ -95,52 +124,62 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
|
|||
QString server = map.value("server").toString();
|
||||
QString password = map.value("password").toString();
|
||||
QString resource = map.value("resource").toString();
|
||||
int passwordType = map.value("passwordType").toInt();
|
||||
bool active = map.value("active").toBool();
|
||||
|
||||
addAccount(login, server, password, name, resource);
|
||||
addAccount(login, server, password, name, resource, active, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
|
||||
}
|
||||
|
||||
void Core::Squawk::addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource)
|
||||
void Core::Squawk::addAccount(
|
||||
const QString& login,
|
||||
const QString& server,
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType)
|
||||
{
|
||||
QSettings settings;
|
||||
unsigned int reconnects = settings.value("reconnects", 2).toUInt();
|
||||
|
||||
Account* acc = new Account(login, server, password, name);
|
||||
if (amap.count(name) > 0) {
|
||||
qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring";
|
||||
return;
|
||||
}
|
||||
Account* acc = new Account(login, server, password, name, active, &network);
|
||||
acc->setResource(resource);
|
||||
acc->setReconnectTimes(reconnects);
|
||||
acc->setPasswordType(passwordType);
|
||||
accounts.push_back(acc);
|
||||
amap.insert(std::make_pair(name, acc));
|
||||
|
||||
connect(acc, SIGNAL(connectionStateChanged(int)), this, SLOT(onAccountConnectionStateChanged(int)));
|
||||
connect(acc, SIGNAL(error(const QString&)), this, SLOT(onAccountError(const QString&)));
|
||||
connect(acc, SIGNAL(availabilityChanged(int)), this, SLOT(onAccountAvailabilityChanged(int)));
|
||||
connect(acc, SIGNAL(addContact(const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
this, SLOT(onAccountAddContact(const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
connect(acc, SIGNAL(addGroup(const QString&)), this, SLOT(onAccountAddGroup(const QString&)));
|
||||
connect(acc, SIGNAL(removeGroup(const QString&)), this, SLOT(onAccountRemoveGroup(const QString&)));
|
||||
connect(acc, SIGNAL(removeContact(const QString&)), this, SLOT(onAccountRemoveContact(const QString&)));
|
||||
connect(acc, SIGNAL(removeContact(const QString&, const QString&)), this, SLOT(onAccountRemoveContact(const QString&, const QString&)));
|
||||
connect(acc, SIGNAL(changeContact(const QString&, const QMap<QString, QVariant>&)),
|
||||
this, SLOT(onAccountChangeContact(const QString&, const QMap<QString, QVariant>&)));
|
||||
connect(acc, SIGNAL(addPresence(const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
this, SLOT(onAccountAddPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
connect(acc, SIGNAL(removePresence(const QString&, const QString&)), this, SLOT(onAccountRemovePresence(const QString&, const QString&)));
|
||||
connect(acc, SIGNAL(message(const Shared::Message&)), this, SLOT(onAccountMessage(const Shared::Message&)));
|
||||
connect(acc, SIGNAL(responseArchive(const QString&, const std::list<Shared::Message>&)),
|
||||
this, SLOT(onAccountResponseArchive(const QString&, const std::list<Shared::Message>&)));
|
||||
connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
|
||||
connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
|
||||
connect(acc, &Account::error, this, &Squawk::onAccountError);
|
||||
connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword);
|
||||
|
||||
connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
|
||||
connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
|
||||
connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
|
||||
connect(acc, &Account::removeGroup, this, &Squawk::onAccountRemoveGroup);
|
||||
connect(acc, qOverload<const QString&, const QString&>(&Account::removeContact),
|
||||
this, qOverload<const QString&, const QString&>(&Squawk::onAccountRemoveContact));
|
||||
connect(acc, qOverload<const QString&>(&Account::removeContact),
|
||||
this, qOverload<const QString&>(&Squawk::onAccountRemoveContact));
|
||||
connect(acc, &Account::changeContact, this, &Squawk::onAccountChangeContact);
|
||||
connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence);
|
||||
connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence);
|
||||
connect(acc, &Account::message, this, &Squawk::onAccountMessage);
|
||||
connect(acc, &Account::changeMessage, this, &Squawk::onAccountChangeMessage);
|
||||
connect(acc, &Account::responseArchive, this, &Squawk::onAccountResponseArchive);
|
||||
|
||||
connect(acc, &Account::addRoom, this, &Squawk::onAccountAddRoom);
|
||||
connect(acc, &Account::changeRoom, this, &Squawk::onAccountChangeRoom);
|
||||
connect(acc, &Account::removeRoom, this, &Squawk::onAccountRemoveRoom);
|
||||
|
||||
connect(acc, SIGNAL(addRoom(const QString&, const QMap<QString, QVariant>&)),
|
||||
this, SLOT(onAccountAddRoom(const QString&, const QMap<QString, QVariant>&)));
|
||||
connect(acc, SIGNAL(changeRoom(const QString&, const QMap<QString, QVariant>&)),
|
||||
this, SLOT(onAccountChangeRoom(const QString&, const QMap<QString, QVariant>&)));
|
||||
connect(acc, SIGNAL(removeRoom(const QString&)), this, SLOT(onAccountRemoveRoom(const QString&)));
|
||||
connect(acc, &Account::addRoomParticipant, this, &Squawk::onAccountAddRoomPresence);
|
||||
connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence);
|
||||
connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence);
|
||||
|
||||
connect(acc, SIGNAL(addRoomParticipant(const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
this, SLOT(onAccountAddRoomPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
connect(acc, SIGNAL(changeRoomParticipant(const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
this, SLOT(onAccountChangeRoomPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
connect(acc, SIGNAL(removeRoomParticipant(const QString&, const QString&)),
|
||||
this, SLOT(onAccountRemoveRoomPresence(const QString&, const QString&)));
|
||||
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
|
||||
|
||||
connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
|
||||
|
||||
QMap<QString, QVariant> map = {
|
||||
{"login", login},
|
||||
|
@ -148,24 +187,48 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
|
|||
{"name", name},
|
||||
{"password", password},
|
||||
{"resource", resource},
|
||||
{"state", Shared::disconnected},
|
||||
{"offline", Shared::offline},
|
||||
{"error", ""}
|
||||
{"state", QVariant::fromValue(Shared::ConnectionState::disconnected)},
|
||||
{"offline", QVariant::fromValue(Shared::Availability::offline)},
|
||||
{"error", ""},
|
||||
{"avatarPath", acc->getAvatarPath()},
|
||||
{"passwordType", QVariant::fromValue(passwordType)},
|
||||
{"active", active}
|
||||
};
|
||||
|
||||
emit newAccount(map);
|
||||
|
||||
switch (passwordType) {
|
||||
case Shared::AccountPassword::alwaysAsk:
|
||||
case Shared::AccountPassword::kwallet:
|
||||
if (password == "") {
|
||||
acc->invalidatePassword();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (state != Shared::Availability::offline) {
|
||||
acc->setAvailability(state);
|
||||
if (acc->getActive()) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::changeState(int p_state)
|
||||
void Core::Squawk::changeState(Shared::Availability p_state)
|
||||
{
|
||||
Shared::Availability avail;
|
||||
if (p_state < Shared::availabilityLowest && p_state > Shared::availabilityHighest) {
|
||||
qDebug("An attempt to set invalid availability to Squawk core, skipping");
|
||||
}
|
||||
avail = static_cast<Shared::Availability>(p_state);
|
||||
state = avail;
|
||||
|
||||
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
||||
(*itr)->setAvailability(state);
|
||||
if (state != p_state) {
|
||||
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
||||
Account* acc = *itr;
|
||||
acc->setAvailability(p_state);
|
||||
if (state == Shared::Availability::offline && acc->getActive()) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
state = p_state;
|
||||
|
||||
emit stateChanged(p_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,7 +239,10 @@ void Core::Squawk::connectAccount(const QString& account)
|
|||
qDebug("An attempt to connect non existing account, skipping");
|
||||
return;
|
||||
}
|
||||
itr->second->connect();
|
||||
itr->second->setActive(true);
|
||||
if (state != Shared::Availability::offline) {
|
||||
itr->second->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::disconnectAccount(const QString& account)
|
||||
|
@ -187,26 +253,25 @@ void Core::Squawk::disconnectAccount(const QString& account)
|
|||
return;
|
||||
}
|
||||
|
||||
itr->second->setActive(false);
|
||||
itr->second->disconnect();
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountConnectionStateChanged(int state)
|
||||
void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), {{"state", state}});
|
||||
|
||||
if (state == Shared::disconnected) {
|
||||
bool equals = true;
|
||||
for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) {
|
||||
if ((*itr)->getState() != Shared::disconnected) {
|
||||
equals = false;
|
||||
}
|
||||
}
|
||||
if (equals) {
|
||||
state = Shared::offline;
|
||||
emit stateChanged(state);
|
||||
emit changeAccount(acc->getName(), {
|
||||
{"state", QVariant::fromValue(p_state)},
|
||||
{"error", ""}
|
||||
});
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
if (p_state == Shared::ConnectionState::connected) {
|
||||
if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
|
||||
kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||
|
@ -257,10 +322,16 @@ void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& na
|
|||
emit removePresence(acc->getName(), jid, name);
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountAvailabilityChanged(int state)
|
||||
void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), {{"availability", state}});
|
||||
emit changeAccount(acc->getName(), {{"availability", QVariant::fromValue(state)}});
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), data);
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountMessage(const Shared::Message& data)
|
||||
|
@ -273,13 +344,35 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
|
|||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug("An attempt to send a message with non existing account, skipping");
|
||||
qDebug() << "An attempt to send a message with non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->sendMessage(data);
|
||||
}
|
||||
|
||||
void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->replaceMessage(originalId, data);
|
||||
}
|
||||
|
||||
void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->resendMessage(jid, id);
|
||||
}
|
||||
|
||||
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
|
@ -290,10 +383,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
|
|||
itr->second->requestArchive(jid, count, before);
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list)
|
||||
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit responseArchive(acc->getName(), jid, list);
|
||||
emit responseArchive(acc->getName(), jid, list, last);
|
||||
}
|
||||
|
||||
void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
|
||||
|
@ -306,12 +399,46 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
|||
|
||||
Core::Account* acc = itr->second;
|
||||
Shared::ConnectionState st = acc->getState();
|
||||
QMap<QString, QVariant>::const_iterator mItr;
|
||||
bool needToReconnect = false;
|
||||
bool wentReconnecting = false;
|
||||
|
||||
if (st != Shared::disconnected) {
|
||||
acc->reconnect();
|
||||
mItr = map.find("login");
|
||||
if (mItr != map.end()) {
|
||||
needToReconnect = acc->getLogin() != mItr->toString();
|
||||
}
|
||||
|
||||
if (!needToReconnect) {
|
||||
mItr = map.find("password");
|
||||
if (mItr != map.end()) {
|
||||
needToReconnect = acc->getPassword() != mItr->toString();
|
||||
}
|
||||
}
|
||||
if (!needToReconnect) {
|
||||
mItr = map.find("server");
|
||||
if (mItr != map.end()) {
|
||||
needToReconnect = acc->getServer() != mItr->toString();
|
||||
}
|
||||
}
|
||||
if (!needToReconnect) {
|
||||
mItr = map.find("resource");
|
||||
if (mItr != map.end()) {
|
||||
needToReconnect = acc->getResource() != mItr->toString();
|
||||
}
|
||||
}
|
||||
|
||||
bool activeChanged = false;
|
||||
mItr = map.find("active");
|
||||
if (mItr == map.end() || mItr->toBool() == acc->getActive()) {
|
||||
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
|
||||
acc->reconnect();
|
||||
wentReconnecting = true;
|
||||
}
|
||||
} else {
|
||||
acc->setActive(mItr->toBool());
|
||||
activeChanged = true;
|
||||
}
|
||||
|
||||
QMap<QString, QVariant>::const_iterator mItr;
|
||||
mItr = map.find("login");
|
||||
if (mItr != map.end()) {
|
||||
acc->setLogin(mItr->toString());
|
||||
|
@ -332,6 +459,28 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
|||
acc->setServer(mItr->toString());
|
||||
}
|
||||
|
||||
mItr = map.find("passwordType");
|
||||
if (mItr != map.end()) {
|
||||
acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt()));
|
||||
}
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
if (acc->getPasswordType() == Shared::AccountPassword::kwallet
|
||||
&& kwallet.supportState() == PSE::KWallet::success
|
||||
&& !needToReconnect
|
||||
) {
|
||||
kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state != Shared::Availability::offline) {
|
||||
if (activeChanged && acc->getActive()) {
|
||||
acc->connect();
|
||||
} else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
|
||||
emit changeAccount(name, map);
|
||||
}
|
||||
|
||||
|
@ -339,6 +488,10 @@ void Core::Squawk::onAccountError(const QString& text)
|
|||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), {{"error", text}});
|
||||
|
||||
if (acc->getLastError() == Account::Error::authentication) {
|
||||
emit requestPassword(acc->getName(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::removeAccountRequest(const QString& name)
|
||||
|
@ -350,7 +503,7 @@ void Core::Squawk::removeAccountRequest(const QString& name)
|
|||
}
|
||||
|
||||
Account* acc = itr->second;
|
||||
if (acc->getState() != Shared::disconnected) {
|
||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
||||
acc->disconnect();
|
||||
}
|
||||
|
||||
|
@ -372,7 +525,6 @@ void Core::Squawk::removeAccountRequest(const QString& name)
|
|||
acc->deleteLater();
|
||||
}
|
||||
|
||||
|
||||
void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
|
@ -473,6 +625,12 @@ void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString
|
|||
emit removeRoomParticipant(acc->getName(), jid, nick);
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeMessage(acc->getName(), jid, id, data);
|
||||
}
|
||||
|
||||
void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
|
@ -493,21 +651,16 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
|
|||
itr->second->addRoomRequest(jid, nick, password, autoJoin);
|
||||
}
|
||||
|
||||
void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url)
|
||||
void Core::Squawk::fileDownloadRequest(const QString& url)
|
||||
{
|
||||
network.fileLocalPathRequest(messageId, url);
|
||||
}
|
||||
|
||||
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
|
||||
{
|
||||
network.downladFileRequest(messageId, url);
|
||||
network.downladFile(url);
|
||||
}
|
||||
|
||||
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping";
|
||||
qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
|
||||
return;
|
||||
}
|
||||
itr->second->addContactToGroupRequest(jid, groupName);
|
||||
|
@ -517,7 +670,7 @@ void Core::Squawk::removeContactFromGroupRequest(const QString& account, const Q
|
|||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping";
|
||||
qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
|
||||
return;
|
||||
}
|
||||
itr->second->removeContactFromGroupRequest(jid, groupName);
|
||||
|
@ -527,8 +680,136 @@ void Core::Squawk::renameContactRequest(const QString& account, const QString& j
|
|||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to rename contact" << jid << "of existing account" << account << ", skipping";
|
||||
qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
itr->second->renameContactRequest(jid, newName);
|
||||
}
|
||||
|
||||
void Core::Squawk::requestVCard(const QString& account, const QString& jid)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to request" << jid << "vcard of non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
itr->second->requestVCard(jid);
|
||||
}
|
||||
|
||||
void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to upload vcard to non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
itr->second->uploadVCard(card);
|
||||
}
|
||||
|
||||
void Core::Squawk::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
int size = settings.beginReadArray("accounts");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
Shared::AccountPassword passwordType =
|
||||
Shared::Global::fromInt<Shared::AccountPassword>(
|
||||
settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt()
|
||||
);
|
||||
|
||||
QString password = settings.value("password", "").toString();
|
||||
if (passwordType == Shared::AccountPassword::jammed) {
|
||||
SimpleCrypt crypto(passwordHash);
|
||||
password = crypto.decryptToString(password);
|
||||
}
|
||||
|
||||
addAccount(
|
||||
settings.value("login").toString(),
|
||||
settings.value("server").toString(),
|
||||
password,
|
||||
settings.value("name").toString(),
|
||||
settings.value("resource").toString(),
|
||||
settings.value("active").toBool(),
|
||||
passwordType
|
||||
);
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
|
||||
qDebug() << "Squawk core is ready";
|
||||
emit ready();
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountNeedPassword()
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
switch (acc->getPasswordType()) {
|
||||
case Shared::AccountPassword::alwaysAsk:
|
||||
emit requestPassword(acc->getName(), false);
|
||||
break;
|
||||
case Shared::AccountPassword::kwallet: {
|
||||
#ifdef WITH_KWALLET
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
kwallet.requestReadPassword(acc->getName());
|
||||
} else {
|
||||
#endif
|
||||
emit requestPassword(acc->getName(), false);
|
||||
#ifdef WITH_KWALLET
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break; //should never happen;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::onWalletRejectPassword(const QString& login)
|
||||
{
|
||||
emit requestPassword(login, false);
|
||||
}
|
||||
|
||||
void Core::Squawk::responsePassword(const QString& account, const QString& password)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
Account* acc = itr->second;
|
||||
acc->setPassword(password);
|
||||
emit changeAccount(account, {{"password", password}});
|
||||
if (state != Shared::Availability::offline && acc->getActive()) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit fileError({{acc->getName(), jid, id}}, errorText, true);
|
||||
}
|
||||
|
||||
void Core::Squawk::onLocalPathInvalid(const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
|
||||
|
||||
QMap<QString, QVariant> data({
|
||||
{"attachPath", ""}
|
||||
});
|
||||
for (const Shared::MessageInfo& info : list) {
|
||||
AccountsMap::const_iterator itr = amap.find(info.account);
|
||||
if (itr != amap.end()) {
|
||||
itr->second->requestChangeMessage(info.jid, info.messageId, data);
|
||||
} else {
|
||||
qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::changeDownloadsPath(const QString& path)
|
||||
{
|
||||
network.moveFilesDirectory(path);
|
||||
}
|
||||
|
||||
|
|
101
core/squawk.h
|
@ -23,12 +23,20 @@
|
|||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QMap>
|
||||
#include <deque>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "account.h"
|
||||
#include "../global.h"
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/global.h"
|
||||
#include "networkaccess.h"
|
||||
#include "external/simpleCrypt/simplecrypt.h"
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
#include "passwordStorageEngines/kwallet.h"
|
||||
#endif
|
||||
|
||||
namespace Core
|
||||
{
|
||||
|
@ -42,41 +50,61 @@ public:
|
|||
|
||||
signals:
|
||||
void quit();
|
||||
void ready();
|
||||
|
||||
void newAccount(const QMap<QString, QVariant>&);
|
||||
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
||||
void removeAccount(const QString& account);
|
||||
|
||||
void addGroup(const QString& account, const QString& name);
|
||||
void removeGroup(const QString& account, const QString& name);
|
||||
|
||||
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
|
||||
void removeContact(const QString& account, const QString& jid);
|
||||
void removeContact(const QString& account, const QString& jid, const QString& group);
|
||||
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
|
||||
|
||||
void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
void stateChanged(int state);
|
||||
|
||||
void stateChanged(Shared::Availability state);
|
||||
|
||||
void accountMessage(const QString& account, const Shared::Message& data);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||
void downloadFileError(const QString& messageId, const QString& error);
|
||||
void downloadFileProgress(const QString& messageId, qreal value);
|
||||
|
||||
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
|
||||
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account, bool authernticationError);
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void newAccountRequest(const QMap<QString, QVariant>& map);
|
||||
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
|
||||
void removeAccountRequest(const QString& name);
|
||||
void connectAccount(const QString& account);
|
||||
void disconnectAccount(const QString& account);
|
||||
void changeState(int state);
|
||||
|
||||
void changeState(Shared::Availability state);
|
||||
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
|
||||
void resendMessage(const QString& account, const QString& jid, const QString& id);
|
||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||
|
@ -84,12 +112,19 @@ public slots:
|
|||
void removeContactRequest(const QString& account, const QString& jid);
|
||||
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
||||
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||
|
||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void removeRoomRequest(const QString& account, const QString& jid);
|
||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downloadFileRequest(const QString& messageId, const QString& url);
|
||||
|
||||
void fileDownloadRequest(const QString& url);
|
||||
|
||||
void requestVCard(const QString& account, const QString& jid);
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void onLocalPathInvalid(const QString& path);
|
||||
void changeDownloadsPath(const QString& path);
|
||||
|
||||
private:
|
||||
typedef std::deque<Account*> Accounts;
|
||||
|
@ -99,13 +134,26 @@ private:
|
|||
AccountsMap amap;
|
||||
Shared::Availability state;
|
||||
NetworkAccess network;
|
||||
|
||||
private:
|
||||
void addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource);
|
||||
bool isInitialized;
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
PSE::KWallet kwallet;
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
void onAccountConnectionStateChanged(int state);
|
||||
void onAccountAvailabilityChanged(int state);
|
||||
void addAccount(
|
||||
const QString& login,
|
||||
const QString& server,
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType
|
||||
);
|
||||
|
||||
void onAccountConnectionStateChanged(Shared::ConnectionState state);
|
||||
void onAccountAvailabilityChanged(Shared::Availability state);
|
||||
void onAccountChanged(const QMap<QString, QVariant>& data);
|
||||
void onAccountAddGroup(const QString& name);
|
||||
void onAccountError(const QString& text);
|
||||
void onAccountRemoveGroup(const QString& name);
|
||||
|
@ -116,13 +164,34 @@ private slots:
|
|||
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemovePresence(const QString& jid, const QString& name);
|
||||
void onAccountMessage(const Shared::Message& data);
|
||||
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
||||
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemoveRoom(const QString jid);
|
||||
void onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
|
||||
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
||||
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void onAccountNeedPassword();
|
||||
|
||||
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
|
||||
|
||||
void onWalletOpened(bool success);
|
||||
void onWalletRejectPassword(const QString& login);
|
||||
|
||||
private:
|
||||
void readSettings();
|
||||
void parseAccount(
|
||||
const QString& login,
|
||||
const QString& server,
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType
|
||||
);
|
||||
|
||||
static const quint64 passwordHash = 0x08d054225ac4871d;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
target_sources(squawk PRIVATE
|
||||
archive.cpp
|
||||
archive.h
|
||||
storage.cpp
|
||||
storage.h
|
||||
urlstorage.cpp
|
||||
urlstorage.h
|
||||
)
|
|
@ -20,9 +20,13 @@
|
|||
#define CORE_ARCHIVE_H
|
||||
|
||||
#include <QObject>
|
||||
#include "../global.h"
|
||||
#include <QCryptographicHash>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "shared/exception.h"
|
||||
#include <lmdb.h>
|
||||
#include "../exception.h"
|
||||
#include <list>
|
||||
|
||||
namespace Core {
|
||||
|
@ -31,6 +35,8 @@ class Archive : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class AvatarInfo;
|
||||
|
||||
Archive(const QString& jid, QObject* parent = 0);
|
||||
~Archive();
|
||||
|
||||
|
@ -39,7 +45,9 @@ public:
|
|||
|
||||
bool addElement(const Shared::Message& message);
|
||||
unsigned int addElements(const std::list<Shared::Message>& messages);
|
||||
Shared::Message getElement(const QString& id);
|
||||
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();
|
||||
|
@ -49,6 +57,12 @@ public:
|
|||
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;
|
||||
|
@ -112,6 +126,22 @@ public:
|
|||
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
|
||||
{
|
||||
|
@ -124,17 +154,42 @@ public:
|
|||
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;
|
||||
MDB_dbi order;
|
||||
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 _isFromTheBeginning();
|
||||
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);
|
||||
};
|
||||
|
||||
}
|
|
@ -73,7 +73,7 @@ void Core::Storage::close()
|
|||
void Core::Storage::addRecord(const QString& key, const QString& value)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addElement", name.toStdString());
|
||||
throw Archive::Closed("addRecord", name.toStdString());
|
||||
}
|
||||
const std::string& id = key.toStdString();
|
||||
const std::string& val = value.toStdString();
|
||||
|
@ -99,6 +99,33 @@ void Core::Storage::addRecord(const QString& key, const QString& value)
|
|||
}
|
||||
}
|
||||
|
||||
void Core::Storage::changeRecord(const QString& key, const QString& value)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("changeRecord", name.toStdString());
|
||||
}
|
||||
const std::string& id = key.toStdString();
|
||||
const std::string& val = value.toStdString();
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
lmdbData.mv_size = val.size();
|
||||
lmdbData.mv_data = (char*)val.c_str();
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
int rc;
|
||||
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0);
|
||||
if (rc != 0) {
|
||||
mdb_txn_abort(txn);
|
||||
if (rc) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
} else {
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Storage::getRecord(const QString& key) const
|
||||
{
|
||||
if (!opened) {
|
|
@ -39,6 +39,7 @@ public:
|
|||
void close();
|
||||
|
||||
void addRecord(const QString& key, const QString& value);
|
||||
void changeRecord(const QString& key, const QString& value);
|
||||
void removeRecord(const QString& key);
|
||||
QString getRecord(const QString& key) const;
|
||||
|
|
@ -0,0 +1,491 @@
|
|||
/*
|
||||
* 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):
|
||||
name(p_name),
|
||||
opened(false),
|
||||
environment(),
|
||||
base(),
|
||||
map()
|
||||
{
|
||||
}
|
||||
|
||||
Core::UrlStorage::~UrlStorage()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Core::UrlStorage::open()
|
||||
{
|
||||
if (!opened) {
|
||||
mdb_env_create(&environment);
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + name;
|
||||
QDir cache(path);
|
||||
|
||||
if (!cache.exists()) {
|
||||
bool res = cache.mkpath(path);
|
||||
if (!res) {
|
||||
throw Archive::Directory(path.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(environment, 2);
|
||||
mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
|
||||
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
mdb_dbi_open(txn, "base", MDB_CREATE, &base);
|
||||
mdb_dbi_open(txn, "map", MDB_CREATE, &map);
|
||||
mdb_txn_commit(txn);
|
||||
opened = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::close()
|
||||
{
|
||||
if (opened) {
|
||||
mdb_dbi_close(environment, map);
|
||||
mdb_dbi_close(environment, base);
|
||||
mdb_env_close(environment);
|
||||
opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite)
|
||||
{
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
|
||||
try {
|
||||
writeInfo(key, info, txn, overwrite);
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite)
|
||||
{
|
||||
QByteArray ba;
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
info.serialize(ds);
|
||||
|
||||
const std::string& id = key.toStdString();
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
lmdbData.mv_size = ba.size();
|
||||
lmdbData.mv_data = (uint8_t*)ba.data();
|
||||
|
||||
int rc;
|
||||
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
|
||||
|
||||
if (rc != 0) {
|
||||
if (rc == MDB_KEYEXIST) {
|
||||
if (!overwrite) {
|
||||
throw Archive::Exist(name.toStdString(), id);
|
||||
}
|
||||
} else {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
if (info.hasPath()) {
|
||||
std::string sp = info.getPath().toStdString();
|
||||
lmdbData.mv_size = sp.size();
|
||||
lmdbData.mv_data = (char*)sp.c_str();
|
||||
rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0);
|
||||
if (rc != 0) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn)
|
||||
{
|
||||
const std::string& id = key.toStdString();
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
int rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
|
||||
|
||||
if (rc == 0) {
|
||||
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
QDataStream ds(&ba, QIODevice::ReadOnly);
|
||||
|
||||
info.deserialize(ds);
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
throw Archive::NotFound(id, name.toStdString());
|
||||
} else {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info)
|
||||
{
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
|
||||
try {
|
||||
readInfo(key, info, txn);
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addFile(no message, no path)", name.toStdString());
|
||||
}
|
||||
|
||||
addToInfo(url, "", "", "");
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url, const QString& path)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addFile(no message, with path)", name.toStdString());
|
||||
}
|
||||
|
||||
addToInfo(url, "", "", "", path);
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addFile(with message, no path)", name.toStdString());
|
||||
}
|
||||
|
||||
addToInfo(url, account, jid, id);
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addFile(with message, with path)", name.toStdString());
|
||||
}
|
||||
|
||||
addToInfo(url, account, jid, id, path);
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addFile(with list)", name.toStdString());
|
||||
}
|
||||
|
||||
UrlInfo info (path, msgs);
|
||||
writeInfo(url, info, true);;
|
||||
}
|
||||
|
||||
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addMessageAndCheckForPath", name.toStdString());
|
||||
}
|
||||
|
||||
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;
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
|
||||
try {
|
||||
readInfo(url, info, txn);
|
||||
} catch (const Archive::NotFound& e) {
|
||||
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
writeInfo(url, info, txn, true);
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
} else {
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list;
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
UrlInfo info;
|
||||
|
||||
try {
|
||||
readInfo(url, info, txn);
|
||||
info.getMessages(list);
|
||||
} catch (const Archive::NotFound& e) {
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
|
||||
info.setPath(path);
|
||||
try {
|
||||
writeInfo(url, info, txn, true);
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list;
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
UrlInfo info;
|
||||
|
||||
try {
|
||||
std::string id = url.toStdString();
|
||||
readInfo(url, info, txn);
|
||||
info.getMessages(list);
|
||||
|
||||
MDB_val lmdbKey;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
int rc = mdb_del(txn, base, &lmdbKey, NULL);
|
||||
if (rc != 0) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
|
||||
if (info.hasPath()) {
|
||||
std::string path = info.getPath().toStdString();
|
||||
lmdbKey.mv_size = path.size();
|
||||
lmdbKey.mv_data = (char*)path.c_str();
|
||||
|
||||
int rc = mdb_del(txn, map, &lmdbKey, NULL);
|
||||
if (rc != 0) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list;
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
|
||||
try {
|
||||
std::string spath = path.toStdString();
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = spath.size();
|
||||
lmdbKey.mv_data = (char*)spath.c_str();
|
||||
|
||||
QString url;
|
||||
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
|
||||
|
||||
if (rc == 0) {
|
||||
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
url = QString(surl.c_str());
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
|
||||
mdb_txn_abort(txn);
|
||||
return list;
|
||||
} else {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
|
||||
UrlInfo info;
|
||||
std::string id = url.toStdString();
|
||||
readInfo(url, info, txn);
|
||||
info.getMessages(list);
|
||||
info.setPath(QString());
|
||||
writeInfo(url, info, txn, true);
|
||||
|
||||
rc = mdb_del(txn, map, &lmdbKey, NULL);
|
||||
if (rc != 0) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
QString Core::UrlStorage::getUrl(const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list;
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
|
||||
std::string spath = path.toStdString();
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = spath.size();
|
||||
lmdbKey.mv_data = (char*)spath.c_str();
|
||||
|
||||
QString url;
|
||||
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
|
||||
|
||||
if (rc == 0) {
|
||||
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
url = QString(surl.c_str());
|
||||
|
||||
mdb_txn_abort(txn);
|
||||
return url;
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
mdb_txn_abort(txn);
|
||||
throw Archive::NotFound(spath, name.toStdString());
|
||||
} else {
|
||||
mdb_txn_abort(txn);
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url)
|
||||
{
|
||||
UrlInfo info;
|
||||
readInfo(url, info);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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_URLSTORAGE_H
|
||||
#define CORE_URLSTORAGE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
#include <lmdb.h>
|
||||
#include <list>
|
||||
|
||||
#include "archive.h"
|
||||
#include <shared/messageinfo.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class UrlStorage
|
||||
{
|
||||
class UrlInfo;
|
||||
public:
|
||||
UrlStorage(const QString& name);
|
||||
~UrlStorage();
|
||||
|
||||
void open();
|
||||
void close();
|
||||
|
||||
void addFile(const QString& url);
|
||||
void addFile(const QString& url, const QString& path);
|
||||
void addFile(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||
void addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path); //this one overwrites all that was
|
||||
std::list<Shared::MessageInfo> removeFile(const QString& url); //removes entry like it never was in the database, returns affected message infos
|
||||
std::list<Shared::MessageInfo> deletedFile(const QString& path); //empties the localPath of the entry, returns affected message infos
|
||||
std::list<Shared::MessageInfo> setPath(const QString& url, const QString& path);
|
||||
QString getUrl(const QString& path);
|
||||
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
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;
|
||||
|
||||
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);
|
||||
UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
|
||||
|
||||
private:
|
||||
class UrlInfo {
|
||||
public:
|
||||
UrlInfo(const QString& path);
|
||||
UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs);
|
||||
UrlInfo();
|
||||
~UrlInfo();
|
||||
|
||||
void serialize(QDataStream& data) const;
|
||||
void deserialize(QDataStream& data);
|
||||
|
||||
QString getPath() const;
|
||||
bool hasPath() const;
|
||||
void setPath(const QString& path);
|
||||
|
||||
bool addMessage(const QString& acc, const QString& jid, const QString& id);
|
||||
void getMessages(std::list<Shared::MessageInfo>& container) const;
|
||||
|
||||
private:
|
||||
QString localPath;
|
||||
std::list<Shared::MessageInfo> messages;
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_URLSTORAGE_H
|
|
@ -1 +1 @@
|
|||
Subproject commit e6eb0b78d0cb17fccd5ddb60966ba2a0a2d2b593
|
||||
Subproject commit fe83e9c3d42c3becf682e2b5ecfc9d77b24c614f
|
|
@ -0,0 +1,10 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(simplecrypt LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||
|
||||
add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
|
||||
|
||||
target_link_libraries(simpleCrypt Qt5::Core)
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
Copyright (c) 2011, Andre Somers
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Rathenau Instituut, Andre Somers nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "simplecrypt.h"
|
||||
#include <QByteArray>
|
||||
#include <QtDebug>
|
||||
#include <QtGlobal>
|
||||
#include <QDateTime>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDataStream>
|
||||
|
||||
SimpleCrypt::SimpleCrypt():
|
||||
m_key(0),
|
||||
m_compressionMode(CompressionAuto),
|
||||
m_protectionMode(ProtectionChecksum),
|
||||
m_lastError(ErrorNoError) {}
|
||||
|
||||
SimpleCrypt::SimpleCrypt(quint64 key):
|
||||
m_key(key),
|
||||
m_compressionMode(CompressionAuto),
|
||||
m_protectionMode(ProtectionChecksum),
|
||||
m_lastError(ErrorNoError)
|
||||
{
|
||||
splitKey();
|
||||
}
|
||||
|
||||
void SimpleCrypt::setKey(quint64 key)
|
||||
{
|
||||
m_key = key;
|
||||
splitKey();
|
||||
}
|
||||
|
||||
void SimpleCrypt::splitKey()
|
||||
{
|
||||
m_keyParts.clear();
|
||||
m_keyParts.resize(8);
|
||||
for (int i=0;i<8;i++) {
|
||||
quint64 part = m_key;
|
||||
for (int j=i; j>0; j--)
|
||||
part = part >> 8;
|
||||
part = part & 0xff;
|
||||
m_keyParts[i] = static_cast<char>(part);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext)
|
||||
{
|
||||
QByteArray plaintextArray = plaintext.toUtf8();
|
||||
return encryptToByteArray(plaintextArray);
|
||||
}
|
||||
|
||||
QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
|
||||
{
|
||||
if (m_keyParts.isEmpty()) {
|
||||
qWarning() << "No key set.";
|
||||
m_lastError = ErrorNoKeySet;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
|
||||
QByteArray ba = plaintext;
|
||||
|
||||
CryptoFlags flags = CryptoFlagNone;
|
||||
if (m_compressionMode == CompressionAlways) {
|
||||
ba = qCompress(ba, 9); //maximum compression
|
||||
flags |= CryptoFlagCompression;
|
||||
} else if (m_compressionMode == CompressionAuto) {
|
||||
QByteArray compressed = qCompress(ba, 9);
|
||||
if (compressed.count() < ba.count()) {
|
||||
ba = compressed;
|
||||
flags |= CryptoFlagCompression;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray integrityProtection;
|
||||
if (m_protectionMode == ProtectionChecksum) {
|
||||
flags |= CryptoFlagChecksum;
|
||||
QDataStream s(&integrityProtection, QIODevice::WriteOnly);
|
||||
s << qChecksum(ba.constData(), ba.length());
|
||||
} else if (m_protectionMode == ProtectionHash) {
|
||||
flags |= CryptoFlagHash;
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(ba);
|
||||
|
||||
integrityProtection += hash.result();
|
||||
}
|
||||
|
||||
//prepend a random char to the string
|
||||
char randomChar = char(QRandomGenerator::global()->generate() & 0xFF);
|
||||
ba = randomChar + integrityProtection + ba;
|
||||
|
||||
int pos(0);
|
||||
char lastChar(0);
|
||||
|
||||
int cnt = ba.count();
|
||||
|
||||
while (pos < cnt) {
|
||||
ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
|
||||
lastChar = ba.at(pos);
|
||||
++pos;
|
||||
}
|
||||
|
||||
QByteArray resultArray;
|
||||
resultArray.append(char(0x03)); //version for future updates to algorithm
|
||||
resultArray.append(char(flags)); //encryption flags
|
||||
resultArray.append(ba);
|
||||
|
||||
m_lastError = ErrorNoError;
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
QString SimpleCrypt::encryptToString(const QString& plaintext)
|
||||
{
|
||||
QByteArray plaintextArray = plaintext.toUtf8();
|
||||
QByteArray cypher = encryptToByteArray(plaintextArray);
|
||||
QString cypherString = QString::fromLatin1(cypher.toBase64());
|
||||
return cypherString;
|
||||
}
|
||||
|
||||
QString SimpleCrypt::encryptToString(QByteArray plaintext)
|
||||
{
|
||||
QByteArray cypher = encryptToByteArray(plaintext);
|
||||
QString cypherString = QString::fromLatin1(cypher.toBase64());
|
||||
return cypherString;
|
||||
}
|
||||
|
||||
QString SimpleCrypt::decryptToString(const QString &cyphertext)
|
||||
{
|
||||
QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
|
||||
QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
|
||||
QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
QString SimpleCrypt::decryptToString(QByteArray cypher)
|
||||
{
|
||||
QByteArray ba = decryptToByteArray(cypher);
|
||||
QString plaintext = QString::fromUtf8(ba, ba.size());
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext)
|
||||
{
|
||||
QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
|
||||
QByteArray ba = decryptToByteArray(cyphertextArray);
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher)
|
||||
{
|
||||
if (m_keyParts.isEmpty()) {
|
||||
qWarning() << "No key set.";
|
||||
m_lastError = ErrorNoKeySet;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray ba = cypher;
|
||||
|
||||
if( cypher.count() < 3 )
|
||||
return QByteArray();
|
||||
|
||||
char version = ba.at(0);
|
||||
|
||||
if (version !=3) { //we only work with version 3
|
||||
m_lastError = ErrorUnknownVersion;
|
||||
qWarning() << "Invalid version or not a cyphertext.";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
CryptoFlags flags = CryptoFlags(ba.at(1));
|
||||
|
||||
ba = ba.mid(2);
|
||||
int pos(0);
|
||||
int cnt(ba.count());
|
||||
char lastChar = 0;
|
||||
|
||||
while (pos < cnt) {
|
||||
char currentChar = ba[pos];
|
||||
ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
|
||||
lastChar = currentChar;
|
||||
++pos;
|
||||
}
|
||||
|
||||
ba = ba.mid(1); //chop off the random number at the start
|
||||
|
||||
bool integrityOk(true);
|
||||
if (flags.testFlag(CryptoFlagChecksum)) {
|
||||
if (ba.length() < 2) {
|
||||
m_lastError = ErrorIntegrityFailed;
|
||||
return QByteArray();
|
||||
}
|
||||
quint16 storedChecksum;
|
||||
{
|
||||
QDataStream s(&ba, QIODevice::ReadOnly);
|
||||
s >> storedChecksum;
|
||||
}
|
||||
ba = ba.mid(2);
|
||||
quint16 checksum = qChecksum(ba.constData(), ba.length());
|
||||
integrityOk = (checksum == storedChecksum);
|
||||
} else if (flags.testFlag(CryptoFlagHash)) {
|
||||
if (ba.length() < 20) {
|
||||
m_lastError = ErrorIntegrityFailed;
|
||||
return QByteArray();
|
||||
}
|
||||
QByteArray storedHash = ba.left(20);
|
||||
ba = ba.mid(20);
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(ba);
|
||||
integrityOk = (hash.result() == storedHash);
|
||||
}
|
||||
|
||||
if (!integrityOk) {
|
||||
m_lastError = ErrorIntegrityFailed;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
if (flags.testFlag(CryptoFlagCompression))
|
||||
ba = qUncompress(ba);
|
||||
|
||||
m_lastError = ErrorNoError;
|
||||
return ba;
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
Copyright (c) 2011, Andre Somers
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Rathenau Instituut, Andre Somers nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef SIMPLECRYPT_H
|
||||
#define SIMPLECRYPT_H
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QFlags>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
/**
|
||||
@ short Simple encrypt*ion and decryption of strings and byte arrays
|
||||
|
||||
This class provides a simple implementation of encryption and decryption
|
||||
of strings and byte arrays.
|
||||
|
||||
@warning The encryption provided by this class is NOT strong encryption. It may
|
||||
help to shield things from curious eyes, but it will NOT stand up to someone
|
||||
determined to break the encryption. Don't say you were not warned.
|
||||
|
||||
The class uses a 64 bit key. Simply create an instance of the class, set the key,
|
||||
and use the encryptToString() method to calculate an encrypted version of the input string.
|
||||
To decrypt that string again, use an instance of SimpleCrypt initialized with
|
||||
the same key, and call the decryptToString() method with the encrypted string. If the key
|
||||
matches, the decrypted version of the string will be returned again.
|
||||
|
||||
If you do not provide a key, or if something else is wrong, the encryption and
|
||||
decryption function will return an empty string or will return a string containing nonsense.
|
||||
lastError() will return a value indicating if the method was succesful, and if not, why not.
|
||||
|
||||
SimpleCrypt is prepared for the case that the encryption and decryption
|
||||
algorithm is changed in a later version, by prepending a version identifier to the cypertext.
|
||||
*/
|
||||
class SimpleCrypt
|
||||
{
|
||||
public:
|
||||
/**
|
||||
CompressionMode describes if compression will be applied to the data to be
|
||||
encrypted.
|
||||
*/
|
||||
enum CompressionMode {
|
||||
CompressionAuto, /*!< Only apply compression if that results in a shorter plaintext. */
|
||||
CompressionAlways, /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */
|
||||
CompressionNever /*!< Never apply compression. */
|
||||
};
|
||||
/**
|
||||
IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
|
||||
or wrong decryption keys.
|
||||
|
||||
Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
|
||||
increases the length of the resulting cypertext, but makes it possible to check if the plaintext
|
||||
appears to be valid after decryption.
|
||||
*/
|
||||
enum IntegrityProtectionMode {
|
||||
ProtectionNone, /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
|
||||
ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
|
||||
ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
|
||||
};
|
||||
/**
|
||||
Error describes t*he type of error that occured.
|
||||
*/
|
||||
enum Error {
|
||||
ErrorNoError, /*!< No error occurred. */
|
||||
ErrorNoKeySet, /*!< No key was set. You can not encrypt or decrypt without a valid key. */
|
||||
ErrorUnknownVersion, /*!< The version of this data is unknown, or the data is otherwise not valid. */
|
||||
ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
|
||||
};
|
||||
|
||||
/**
|
||||
Constructor. *
|
||||
|
||||
Constructs a SimpleCrypt instance without a valid key set on it.
|
||||
*/
|
||||
SimpleCrypt();
|
||||
/**
|
||||
Constructor. *
|
||||
|
||||
Constructs a SimpleCrypt instance and initializes it with the given @arg key.
|
||||
*/
|
||||
explicit SimpleCrypt(quint64 key);
|
||||
|
||||
/**
|
||||
( Re-) initializes* the key with the given @arg key.
|
||||
*/
|
||||
void setKey(quint64 key);
|
||||
/**
|
||||
Returns true if SimpleCrypt has been initialized with a key.
|
||||
*/
|
||||
bool hasKey() const {return !m_keyParts.isEmpty();}
|
||||
|
||||
/**
|
||||
Sets the compress*ion mode to use when encrypting data. The default mode is Auto.
|
||||
|
||||
Note that decryption is not influenced by this mode, as the decryption recognizes
|
||||
what mode was used when encrypting.
|
||||
*/
|
||||
void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
|
||||
/**
|
||||
Returns the CompressionMode that is currently in use.
|
||||
*/
|
||||
CompressionMode compressionMode() const {return m_compressionMode;}
|
||||
|
||||
/**
|
||||
Sets the integrity mode to use when encrypting data. The default mode is Checksum.
|
||||
|
||||
Note that decryption is not influenced by this mode, as the decryption recognizes
|
||||
what mode was used when encrypting.
|
||||
*/
|
||||
void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
|
||||
/**
|
||||
Returns the IntegrityProtectionMode that is currently in use.
|
||||
*/
|
||||
IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}
|
||||
|
||||
/**
|
||||
Returns the last *error that occurred.
|
||||
*/
|
||||
Error lastError() const {return m_lastError;}
|
||||
|
||||
/**
|
||||
Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
|
||||
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
|
||||
actual result of the string, so it can be stored easily in a text format.
|
||||
*/
|
||||
QString encryptToString(const QString& plaintext) ;
|
||||
/**
|
||||
Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
|
||||
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
|
||||
actual result of the encryption, so it can be stored easily in a text format.
|
||||
*/
|
||||
QString encryptToString(QByteArray plaintext) ;
|
||||
/**
|
||||
Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
|
||||
a binary cyphertext in a QByteArray the result.
|
||||
|
||||
This method returns a byte array, that is useable for storing a binary format. If you need
|
||||
a string you can store in a text file, use encryptToString() instead.
|
||||
*/
|
||||
QByteArray encryptToByteArray(const QString& plaintext) ;
|
||||
/**
|
||||
Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
|
||||
a binary cyphertext in a QByteArray the result.
|
||||
|
||||
This method returns a byte array, that is useable for storing a binary format. If you need
|
||||
a string you can store in a text file, use encryptToString() instead.
|
||||
*/
|
||||
QByteArray encryptToByteArray(QByteArray plaintext) ;
|
||||
|
||||
/**
|
||||
Decrypts a cypher*text string encrypted with this class with the set key back to the
|
||||
plain text version.
|
||||
|
||||
If an error occured, such as non-matching keys between encryption and decryption,
|
||||
an empty string or a string containing nonsense may be returned.
|
||||
*/
|
||||
QString decryptToString(const QString& cyphertext) ;
|
||||
/**
|
||||
Decrypts a cypher*text string encrypted with this class with the set key back to the
|
||||
plain text version.
|
||||
|
||||
If an error occured, such as non-matching keys between encryption and decryption,
|
||||
an empty string or a string containing nonsense may be returned.
|
||||
*/
|
||||
QByteArray decryptToByteArray(const QString& cyphertext) ;
|
||||
/**
|
||||
Decrypts a cypher*text binary encrypted with this class with the set key back to the
|
||||
plain text version.
|
||||
|
||||
If an error occured, such as non-matching keys between encryption and decryption,
|
||||
an empty string or a string containing nonsense may be returned.
|
||||
*/
|
||||
QString decryptToString(QByteArray cypher) ;
|
||||
/**
|
||||
Decrypts a cypher*text binary encrypted with this class with the set key back to the
|
||||
plain text version.
|
||||
|
||||
If an error occured, such as non-matching keys between encryption and decryption,
|
||||
an empty string or a string containing nonsense may be returned.
|
||||
*/
|
||||
QByteArray decryptToByteArray(QByteArray cypher) ;
|
||||
|
||||
//enum to describe options that have been used for the encryption. Currently only one, but
|
||||
//that only leaves room for future extensions like adding a cryptographic hash...
|
||||
enum CryptoFlag{CryptoFlagNone = 0,
|
||||
CryptoFlagCompression = 0x01,
|
||||
CryptoFlagChecksum = 0x02,
|
||||
CryptoFlagHash = 0x04
|
||||
};
|
||||
Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag);
|
||||
private:
|
||||
|
||||
void splitKey();
|
||||
|
||||
quint64 m_key;
|
||||
QVector<char> m_keyParts;
|
||||
CompressionMode m_compressionMode;
|
||||
IntegrityProtectionMode m_protectionMode;
|
||||
Error m_lastError;
|
||||
};
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)
|
||||
|
||||
#endif // SimpleCrypt_H
|
355
global.cpp
|
@ -1,355 +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/>.
|
||||
*/
|
||||
|
||||
#include "global.h"
|
||||
#include <uuid/uuid.h>
|
||||
#include <QApplication>
|
||||
#include <QPalette>
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
|
||||
Shared::Message::Message(Shared::Message::Type p_type):
|
||||
jFrom(),
|
||||
rFrom(),
|
||||
jTo(),
|
||||
rTo(),
|
||||
id(),
|
||||
body(),
|
||||
time(),
|
||||
thread(),
|
||||
type(p_type),
|
||||
outgoing(false),
|
||||
forwarded(false)
|
||||
{
|
||||
}
|
||||
|
||||
Shared::Message::Message():
|
||||
jFrom(),
|
||||
rFrom(),
|
||||
jTo(),
|
||||
rTo(),
|
||||
id(),
|
||||
body(),
|
||||
time(),
|
||||
thread(),
|
||||
type(Message::normal),
|
||||
outgoing(false),
|
||||
forwarded(false)
|
||||
{
|
||||
}
|
||||
|
||||
QString Shared::Message::getBody() const
|
||||
{
|
||||
return body;
|
||||
}
|
||||
|
||||
QString Shared::Message::getFrom() const
|
||||
{
|
||||
QString from = jFrom;
|
||||
if (rFrom.size() > 0) {
|
||||
from += "/" + rFrom;
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
QString Shared::Message::getTo() const
|
||||
{
|
||||
QString to = jTo;
|
||||
if (rTo.size() > 0) {
|
||||
to += "/" + rTo;
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
QString Shared::Message::getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
QDateTime Shared::Message::getTime() const
|
||||
{
|
||||
return time;
|
||||
}
|
||||
|
||||
void Shared::Message::setBody(const QString& p_body)
|
||||
{
|
||||
body = p_body;
|
||||
}
|
||||
|
||||
void Shared::Message::setFrom(const QString& from)
|
||||
{
|
||||
QStringList list = from.split("/");
|
||||
if (list.size() == 1) {
|
||||
jFrom = from;
|
||||
} else {
|
||||
jFrom = list.front();
|
||||
rFrom = list.back();
|
||||
}
|
||||
}
|
||||
|
||||
void Shared::Message::setTo(const QString& to)
|
||||
{
|
||||
QStringList list = to.split("/");
|
||||
if (list.size() == 1) {
|
||||
jTo = to;
|
||||
} else {
|
||||
jTo = list.front();
|
||||
rTo = list.back();
|
||||
}
|
||||
}
|
||||
|
||||
void Shared::Message::setId(const QString& p_id)
|
||||
{
|
||||
id = p_id;
|
||||
}
|
||||
|
||||
void Shared::Message::setTime(const QDateTime& p_time)
|
||||
{
|
||||
time = p_time;
|
||||
}
|
||||
|
||||
QString Shared::Message::getFromJid() const
|
||||
{
|
||||
return jFrom;
|
||||
}
|
||||
|
||||
QString Shared::Message::getFromResource() const
|
||||
{
|
||||
return rFrom;
|
||||
}
|
||||
|
||||
QString Shared::Message::getToJid() const
|
||||
{
|
||||
return jTo;
|
||||
}
|
||||
|
||||
QString Shared::Message::getToResource() const
|
||||
{
|
||||
return rTo;
|
||||
}
|
||||
|
||||
QString Shared::Message::getPenPalJid() const
|
||||
{
|
||||
if (outgoing) {
|
||||
return jTo;
|
||||
} else {
|
||||
return jFrom;
|
||||
}
|
||||
}
|
||||
|
||||
QString Shared::Message::getPenPalResource() const
|
||||
{
|
||||
if (outgoing) {
|
||||
return rTo;
|
||||
} else {
|
||||
return rFrom;
|
||||
}
|
||||
}
|
||||
|
||||
void Shared::Message::setFromJid(const QString& from)
|
||||
{
|
||||
jFrom = from;
|
||||
}
|
||||
|
||||
void Shared::Message::setFromResource(const QString& from)
|
||||
{
|
||||
rFrom = from;
|
||||
}
|
||||
|
||||
void Shared::Message::setToJid(const QString& to)
|
||||
{
|
||||
jTo = to;
|
||||
}
|
||||
|
||||
void Shared::Message::setToResource(const QString& to)
|
||||
{
|
||||
rTo = to;
|
||||
}
|
||||
|
||||
bool Shared::Message::getOutgoing() const
|
||||
{
|
||||
return outgoing;
|
||||
}
|
||||
|
||||
void Shared::Message::setOutgoing(bool og)
|
||||
{
|
||||
outgoing = og;
|
||||
}
|
||||
|
||||
bool Shared::Message::getForwarded() const
|
||||
{
|
||||
return forwarded;
|
||||
}
|
||||
|
||||
void Shared::Message::generateRandomId()
|
||||
{
|
||||
id = generateUUID();
|
||||
}
|
||||
|
||||
QString Shared::Message::getThread() const
|
||||
{
|
||||
return thread;
|
||||
}
|
||||
|
||||
void Shared::Message::setForwarded(bool fwd)
|
||||
{
|
||||
forwarded = fwd;
|
||||
}
|
||||
|
||||
void Shared::Message::setThread(const QString& p_body)
|
||||
{
|
||||
thread = p_body;
|
||||
}
|
||||
|
||||
Shared::Message::Type Shared::Message::getType() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
void Shared::Message::setType(Shared::Message::Type t)
|
||||
{
|
||||
type = t;
|
||||
}
|
||||
|
||||
void Shared::Message::serialize(QDataStream& data) const
|
||||
{
|
||||
data << jFrom;
|
||||
data << rFrom;
|
||||
data << jTo;
|
||||
data << rTo;
|
||||
data << id;
|
||||
data << body;
|
||||
data << time;
|
||||
data << thread;
|
||||
quint8 t = type;
|
||||
data << t;
|
||||
data << outgoing;
|
||||
data << forwarded;
|
||||
data << oob;
|
||||
}
|
||||
|
||||
void Shared::Message::deserialize(QDataStream& data)
|
||||
{
|
||||
data >> jFrom;
|
||||
data >> rFrom;
|
||||
data >> jTo;
|
||||
data >> rTo;
|
||||
data >> id;
|
||||
data >> body;
|
||||
data >> time;
|
||||
data >> thread;
|
||||
quint8 t;
|
||||
data >> t;
|
||||
type = static_cast<Type>(t);
|
||||
data >> outgoing;
|
||||
data >> forwarded;
|
||||
data >> oob;
|
||||
}
|
||||
|
||||
QString Shared::generateUUID()
|
||||
{
|
||||
uuid_t uuid;
|
||||
uuid_generate(uuid);
|
||||
|
||||
char uuid_str[36];
|
||||
uuid_unparse_lower(uuid, uuid_str);
|
||||
return uuid_str;
|
||||
}
|
||||
|
||||
void Shared::Message::setCurrentTime()
|
||||
{
|
||||
time = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
QString Shared::Message::getOutOfBandUrl() const
|
||||
{
|
||||
return oob;
|
||||
}
|
||||
|
||||
bool Shared::Message::hasOutOfBandUrl() const
|
||||
{
|
||||
return oob.size() > 0;
|
||||
}
|
||||
|
||||
void Shared::Message::setOutOfBandUrl(const QString& url)
|
||||
{
|
||||
oob = url;
|
||||
}
|
||||
|
||||
bool Shared::Message::storable() const
|
||||
{
|
||||
return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
|
||||
}
|
||||
|
||||
QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
|
||||
{
|
||||
const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ?
|
||||
big ?
|
||||
Shared::fallbackAvailabilityThemeIconsDarkBig:
|
||||
Shared::fallbackAvailabilityThemeIconsDarkSmall:
|
||||
big ?
|
||||
Shared::fallbackAvailabilityThemeIconsLightBig:
|
||||
Shared::fallbackAvailabilityThemeIconsLightSmall;
|
||||
|
||||
return QIcon::fromTheme(availabilityThemeIcons[av], QIcon(fallback[av]));
|
||||
}
|
||||
|
||||
QIcon Shared::subscriptionStateIcon(Shared::SubscriptionState ss, bool big)
|
||||
{
|
||||
const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ?
|
||||
big ?
|
||||
Shared::fallbackSubscriptionStateThemeIconsDarkBig:
|
||||
Shared::fallbackSubscriptionStateThemeIconsDarkSmall:
|
||||
big ?
|
||||
Shared::fallbackSubscriptionStateThemeIconsLightBig:
|
||||
Shared::fallbackSubscriptionStateThemeIconsLightSmall;
|
||||
|
||||
return QIcon::fromTheme(subscriptionStateThemeIcons[ss], QIcon(fallback[ss]));
|
||||
}
|
||||
|
||||
QIcon Shared::connectionStateIcon(Shared::ConnectionState cs, bool big)
|
||||
{
|
||||
const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ?
|
||||
big ?
|
||||
Shared::fallbackConnectionStateThemeIconsDarkBig:
|
||||
Shared::fallbackConnectionStateThemeIconsDarkSmall:
|
||||
big ?
|
||||
Shared::fallbackConnectionStateThemeIconsLightBig:
|
||||
Shared::fallbackConnectionStateThemeIconsLightSmall;
|
||||
|
||||
return QIcon::fromTheme(connectionStateThemeIcons[cs], QIcon(fallback[cs]));
|
||||
}
|
||||
|
||||
static const QString ds = ":images/fallback/dark/small/";
|
||||
static const QString db = ":images/fallback/dark/big/";
|
||||
static const QString ls = ":images/fallback/light/small/";
|
||||
static const QString lb = ":images/fallback/light/big/";
|
||||
|
||||
QIcon Shared::icon(const QString& name, bool big)
|
||||
{
|
||||
std::map<QString, std::pair<QString, QString>>::const_iterator itr = icons.find(name);
|
||||
if (itr != icons.end()) {
|
||||
const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ?
|
||||
big ? db : ds:
|
||||
big ? lb : ls;
|
||||
return QIcon::fromTheme(itr->second.first, QIcon(prefix + itr->second.second));
|
||||
} else {
|
||||
qDebug() << "Icon" << name << "not found";
|
||||
return QIcon::fromTheme(name);
|
||||
}
|
||||
}
|
163
main.cpp
|
@ -1,163 +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/>.
|
||||
*/
|
||||
|
||||
#include "ui/squawk.h"
|
||||
#include "core/squawk.h"
|
||||
#include "signalcatcher.h"
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QObject>
|
||||
#include <QSettings>
|
||||
#include <QTranslator>
|
||||
#include <QLibraryInfo>
|
||||
#include <QStandardPaths>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qRegisterMetaType<Shared::Message>("Shared::Message");
|
||||
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
|
||||
qRegisterMetaType<QSet<QString>>("QSet<QString>");
|
||||
|
||||
QApplication app(argc, argv);
|
||||
SignalCatcher sc(&app);
|
||||
|
||||
QCoreApplication::setOrganizationName("Macaw");
|
||||
QCoreApplication::setOrganizationDomain("macaw.me");
|
||||
QCoreApplication::setApplicationName("Squawk");
|
||||
QCoreApplication::setApplicationVersion("0.0.5");
|
||||
|
||||
QTranslator qtTranslator;
|
||||
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
||||
app.installTranslator(&qtTranslator);
|
||||
|
||||
QTranslator myappTranslator;
|
||||
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
bool found = false;
|
||||
for (QString share : shares) {
|
||||
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
|
||||
}
|
||||
|
||||
app.installTranslator(&myappTranslator);
|
||||
|
||||
QIcon icon;
|
||||
icon.addFile(":images/logo.svg", QSize(16, 16));
|
||||
icon.addFile(":images/logo.svg", QSize(24, 24));
|
||||
icon.addFile(":images/logo.svg", QSize(32, 32));
|
||||
icon.addFile(":images/logo.svg", QSize(48, 48));
|
||||
icon.addFile(":images/logo.svg", QSize(64, 64));
|
||||
icon.addFile(":images/logo.svg", QSize(96, 96));
|
||||
icon.addFile(":images/logo.svg", QSize(128, 128));
|
||||
icon.addFile(":images/logo.svg", QSize(256, 256));
|
||||
icon.addFile(":images/logo.svg", QSize(512, 512));
|
||||
QApplication::setWindowIcon(icon);
|
||||
|
||||
Squawk w;
|
||||
w.show();
|
||||
|
||||
Core::Squawk* squawk = new Core::Squawk();
|
||||
QThread* coreThread = new QThread();
|
||||
squawk->moveToThread(coreThread);
|
||||
|
||||
QObject::connect(coreThread, SIGNAL(started()), squawk, SLOT(start()));
|
||||
QObject::connect(&app, SIGNAL(aboutToQuit()), squawk, SLOT(stop()));
|
||||
QObject::connect(squawk, SIGNAL(quit()), coreThread, SLOT(quit()));
|
||||
QObject::connect(coreThread, SIGNAL(finished()), squawk, SLOT(deleteLater()));
|
||||
|
||||
QObject::connect(&w, SIGNAL(newAccountRequest(const QMap<QString, QVariant>&)), squawk, SLOT(newAccountRequest(const QMap<QString, QVariant>&)));
|
||||
QObject::connect(&w, SIGNAL(modifyAccountRequest(const QString&, const QMap<QString, QVariant>&)),
|
||||
squawk, SLOT(modifyAccountRequest(const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(&w, SIGNAL(removeAccountRequest(const QString&)), squawk, SLOT(removeAccountRequest(const QString&)));
|
||||
QObject::connect(&w, SIGNAL(connectAccount(const QString&)), squawk, SLOT(connectAccount(const QString&)));
|
||||
QObject::connect(&w, SIGNAL(disconnectAccount(const QString&)), squawk, SLOT(disconnectAccount(const QString&)));
|
||||
QObject::connect(&w, SIGNAL(changeState(int)), squawk, SLOT(changeState(int)));
|
||||
QObject::connect(&w, SIGNAL(sendMessage(const QString&, const Shared::Message&)), squawk, SLOT(sendMessage(const QString&, const Shared::Message&)));
|
||||
QObject::connect(&w, SIGNAL(requestArchive(const QString&, const QString&, int, const QString&)),
|
||||
squawk, SLOT(requestArchive(const QString&, const QString&, int, const QString&)));
|
||||
QObject::connect(&w, SIGNAL(subscribeContact(const QString&, const QString&, const QString&)),
|
||||
squawk, SLOT(subscribeContact(const QString&, const QString&, const QString&)));
|
||||
QObject::connect(&w, SIGNAL(unsubscribeContact(const QString&, const QString&, const QString&)),
|
||||
squawk, SLOT(unsubscribeContact(const QString&, const QString&, const QString&)));
|
||||
QObject::connect(&w, SIGNAL(addContactRequest(const QString&, const QString&, const QString&, const QSet<QString>&)),
|
||||
squawk, SLOT(addContactRequest(const QString&, const QString&, const QString&, const QSet<QString>&)));
|
||||
QObject::connect(&w, SIGNAL(removeContactRequest(const QString&, const QString&)),
|
||||
squawk, SLOT(removeContactRequest(const QString&, const QString&)));
|
||||
QObject::connect(&w, SIGNAL(setRoomJoined(const QString&, const QString&, bool)), squawk, SLOT(setRoomJoined(const QString&, const QString&, bool)));
|
||||
QObject::connect(&w, SIGNAL(setRoomAutoJoin(const QString&, const QString&, bool)), squawk, SLOT(setRoomAutoJoin(const QString&, const QString&, bool)));
|
||||
|
||||
QObject::connect(&w, SIGNAL(removeRoomRequest(const QString&, const QString&)),
|
||||
squawk, SLOT(removeRoomRequest(const QString&, const QString&)));
|
||||
QObject::connect(&w, SIGNAL(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool)),
|
||||
squawk, SLOT(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool)));
|
||||
QObject::connect(&w, SIGNAL(fileLocalPathRequest(const QString&, const QString&)), squawk, SLOT(fileLocalPathRequest(const QString&, const QString&)));
|
||||
QObject::connect(&w, SIGNAL(downloadFileRequest(const QString&, const QString&)), squawk, SLOT(downloadFileRequest(const QString&, const QString&)));
|
||||
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
|
||||
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
|
||||
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
|
||||
|
||||
QObject::connect(squawk, SIGNAL(newAccount(const QMap<QString, QVariant>&)), &w, SLOT(newAccount(const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(addContact(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(changeAccount(const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(changeAccount(const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(removeAccount(const QString&)), &w, SLOT(removeAccount(const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(addGroup(const QString&, const QString&)), &w, SLOT(addGroup(const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(removeGroup(const QString&, const QString&)), &w, SLOT(removeGroup(const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(removeContact(const QString&, const QString&)), &w, SLOT(removeContact(const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(removeContact(const QString&, const QString&, const QString&)), &w, SLOT(removeContact(const QString&, const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(changeContact(const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(changeContact(const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(addPresence(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(addPresence(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(removePresence(const QString&, const QString&, const QString&)), &w, SLOT(removePresence(const QString&, const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(stateChanged(int)), &w, SLOT(stateChanged(int)));
|
||||
QObject::connect(squawk, SIGNAL(accountMessage(const QString&, const Shared::Message&)), &w, SLOT(accountMessage(const QString&, const Shared::Message&)));
|
||||
QObject::connect(squawk, SIGNAL(responseArchive(const QString&, const QString&, const std::list<Shared::Message>&)),
|
||||
&w, SLOT(responseArchive(const QString&, const QString&, const std::list<Shared::Message>&)));
|
||||
|
||||
QObject::connect(squawk, SIGNAL(addRoom(const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(addRoom(const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(changeRoom(const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(changeRoom(const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(removeRoom(const QString&, const QString&)), &w, SLOT(removeRoom(const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(addRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(addRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(changeRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)),
|
||||
&w, SLOT(changeRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
|
||||
QObject::connect(squawk, SIGNAL(removeRoomParticipant(const QString&, const QString&, const QString&)),
|
||||
&w, SLOT(removeRoomParticipant(const QString&, const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), &w, SLOT(fileLocalPathResponse(const QString&, const QString&)));
|
||||
QObject::connect(squawk, SIGNAL(downloadFileProgress(const QString&, qreal)), &w, SLOT(downloadFileProgress(const QString&, qreal)));
|
||||
QObject::connect(squawk, SIGNAL(downloadFileError(const QString&, const QString&)), &w, SLOT(downloadFileError(const QString&, const QString&)));
|
||||
|
||||
|
||||
//qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||
|
||||
coreThread->start();
|
||||
|
||||
int result = app.exec();
|
||||
coreThread->wait(500); //TODO hate doing that but settings for some reason don't get saved to the disk
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
target_sources(squawk PRIVATE
|
||||
main.cpp
|
||||
application.cpp
|
||||
application.h
|
||||
dialogqueue.cpp
|
||||
dialogqueue.h
|
||||
)
|
|
@ -0,0 +1,566 @@
|
|||
// 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 "application.h"
|
||||
|
||||
Application::Application(Core::Squawk* p_core):
|
||||
QObject(),
|
||||
availability(Shared::Availability::offline),
|
||||
core(p_core),
|
||||
squawk(nullptr),
|
||||
notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"),
|
||||
roster(),
|
||||
conversations(),
|
||||
dialogueQueue(roster),
|
||||
nowQuitting(false),
|
||||
destroyingSquawk(false),
|
||||
storage()
|
||||
{
|
||||
connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
|
||||
connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
|
||||
|
||||
|
||||
//connecting myself to the backed
|
||||
connect(this, &Application::changeState, core, &Core::Squawk::changeState);
|
||||
connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined);
|
||||
connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin);
|
||||
connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact);
|
||||
connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact);
|
||||
connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage);
|
||||
connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage);
|
||||
connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage);
|
||||
connect(&roster, &Models::Roster::requestArchive,
|
||||
std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3));
|
||||
|
||||
connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||||
connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword);
|
||||
connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||||
|
||||
connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest);
|
||||
connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid);
|
||||
|
||||
|
||||
//coonecting backend to myself
|
||||
connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged);
|
||||
|
||||
connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage);
|
||||
connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive);
|
||||
connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage);
|
||||
|
||||
connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount);
|
||||
connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount);
|
||||
connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount);
|
||||
|
||||
connect(core, &Core::Squawk::addContact, this, &Application::addContact);
|
||||
connect(core, &Core::Squawk::addGroup, this, &Application::addGroup);
|
||||
connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup);
|
||||
connect(core, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
|
||||
&roster, qOverload<const QString&, const QString&>(&Models::Roster::removeContact));
|
||||
connect(core, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
|
||||
&roster, qOverload<const QString&, const QString&, const QString&>(&Models::Roster::removeContact));
|
||||
connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact);
|
||||
connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence);
|
||||
connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence);
|
||||
|
||||
connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom);
|
||||
connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom);
|
||||
connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom);
|
||||
connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant);
|
||||
connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant);
|
||||
connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant);
|
||||
|
||||
|
||||
connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false));
|
||||
connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true));
|
||||
connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress);
|
||||
connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError);
|
||||
|
||||
connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword);
|
||||
connect(core, &Core::Squawk::ready, this, &Application::readSettings);
|
||||
|
||||
QDBusConnection sys = QDBusConnection::sessionBus();
|
||||
sys.connect(
|
||||
"org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"NotificationClosed",
|
||||
this,
|
||||
SLOT(onNotificationClosed(quint32, quint32))
|
||||
);
|
||||
sys.connect(
|
||||
"org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"ActionInvoked",
|
||||
this,
|
||||
SLOT(onNotificationInvoked(quint32, const QString&))
|
||||
);
|
||||
}
|
||||
|
||||
Application::~Application() {}
|
||||
|
||||
void Application::quit()
|
||||
{
|
||||
if (!nowQuitting) {
|
||||
nowQuitting = true;
|
||||
emit quitting();
|
||||
|
||||
writeSettings();
|
||||
unreadMessagesCountChanged(0); //this notification persist in the desktop, for now I'll zero it on quit not to confuse people
|
||||
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
|
||||
disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||
itr->second->close();
|
||||
}
|
||||
conversations.clear();
|
||||
dialogueQueue.quit();
|
||||
|
||||
if (squawk != nullptr) {
|
||||
squawk->close();
|
||||
}
|
||||
if (!destroyingSquawk) {
|
||||
checkForTheLastWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::checkForTheLastWindow()
|
||||
{
|
||||
if (QApplication::topLevelWidgets().size() > 0) {
|
||||
emit readyToQuit();
|
||||
} else {
|
||||
connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::createMainWindow()
|
||||
{
|
||||
if (squawk == nullptr) {
|
||||
squawk = new Squawk(roster);
|
||||
|
||||
connect(squawk, &Squawk::notify, this, &Application::notify);
|
||||
connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription);
|
||||
connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation);
|
||||
connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
|
||||
connect(squawk, &Squawk::changeState, this, &Application::setState);
|
||||
connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
|
||||
|
||||
connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||||
connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest);
|
||||
connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest);
|
||||
connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount);
|
||||
connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||||
|
||||
connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest);
|
||||
connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest);
|
||||
connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest);
|
||||
connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest);
|
||||
connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest);
|
||||
connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest);
|
||||
connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest);
|
||||
connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard);
|
||||
connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard);
|
||||
connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath);
|
||||
|
||||
connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||||
|
||||
dialogueQueue.setParentWidnow(squawk);
|
||||
squawk->stateChanged(availability);
|
||||
squawk->show();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onSquawkClosing()
|
||||
{
|
||||
dialogueQueue.setParentWidnow(nullptr);
|
||||
|
||||
if (!nowQuitting) {
|
||||
disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||||
}
|
||||
|
||||
destroyingSquawk = true;
|
||||
squawk->deleteLater();
|
||||
squawk = nullptr;
|
||||
|
||||
//for now
|
||||
quit();
|
||||
}
|
||||
|
||||
void Application::onSquawkDestroyed() {
|
||||
destroyingSquawk = false;
|
||||
if (nowQuitting) {
|
||||
checkForTheLastWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Application::notify(const QString& account, const Shared::Message& msg)
|
||||
{
|
||||
QString jid = msg.getPenPalJid();
|
||||
QString name = QString(roster.getContactName(account, jid));
|
||||
QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource()));
|
||||
QVariantList args;
|
||||
args << QString();
|
||||
|
||||
uint32_t notificationId = qHash(msg.getId());
|
||||
args << notificationId;
|
||||
if (path.size() > 0) {
|
||||
args << path;
|
||||
} else {
|
||||
args << QString("mail-message"); //TODO should here better be unknown user icon?
|
||||
}
|
||||
if (msg.getType() == Shared::Message::groupChat) {
|
||||
args << msg.getFromResource() + tr(" from ") + name;
|
||||
} else {
|
||||
args << name;
|
||||
}
|
||||
|
||||
QString body(msg.getBody());
|
||||
QString oob(msg.getOutOfBandUrl());
|
||||
if (body == oob) {
|
||||
body = tr("Attached file");
|
||||
}
|
||||
|
||||
args << body;
|
||||
args << QStringList({
|
||||
"markAsRead", tr("Mark as Read"),
|
||||
"openConversation", tr("Open conversation")
|
||||
});
|
||||
args << QVariantMap({
|
||||
{"desktop-entry", qApp->desktopFileName()},
|
||||
{"category", QString("message")},
|
||||
{"urgency", 1},
|
||||
// {"sound-file", "/path/to/macaw/squawk"},
|
||||
{"sound-name", QString("message-new-instant")}
|
||||
});
|
||||
args << -1;
|
||||
notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
|
||||
|
||||
storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId())));
|
||||
|
||||
if (squawk != nullptr) {
|
||||
QApplication::alert(squawk);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onNotificationClosed(quint32 id, quint32 reason)
|
||||
{
|
||||
Notifications::const_iterator itr = storage.find(id);
|
||||
if (itr != storage.end()) {
|
||||
if (reason == 2) { //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html)
|
||||
//TODO may ba also mark as read?
|
||||
}
|
||||
if (reason != 1) { //just expired, can be activated again from history, so no removing for now
|
||||
storage.erase(id);
|
||||
qDebug() << "Notification" << id << "was closed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onNotificationInvoked(quint32 id, const QString& action)
|
||||
{
|
||||
qDebug() << "Notification" << id << action << "request";
|
||||
Notifications::const_iterator itr = storage.find(id);
|
||||
if (itr != storage.end()) {
|
||||
if (action == "markAsRead") {
|
||||
roster.markMessageAsRead(itr->second.first, itr->second.second);
|
||||
} else if (action == "openConversation") {
|
||||
focusConversation(itr->second.first, "", itr->second.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::unreadMessagesCountChanged(int count)
|
||||
{
|
||||
QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update");
|
||||
signal << qApp->desktopFileName() + QLatin1String(".desktop");
|
||||
signal << QVariantMap ({
|
||||
{"count-visible", count != 0},
|
||||
{"count", count}
|
||||
});
|
||||
QDBusConnection::sessionBus().send(signal);
|
||||
}
|
||||
|
||||
void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId)
|
||||
{
|
||||
if (squawk != nullptr) {
|
||||
if (squawk->currentConversationId() != id) {
|
||||
QModelIndex index = roster.getContactIndex(id.account, id.name, resource);
|
||||
squawk->select(index);
|
||||
}
|
||||
|
||||
if (squawk->isMinimized()) {
|
||||
squawk->showNormal();
|
||||
} else {
|
||||
squawk->show();
|
||||
}
|
||||
squawk->raise();
|
||||
squawk->activateWindow();
|
||||
} else {
|
||||
openConversation(id, resource);
|
||||
}
|
||||
|
||||
//TODO focus messageId;
|
||||
}
|
||||
|
||||
void Application::setState(Shared::Availability p_availability)
|
||||
{
|
||||
if (availability != p_availability) {
|
||||
availability = p_availability;
|
||||
emit changeState(availability);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::stateChanged(Shared::Availability state)
|
||||
{
|
||||
availability = state;
|
||||
if (squawk != nullptr) {
|
||||
squawk->stateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
int avail;
|
||||
if (settings.contains("availability")) {
|
||||
avail = settings.value("availability").toInt();
|
||||
} else {
|
||||
avail = static_cast<int>(Shared::Availability::online);
|
||||
}
|
||||
settings.endGroup();
|
||||
|
||||
setState(Shared::Global::fromInt<Shared::Availability>(avail));
|
||||
createMainWindow();
|
||||
}
|
||||
|
||||
void Application::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("availability", static_cast<int>(availability));
|
||||
}
|
||||
|
||||
void Application::requestPassword(const QString& account, bool authenticationError) {
|
||||
if (authenticationError) {
|
||||
dialogueQueue.addAction(account, DialogQueue::askCredentials);
|
||||
} else {
|
||||
dialogueQueue.addAction(account, DialogQueue::askPassword);
|
||||
}
|
||||
|
||||
}
|
||||
void Application::onConversationClosed()
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
|
||||
Conversations::const_iterator itr = conversations.find(id);
|
||||
if (itr != conversations.end()) {
|
||||
conversations.erase(itr);
|
||||
}
|
||||
if (conv->isMuc) {
|
||||
Room* room = static_cast<Room*>(conv);
|
||||
if (!room->autoJoined()) {
|
||||
emit setRoomJoined(id.account, id.name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe)
|
||||
{
|
||||
Models::Item::Type type = roster.getContactType(id);
|
||||
|
||||
switch (type) {
|
||||
case Models::Item::contact:
|
||||
if (subscribe) {
|
||||
emit subscribeContact(id.account, id.name, "");
|
||||
} else {
|
||||
emit unsubscribeContact(id.account, id.name, "");
|
||||
}
|
||||
break;
|
||||
case Models::Item::room:
|
||||
setRoomAutoJoin(id.account, id.name, subscribe);
|
||||
if (!isConverstationOpened(id)) {
|
||||
emit setRoomJoined(id.account, id.name, subscribe);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::subscribeConversation(Conversation* conv)
|
||||
{
|
||||
connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||
connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
|
||||
connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
|
||||
connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend);
|
||||
connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
|
||||
}
|
||||
|
||||
void Application::openConversation(const Models::Roster::ElId& id, const QString& resource)
|
||||
{
|
||||
Conversations::const_iterator itr = conversations.find(id);
|
||||
Models::Account* acc = roster.getAccount(id.account);
|
||||
Conversation* conv = nullptr;
|
||||
bool created = false;
|
||||
if (itr != conversations.end()) {
|
||||
conv = itr->second;
|
||||
} else {
|
||||
Models::Element* el = roster.getElement(id);
|
||||
if (el != nullptr) {
|
||||
if (el->type == Models::Item::room) {
|
||||
created = true;
|
||||
Models::Room* room = static_cast<Models::Room*>(el);
|
||||
conv = new Room(acc, room);
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id.account, id.name, true);
|
||||
}
|
||||
} else if (el->type == Models::Item::contact) {
|
||||
created = true;
|
||||
conv = new Chat(acc, static_cast<Models::Contact*>(el));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (conv != nullptr) {
|
||||
if (created) {
|
||||
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||||
subscribeConversation(conv);
|
||||
conversations.insert(std::make_pair(id, conv));
|
||||
}
|
||||
|
||||
conv->show();
|
||||
conv->raise();
|
||||
conv->activateWindow();
|
||||
|
||||
if (resource.size() > 0) {
|
||||
conv->setPalResource(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onConversationMessage(const Shared::Message& msg)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
QString acc = conv->getAccount();
|
||||
|
||||
roster.addMessage(acc, msg);
|
||||
emit sendMessage(acc, msg);
|
||||
}
|
||||
|
||||
void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
QString acc = conv->getAccount();
|
||||
|
||||
roster.changeMessage(acc, msg.getPenPalJid(), originalId, {
|
||||
{"state", static_cast<uint>(Shared::Message::State::pending)}
|
||||
});
|
||||
emit replaceMessage(acc, originalId, msg);
|
||||
}
|
||||
|
||||
void Application::onConversationResend(const QString& id)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
QString acc = conv->getAccount();
|
||||
QString jid = conv->getJid();
|
||||
|
||||
emit resendMessage(acc, jid, id);
|
||||
}
|
||||
|
||||
void Application::onSquawkOpenedConversation() {
|
||||
subscribeConversation(squawk->currentConversation);
|
||||
Models::Roster::ElId id = squawk->currentConversationId();
|
||||
|
||||
const Models::Element* el = roster.getElementConst(id);
|
||||
if (el != nullptr && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
|
||||
emit setRoomJoined(id.account, id.name, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::removeAccount(const QString& account)
|
||||
{
|
||||
Conversations::const_iterator itr = conversations.begin();
|
||||
while (itr != conversations.end()) {
|
||||
if (itr->first.account == account) {
|
||||
Conversations::const_iterator lItr = itr;
|
||||
++itr;
|
||||
Conversation* conv = lItr->second;
|
||||
disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||
conv->close();
|
||||
conversations.erase(lItr);
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
if (squawk != nullptr && squawk->currentConversationId().account == account) {
|
||||
squawk->closeCurrentConversation();
|
||||
}
|
||||
|
||||
roster.removeAccount(account);
|
||||
}
|
||||
|
||||
void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
QString attr = itr.key();
|
||||
roster.updateAccount(account, attr, *itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
roster.addContact(account, jid, group, data);
|
||||
|
||||
if (squawk != nullptr) {
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = roster.getAccountIndex(account);
|
||||
squawk->expand(ind);
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::addGroup(const QString& account, const QString& name)
|
||||
{
|
||||
roster.addGroup(account, name);
|
||||
|
||||
if (squawk != nullptr) {
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = roster.getAccountIndex(account);
|
||||
squawk->expand(ind);
|
||||
if (settings.value(name + "/expanded", false).toBool()) {
|
||||
squawk->expand(roster.getGroupIndex(account, name));
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::isConverstationOpened(const Models::Roster::ElId& id) const {
|
||||
return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);}
|
|
@ -0,0 +1,118 @@
|
|||
// 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 APPLICATION_H
|
||||
#define APPLICATION_H
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <QObject>
|
||||
#include <QDBusInterface>
|
||||
|
||||
#include "dialogqueue.h"
|
||||
#include "core/squawk.h"
|
||||
|
||||
#include "ui/squawk.h"
|
||||
#include "ui/models/roster.h"
|
||||
#include "ui/widgets/conversation.h"
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "shared/enums.h"
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class Application : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Application(Core::Squawk* core);
|
||||
~Application();
|
||||
|
||||
bool isConverstationOpened(const Models::Roster::ElId& id) const;
|
||||
|
||||
signals:
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
|
||||
void resendMessage(const QString& account, const QString& jid, const QString& id);
|
||||
|
||||
void changeState(Shared::Availability state);
|
||||
|
||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
|
||||
void quitting();
|
||||
void readyToQuit();
|
||||
|
||||
public slots:
|
||||
void readSettings();
|
||||
void quit();
|
||||
|
||||
protected slots:
|
||||
void notify(const QString& account, const Shared::Message& msg);
|
||||
void unreadMessagesCountChanged(int count);
|
||||
void setState(Shared::Availability availability);
|
||||
|
||||
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
||||
void removeAccount(const QString& account);
|
||||
void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
|
||||
|
||||
void addGroup(const QString& account, const QString& name);
|
||||
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
|
||||
|
||||
void requestPassword(const QString& account, bool authenticationError);
|
||||
|
||||
void writeSettings();
|
||||
|
||||
private slots:
|
||||
void onConversationClosed();
|
||||
void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
|
||||
void onSquawkOpenedConversation();
|
||||
void onConversationMessage(const Shared::Message& msg);
|
||||
void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
|
||||
void onConversationResend(const QString& id);
|
||||
void stateChanged(Shared::Availability state);
|
||||
void onSquawkClosing();
|
||||
void onSquawkDestroyed();
|
||||
void onNotificationClosed(quint32 id, quint32 reason);
|
||||
void onNotificationInvoked(quint32 id, const QString& action);
|
||||
|
||||
|
||||
private:
|
||||
void createMainWindow();
|
||||
void subscribeConversation(Conversation* conv);
|
||||
void checkForTheLastWindow();
|
||||
void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = "");
|
||||
|
||||
private:
|
||||
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
|
||||
typedef std::map<uint32_t, std::pair<Models::Roster::ElId, QString>> Notifications;
|
||||
|
||||
Shared::Availability availability;
|
||||
Core::Squawk* core;
|
||||
Squawk* squawk;
|
||||
QDBusInterface notifications;
|
||||
Models::Roster roster;
|
||||
Conversations conversations;
|
||||
DialogQueue dialogueQueue;
|
||||
bool nowQuitting;
|
||||
bool destroyingSquawk;
|
||||
Notifications storage;
|
||||
};
|
||||
|
||||
#endif // APPLICATION_H
|
|
@ -0,0 +1,187 @@
|
|||
// 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 "dialogqueue.h"
|
||||
#include <QDebug>
|
||||
|
||||
DialogQueue::DialogQueue(const Models::Roster& p_roster):
|
||||
QObject(),
|
||||
currentSource(),
|
||||
currentAction(none),
|
||||
queue(),
|
||||
collection(queue.get<0>()),
|
||||
sequence(queue.get<1>()),
|
||||
prompt(nullptr),
|
||||
parent(nullptr),
|
||||
roster(p_roster)
|
||||
{
|
||||
}
|
||||
|
||||
DialogQueue::~DialogQueue()
|
||||
{
|
||||
}
|
||||
|
||||
void DialogQueue::quit()
|
||||
{
|
||||
queue.clear();
|
||||
if (currentAction != none) {
|
||||
actionDone();
|
||||
}
|
||||
}
|
||||
|
||||
void DialogQueue::setParentWidnow(QMainWindow* p_parent)
|
||||
{
|
||||
parent = p_parent;
|
||||
}
|
||||
|
||||
bool DialogQueue::addAction(const QString& source, DialogQueue::Action action)
|
||||
{
|
||||
if (action == none) {
|
||||
return false;
|
||||
}
|
||||
if (currentAction == none) {
|
||||
currentAction = action;
|
||||
currentSource = source;
|
||||
performNextAction();
|
||||
return true;
|
||||
} else {
|
||||
if (currentAction != action || currentSource != source) {
|
||||
std::pair<Queue::iterator, bool> result = queue.emplace(source, action);
|
||||
return result.second;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DialogQueue::cancelAction(const QString& source, DialogQueue::Action action)
|
||||
{
|
||||
if (source == currentSource && action == currentAction) {
|
||||
actionDone();
|
||||
return true;
|
||||
} else {
|
||||
Collection::iterator itr = collection.find(ActionId{source, action});
|
||||
if (itr != collection.end()) {
|
||||
collection.erase(itr);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DialogQueue::performNextAction()
|
||||
{
|
||||
switch (currentAction) {
|
||||
case none:
|
||||
actionDone();
|
||||
break;
|
||||
case askPassword: {
|
||||
QInputDialog* dialog = new QInputDialog(parent);
|
||||
prompt = dialog;
|
||||
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
|
||||
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
|
||||
dialog->setInputMode(QInputDialog::TextInput);
|
||||
dialog->setTextEchoMode(QLineEdit::Password);
|
||||
dialog->setLabelText(tr("Input the password for account %1").arg(currentSource));
|
||||
dialog->setWindowTitle(tr("Password for account %1").arg(currentSource));
|
||||
dialog->setTextValue("");
|
||||
dialog->exec();
|
||||
}
|
||||
break;
|
||||
case askCredentials: {
|
||||
CredentialsPrompt* dialog = new CredentialsPrompt(parent);
|
||||
prompt = dialog;
|
||||
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
|
||||
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
|
||||
const Models::Account* acc = roster.getAccountConst(currentSource);
|
||||
dialog->setAccount(currentSource);
|
||||
dialog->setLogin(acc->getLogin());
|
||||
dialog->setPassword(acc->getPassword());
|
||||
dialog->exec();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DialogQueue::onPropmtAccepted()
|
||||
{
|
||||
switch (currentAction) {
|
||||
case none:
|
||||
break;
|
||||
case askPassword: {
|
||||
QInputDialog* dialog = static_cast<QInputDialog*>(prompt);
|
||||
emit responsePassword(currentSource, dialog->textValue());
|
||||
}
|
||||
break;
|
||||
case askCredentials: {
|
||||
CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt);
|
||||
emit modifyAccountRequest(currentSource, {
|
||||
{"login", dialog->getLogin()},
|
||||
{"password", dialog->getPassword()}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
actionDone();
|
||||
}
|
||||
|
||||
void DialogQueue::onPropmtRejected()
|
||||
{
|
||||
switch (currentAction) {
|
||||
case none:
|
||||
break;
|
||||
case askPassword:
|
||||
case askCredentials:
|
||||
emit disconnectAccount(currentSource);
|
||||
break;
|
||||
}
|
||||
actionDone();
|
||||
}
|
||||
|
||||
void DialogQueue::actionDone()
|
||||
{
|
||||
prompt->deleteLater();
|
||||
prompt = nullptr;
|
||||
|
||||
if (queue.empty()) {
|
||||
currentAction = none;
|
||||
currentSource = "";
|
||||
} else {
|
||||
Sequence::iterator itr = sequence.begin();
|
||||
currentAction = itr->action;
|
||||
currentSource = itr->source;
|
||||
sequence.erase(itr);
|
||||
performNextAction();
|
||||
}
|
||||
}
|
||||
|
||||
DialogQueue::ActionId::ActionId(const QString& p_source, DialogQueue::Action p_action):
|
||||
source(p_source),
|
||||
action(p_action) {}
|
||||
|
||||
bool DialogQueue::ActionId::operator < (const DialogQueue::ActionId& other) const
|
||||
{
|
||||
if (action == other.action) {
|
||||
return source < other.source;
|
||||
} else {
|
||||
return action < other.action;
|
||||
}
|
||||
}
|
||||
|
||||
DialogQueue::ActionId::ActionId(const DialogQueue::ActionId& other):
|
||||
source(other.source),
|
||||
action(other.action) {}
|
|
@ -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/>.
|
||||
|
||||
#ifndef DIALOGQUEUE_H
|
||||
#define DIALOGQUEUE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QInputDialog>
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/sequenced_index.hpp>
|
||||
|
||||
#include <ui/widgets/accounts/credentialsprompt.h>
|
||||
#include <ui/models/roster.h>
|
||||
|
||||
class DialogQueue : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Action {
|
||||
none,
|
||||
askPassword,
|
||||
askCredentials
|
||||
};
|
||||
|
||||
DialogQueue(const Models::Roster& roster);
|
||||
~DialogQueue();
|
||||
|
||||
bool addAction(const QString& source, Action action);
|
||||
bool cancelAction(const QString& source, Action action);
|
||||
|
||||
signals:
|
||||
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void disconnectAccount(const QString&);
|
||||
|
||||
public:
|
||||
void setParentWidnow(QMainWindow* parent);
|
||||
void quit();
|
||||
|
||||
private:
|
||||
void performNextAction();
|
||||
void actionDone();
|
||||
|
||||
private slots:
|
||||
void onPropmtAccepted();
|
||||
void onPropmtRejected();
|
||||
|
||||
private:
|
||||
QString currentSource;
|
||||
Action currentAction;
|
||||
|
||||
struct ActionId {
|
||||
public:
|
||||
ActionId(const QString& p_source, Action p_action);
|
||||
ActionId(const ActionId& other);
|
||||
|
||||
const QString source;
|
||||
const Action action;
|
||||
|
||||
bool operator < (const ActionId& other) const;
|
||||
};
|
||||
|
||||
typedef boost::multi_index_container <
|
||||
ActionId,
|
||||
boost::multi_index::indexed_by <
|
||||
boost::multi_index::ordered_unique <
|
||||
boost::multi_index::identity <ActionId>
|
||||
>,
|
||||
boost::multi_index::sequenced<>
|
||||
>
|
||||
> Queue;
|
||||
|
||||
typedef Queue::nth_index<0>::type Collection;
|
||||
typedef Queue::nth_index<1>::type Sequence;
|
||||
|
||||
Queue queue;
|
||||
Collection& collection;
|
||||
Sequence& sequence;
|
||||
|
||||
QDialog* prompt;
|
||||
QMainWindow* parent;
|
||||
const Models::Roster& roster;
|
||||
};
|
||||
|
||||
#endif // DIALOGQUEUE_H
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 "shared/global.h"
|
||||
#include "shared/messageinfo.h"
|
||||
#include "shared/pathcheck.h"
|
||||
#include "main/application.h"
|
||||
#include "core/signalcatcher.h"
|
||||
#include "core/squawk.h"
|
||||
|
||||
#include <QLibraryInfo>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QTranslator>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QDir>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qRegisterMetaType<Shared::Message>("Shared::Message");
|
||||
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
|
||||
qRegisterMetaType<Shared::VCard>("Shared::VCard");
|
||||
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
|
||||
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
|
||||
qRegisterMetaType<QSet<QString>>("QSet<QString>");
|
||||
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
|
||||
qRegisterMetaType<Shared::Availability>("Shared::Availability");
|
||||
|
||||
QApplication app(argc, argv);
|
||||
SignalCatcher sc(&app);
|
||||
|
||||
QApplication::setApplicationName("squawk");
|
||||
QApplication::setOrganizationName("macaw.me");
|
||||
QApplication::setApplicationDisplayName("Squawk");
|
||||
QApplication::setApplicationVersion("0.2.2");
|
||||
app.setDesktopFileName("squawk");
|
||||
|
||||
QTranslator qtTranslator;
|
||||
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
||||
app.installTranslator(&qtTranslator);
|
||||
|
||||
QTranslator myappTranslator;
|
||||
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
bool found = false;
|
||||
for (QString share : shares) {
|
||||
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
|
||||
}
|
||||
|
||||
app.installTranslator(&myappTranslator);
|
||||
|
||||
QIcon icon;
|
||||
icon.addFile(":images/logo.svg", QSize(16, 16));
|
||||
icon.addFile(":images/logo.svg", QSize(24, 24));
|
||||
icon.addFile(":images/logo.svg", QSize(32, 32));
|
||||
icon.addFile(":images/logo.svg", QSize(48, 48));
|
||||
icon.addFile(":images/logo.svg", QSize(64, 64));
|
||||
icon.addFile(":images/logo.svg", QSize(96, 96));
|
||||
icon.addFile(":images/logo.svg", QSize(128, 128));
|
||||
icon.addFile(":images/logo.svg", QSize(256, 256));
|
||||
icon.addFile(":images/logo.svg", QSize(512, 512));
|
||||
QApplication::setWindowIcon(icon);
|
||||
|
||||
new Shared::Global(); //translates enums
|
||||
|
||||
QSettings settings;
|
||||
QVariant vs = settings.value("style");
|
||||
if (vs.isValid()) {
|
||||
QString style = vs.toString().toLower();
|
||||
if (style != "system") {
|
||||
Shared::Global::setStyle(style);
|
||||
}
|
||||
}
|
||||
if (Shared::Global::supported("colorSchemeTools")) {
|
||||
QVariant vt = settings.value("theme");
|
||||
if (vt.isValid()) {
|
||||
QString theme = vt.toString();
|
||||
if (theme.toLower() != "system") {
|
||||
Shared::Global::setTheme(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
QString path = Shared::downloadsPathCheck();
|
||||
if (path.size() > 0) {
|
||||
settings.setValue("downloadsPath", path);
|
||||
} else {
|
||||
qDebug() << "couldn't initialize directory for downloads, quitting";
|
||||
return -1;
|
||||
}
|
||||
|
||||
Core::Squawk* squawk = new Core::Squawk();
|
||||
QThread* coreThread = new QThread();
|
||||
squawk->moveToThread(coreThread);
|
||||
|
||||
Application application(squawk);
|
||||
|
||||
QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit);
|
||||
|
||||
QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
|
||||
QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop);
|
||||
//QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
|
||||
QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
|
||||
QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
|
||||
QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
|
||||
|
||||
coreThread->start();
|
||||
int result = app.exec();
|
||||
|
||||
if (coreThread->isRunning()) {
|
||||
//coreThread->wait();
|
||||
//todo if I uncomment that, the app will not quit if it has reconnected at least once
|
||||
//it feels like a symptom of something badly desinged in the core thread
|
||||
//need to investigate;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1,21 +1,26 @@
|
|||
# Maintainer: Yury Gubich <blue@macaw.me>
|
||||
pkgname=squawk
|
||||
pkgver=0.0.5
|
||||
pkgver=0.2.2
|
||||
pkgrel=1
|
||||
pkgdesc="An XMPP desktop messenger, written on qt"
|
||||
pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
|
||||
arch=('i686' 'x86_64')
|
||||
url="https://git.macaw.me/blue/squawk"
|
||||
license=('GPLv3')
|
||||
depends=('qt5-base' 'qt5-svg' 'lmdb' 'qxmpp>=1.0.0' 'libutil-linux')
|
||||
makedepends=('cmake>=3.3' 'imagemagick')
|
||||
source=("https://git.macaw.me/blue/squawk/archive/master.tar.gz")
|
||||
md5sums=('SKIP')
|
||||
license=('GPL3')
|
||||
depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
|
||||
makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
|
||||
optdepends=('kwallet: secure password storage (requires rebuild)'
|
||||
'kconfig: system themes support (requires rebuild)'
|
||||
'kconfigwidgets: system themes support (requires rebuild)'
|
||||
'kio: better show in folder action (requires rebuild)')
|
||||
|
||||
source=("$pkgname-$pkgver.tar.gz")
|
||||
sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
|
||||
build() {
|
||||
cd "$srcdir/squawk"
|
||||
cmake . -D SYSTEM_QXMPP:BOOL=True -D CMAKE_INSTALL_PREFIX=/usr -G Ninja
|
||||
cmake --build .
|
||||
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
|
||||
cmake --build . -j $nproc
|
||||
}
|
||||
package() {
|
||||
cd "$srcdir/squawk"
|
||||
DESTDIR="$pkgdir/" cmake --build . --target install
|
||||
DESTDIR="$pkgdir/" cmake --build . --target install
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
configure_file(squawk.desktop squawk.desktop COPYONLY)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
|
@ -5,8 +5,10 @@ Version=1.0
|
|||
Name=Squawk
|
||||
GenericName=Instant Messenger
|
||||
GenericName[ru]=Мгновенные сообщения
|
||||
GenericName[pt_BR]=Mensageiro instantâneo
|
||||
Comment=XMPP (Jabber) instant messenger client
|
||||
Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями
|
||||
Comment[pt_BR]=Cliente de mensagem instantânea XMPP (Jabber)
|
||||
Exec=squawk %u
|
||||
Icon=squawk
|
||||
StartupNotify=true
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
if (WITH_KIO)
|
||||
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
|
||||
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
|
||||
|
||||
install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif ()
|
||||
|
||||
if (WITH_KCONFIG)
|
||||
add_library(colorSchemeTools SHARED colorschemetools.cpp)
|
||||
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
|
||||
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
|
||||
|
||||
install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 <QIcon>
|
||||
#include <QPainter>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <KConfigCore/KSharedConfig>
|
||||
#include <KConfigCore/KConfigGroup>
|
||||
#include <KConfigWidgets/KColorScheme>
|
||||
|
||||
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection);
|
||||
|
||||
extern "C" QIcon* createPreview(const QString& path) {
|
||||
KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path);
|
||||
QIcon* result = new QIcon();
|
||||
KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig);
|
||||
KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig);
|
||||
KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig);
|
||||
KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig);
|
||||
|
||||
result->addPixmap(createPixmap(16, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
|
||||
result->addPixmap(createPixmap(24, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C" void deletePreview(QIcon* icon) {
|
||||
delete icon;
|
||||
}
|
||||
|
||||
extern "C" void colorSchemeName(const QString& path, QString& answer) {
|
||||
KSharedConfigPtr config = KSharedConfig::openConfig(path);
|
||||
KConfigGroup group(config, QStringLiteral("General"));
|
||||
answer = group.readEntry("Name", QFileInfo(path).baseName());
|
||||
}
|
||||
|
||||
extern "C" void createPalette(const QString& path, QPalette& answer) {
|
||||
KSharedConfigPtr config = KSharedConfig::openConfig(path);
|
||||
answer = KColorScheme::createApplicationPalette(config);
|
||||
}
|
||||
|
||||
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) {
|
||||
QPixmap pix(size, size);
|
||||
pix.fill(Qt::black);
|
||||
QPainter p;
|
||||
p.begin(&pix);
|
||||
const int itemSize = size / 2 - 1;
|
||||
p.fillRect(1, 1, itemSize, itemSize, window);
|
||||
p.fillRect(1 + itemSize, 1, itemSize, itemSize, button);
|
||||
p.fillRect(1, 1 + itemSize, itemSize, itemSize, view);
|
||||
p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, selection);
|
||||
p.end();
|
||||
return pix;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 <QUrl>
|
||||
#include <QObject>
|
||||
#include <KIO/OpenFileManagerWindowJob>
|
||||
|
||||
extern "C" void highlightInFileManager(const QUrl& url) {
|
||||
KIO::OpenFileManagerWindowJob* job = KIO::highlightInFileManager({url});
|
||||
QObject::connect(job, &KIO::OpenFileManagerWindowJob::result, job, &KIO::OpenFileManagerWindowJob::deleteLater);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
target_sources(squawk PRIVATE resources.qrc)
|
||||
|
||||
configure_file(images/logo.svg squawk.svg COPYONLY)
|
||||
configure_file(squawk.rc squawk.rc COPYONLY)
|
||||
|
||||
if(WIN32)
|
||||
set(CONVERT_BIN magick convert)
|
||||
else(WIN32)
|
||||
set(CONVERT_BIN convert)
|
||||
endif(WIN32)
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
if (WIN32)
|
||||
execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
|
||||
set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE)
|
||||
target_sources(squawk PRIVATE ${SQUAWK_WIN_RC})
|
||||
endif(WIN32)
|
||||
|
||||
if (APPLE)
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset")
|
||||
execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} PARENT_SCOPE)
|
||||
set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns)
|
||||
set(APP_ICON_MACOSX ${APP_ICON_MACOSX} PARENT_SCOPE)
|
||||
target_sources(squawk PRIVATE ${APP_ICON_MACOSX})
|
||||
set_source_files_properties(${APP_ICON_MACOSX} TARGET_DIRECTORY squawk PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION "Resources")
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if (APPLE)
|
||||
set_target_properties(squawk PROPERTIES
|
||||
MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
|
||||
MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
|
||||
endif(APPLE)
|
||||
endif()
|
||||
endif (APPLE)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 10 4 L 10 11 L 3 11 L 3 12 L 10 12 L 10 19 L 11 19 L 11 12 L 18 12 L 18 11 L 11 11 L 11 4 L 10 4 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 441 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m4 3v1 13h1 2 1v1 1h6l4-4v-1-7-1h-2v-3h-1-10-1m1 1h10v2h-7v1 9h-1-2v-12m4 3h8v7h-3-1v1 3h-4v-11"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 432 B |
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 807 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m14.996094 3l-11.992188 11.992188h-.003906v4.00781h1 2 1.00781v-.003906l11.992188-11.992188-.001953-.001953.001953-.001953-4-4-.001953.001953-.001953-.001953m-1.998047 3.412109l2.589844 2.589844-7.587891 7.587891v-1.589844h-1-1v-1-.589844l6.998047-6.998047m-7.998047 7.998047v1.589844h1 1v1 .589844l-.410156.410156h-1.589844l-1-1v-1.589844l1-1"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 681 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 439 B |
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs5455">
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
|
||||
<stop style="stop-color:#3daee9" id="stop4174-6"/>
|
||||
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
|
||||
</defs>
|
||||
<metadata id="metadata5458"/>
|
||||
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
|
||||
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
|
||||
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
|
||||
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.3380003 6 5 7.3380003 5 9 C 5 10.662 6.3380003 12 8 12 C 9.6619997 12 11 10.662 11 9 C 11 7.3380003 9.6619997 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 15.482985 11 16.758385 11.807292 17.449219 13 L 18.580078 13 C 17.810617 11.232833 16.056835 10 14 10 z M 8 13 C 5.2299834 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.7839834 14 8 14 z M 16 14 L 16 16 L 14 16 L 14 17 L 16 17 L 16 19 L 17 19 L 17 17 L 19 17 L 19 16 L 17 16 L 17 14 L 16 14 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 16.21602 11 18 12.78398 18 15 L 14.5 15 L 15 16 L 19 16 L 19 15 C 19 12.22998 16.77002 10 14 10 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 953 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 660 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 10 3 L 10 4 L 19 4 L 19 3 L 10 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 12 8 L 12 9 L 19 9 L 19 8 L 12 8 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 13 13 L 13 14 L 19 14 L 19 13 L 13 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 734 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 7 3.0058594 L 7 8 L 2 8 L 2 8.9980469 L 7 8.9980469 L 7 14.007812 L 8 14.007812 L 8 8.9980469 L 13 8.9980469 L 13 8 L 8 8 L 8 3.0058594 L 7 3.0058594 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 490 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 3 2 L 3 13 L 6 13 L 6 14 L 11 14 L 14 11 L 14 4 L 13 4 L 13 2 L 3.7851562 2 L 3 2 z M 4 3 L 12 3 L 12 4 L 6 4 L 6 12 L 4 12 L 4 3 z M 7 5 L 13 5 L 13 10 L 10 10 L 10 13 L 7 13 L 7 5 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 522 B |
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1010 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 883 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 439 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 609 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11 8.0722656 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.0917969 11 L 9.2929688 11 A 2.5 3 0 0 1 11 9.1074219 L 11 8.0722656 z M 12 9 L 12 11 L 10 11 L 10 12 L 12 12 L 12 14 L 13 14 L 13 12 L 15 12 L 15 11 L 13 11 L 13 9 L 12 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 938 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11.646484 8 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.6171875 12 L 14.146484 12 L 15.146484 12 A 3.4999979 4 0 0 0 11.646484 8 z M 11.646484 9 A 2.5 3 0 0 1 14 11 L 9.2929688 11 A 2.5 3 0 0 1 11.646484 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 916 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 660 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 9 2 L 9 3 L 14 3 L 14 2 L 9 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 9 5 L 9 6 L 14 6 L 14 5 L 9 5 z M 9 8 L 9 9 L 14 9 L 14 8 L 9 8 z M 5.5 10 A 3.499998 4 0 0 0 2 14 L 9 14 A 3.499998 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
|
||||
class="ColorScheme-Text"/>
|
||||
</svg>
|
After Width: | Height: | Size: 678 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 10 4 L 10 11 L 3 11 L 3 12 L 10 12 L 10 19 L 11 19 L 11 12 L 18 12 L 18 11 L 11 11 L 11 4 L 10 4 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 441 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m4 3v1 13h1 2 1v1 1h6l4-4v-1-7-1h-2v-3h-1-10-1m1 1h10v2h-7v1 9h-1-2v-12m4 3h8v7h-3-1v1 3h-4v-11"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 432 B |
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 807 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m14.996094 3l-11.992188 11.992188h-.003906v4.00781h1 2 1.00781v-.003906l11.992188-11.992188-.001953-.001953.001953-.001953-4-4-.001953.001953-.001953-.001953m-1.998047 3.412109l2.589844 2.589844-7.587891 7.587891v-1.589844h-1-1v-1-.589844l6.998047-6.998047m-7.998047 7.998047v1.589844h1 1v1 .589844l-.410156.410156h-1.589844l-1-1v-1.589844l1-1"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 681 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 439 B |
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs5455">
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
|
||||
<stop style="stop-color:#3daee9" id="stop4174-6"/>
|
||||
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
|
||||
</defs>
|
||||
<metadata id="metadata5458"/>
|
||||
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
|
||||
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
|
||||
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
|
||||
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.3380003 6 5 7.3380003 5 9 C 5 10.662 6.3380003 12 8 12 C 9.6619997 12 11 10.662 11 9 C 11 7.3380003 9.6619997 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 15.482985 11 16.758385 11.807292 17.449219 13 L 18.580078 13 C 17.810617 11.232833 16.056835 10 14 10 z M 8 13 C 5.2299834 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.7839834 14 8 14 z M 16 14 L 16 16 L 14 16 L 14 17 L 16 17 L 16 19 L 17 19 L 17 17 L 19 17 L 19 16 L 17 16 L 17 14 L 16 14 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 16.21602 11 18 12.78398 18 15 L 14.5 15 L 15 16 L 19 16 L 19 15 C 19 12.22998 16.77002 10 14 10 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 953 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 660 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 10 3 L 10 4 L 19 4 L 19 3 L 10 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 12 8 L 12 9 L 19 9 L 19 8 L 12 8 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 13 13 L 13 14 L 19 14 L 19 13 L 13 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 734 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 7 3.0058594 L 7 8 L 2 8 L 2 8.9980469 L 7 8.9980469 L 7 14.007812 L 8 14.007812 L 8 8.9980469 L 13 8.9980469 L 13 8 L 8 8 L 8 3.0058594 L 7 3.0058594 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 490 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 3 2 L 3 13 L 6 13 L 6 14 L 11 14 L 14 11 L 14 4 L 13 4 L 13 2 L 3.7851562 2 L 3 2 z M 4 3 L 12 3 L 12 4 L 6 4 L 6 12 L 4 12 L 4 3 z M 7 5 L 13 5 L 13 10 L 10 10 L 10 13 L 7 13 L 7 5 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 522 B |
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1010 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 883 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 439 B |