Compare commits

...

59 Commits

Author SHA1 Message Date
vae
6c9f1ab964 feat: wip omemo + key publish 2021-07-22 23:01:30 +03:00
vae
442ad37300 feat: omemo signal lib wip 2021-07-22 20:45:39 +03:00
vae
08fe37bfb2
feat(omemo): basic UI 2021-05-15 12:28:43 +03:00
vae
bb2ce750c8
build: add OpenSSL dependency 2021-05-15 00:48:32 +03:00
vae
bbeeee4c8a
ref(omemo): reformat qomemo/ 2021-05-13 17:54:37 +03:00
vae
574210f5d9
ref(omemo): bundle separate, add db 2021-05-13 17:51:05 +03:00
vae
2654e38665
feat(omemo): add signal protocol wrappers 2021-05-13 17:50:29 +03:00
vae
12ffe8e8e6
ref(omemo): add qomemo/variant 2021-05-13 00:32:13 +03:00
vae
006752b31c
feat(OMEMO): QXmppClientExtension for OMEMO 2021-05-12 17:33:34 +03:00
vae
b1a8f162ce
feat(OMEMO): DeviceKeyStorage, DeviceService, UserDeviceList stubs 2021-05-12 15:50:52 +03:00
vae
6721b62629
feat(OMEMO): qxmppfactories, refactoring 2021-05-12 15:00:41 +03:00
vae
b22a4c8ca3
feat(OMEMO): add Device, DeviceList, PreKey, Bundle + XML 2021-05-12 12:33:35 +03:00
vae
a6254d88b3
feat: OMEMO devices ui mock 2021-05-12 10:06:14 +03:00
vae
2fbbe1ec22
Merge branch 'build-refactor' into maybe-omemo 2021-05-12 08:30:22 +03:00
vae
8e99cc2969
build: plugins/, passwordStorageEngines/wrappers/ as shared libs 2021-05-12 02:01:02 +03:00
vae
140b0fa6b4
feat: add omemo key list entry WIP 2021-05-12 01:44:47 +03:00
vae
cb7e2ede75
feat: add OMEMODevices (WIP) 2021-05-12 00:58:39 +03:00
vae
bc66ab7e52
build: signal-protocol-c 2021-05-11 23:17:57 +03:00
vae
f94c3dac14 Merge branch 'build-refactor' into maybe-omemo 2021-05-11 23:05:50 +03:00
vae
a184ecafa3
build: reformat cmake code 2021-05-11 22:24:55 +03:00
vae
7d2688151c
build: finish up CMakeLists refactoring 2021-05-11 22:21:25 +03:00
vae
0038aca1f6
build: WIP CMakeLists refactoring continue - add FindSignal 2021-05-11 21:35:12 +03:00
vae
6e06a1d5bc
build: WIP CMakeLists refactoring 2021-05-11 20:29:08 +03:00
vae
7d648ab081 feat: add omemo buttons 2021-05-11 19:11:02 +03:00
vae
7c1ae4737e
feat(omemo): WIP signal-protocol-c cpp wrapper 2021-05-11 00:32:44 +03:00
vae
04e745fad4
build: add qomemo dir with TODO 2021-05-11 00:12:33 +03:00
b7b70bc198 segfault fix when trying to send something but the history isn't loaded yet, icon and for attached files which are not previewed 2021-05-11 00:06:40 +03:00
vae
9fbbe0c120
build: add signal-protocol-c dependency 2021-05-10 22:42:17 +03:00
ce047db787 patches from Vae about making libraries static, and about boost, findLMDB CMake script, drop dependency for qtquickcontrols 2021-05-09 02:12:17 +03:00
f45319de25 now instead of storing uploading message in ram I store them in database to be able to recover unsent ones on the next statrt. Found and fixed bug with spam repaints in feedview because of icons 2021-05-07 21:26:02 +03:00
ebf0c64ffb highlight in directory now is optional runtime plugin to KIO, several more file managers to fallback, refactor, 2 new icons 2021-05-06 17:44:43 +03:00
d514db9c4a message context menu began, open and show in folder features 2021-05-04 17:09:41 +03:00
f34289399e text inside of message is selectable again, links are clickable, some refactor, some bugfixes 2021-05-03 14:23:41 +03:00
05d6761baa a bit of refactor, fix the time to request next portion of messages in ui, fancy shadows are back! 2021-05-03 03:35:43 +03:00
216dcd29e9 bug fixing, better progres indicator positioning 2021-05-02 02:03:08 +03:00
0973cb2991 first lousy attempt to make load indicator in feedView 2021-04-30 23:07:00 +03:00
50190f3eac handled a case when user removes downloaded file, minor optimizations on message changing 2021-04-28 23:26:19 +03:00
b44873d587 pal resourse sticking, notifications of unread messages, new message icons 2021-04-27 22:29:15 +03:00
4c5efad9dc CMake build error, status icon text tooltip 2021-04-26 19:37:36 +03:00
8310708c92 First attemtps to upload files, debug, reused of once uploaded or downloaded files 2021-04-23 14:53:48 +03:00
d936c0302d bug with downloads in group chats, status icons in messages, visuals, feedView optimisations 2021-04-23 01:41:32 +03:00
0e937199b0 debug and actual first way to display pictures in messageFeed 2021-04-21 00:56:47 +03:00
48e498be25 some debug, message changing in messageFeed 2021-04-20 00:49:24 +03:00
3a7735b192 First steps on the new idea of file up/downloading 2021-04-18 15:49:20 +03:00
8f914c02a7 temp url storage commit 2021-04-13 16:27:31 +03:00
50bb3f5fd7 started progress bars, changed gcc standard to 17 2021-03-22 21:04:26 +03:00
a0348b8fd2 file progress events delivery methonds 2021-02-27 15:21:27 +03:00
85555da81f just a temp one 2021-02-07 20:02:11 +03:00
ebe5addfb5 just proxying button event from feed view delegate to the feed model 2021-02-06 14:02:42 +03:00
b3c6860e25 seems like i have found solution how to properly render buttons 2021-02-02 01:55:15 +03:00
00ffbac6b0 initial attempt to paint buttons in the messagefeed 2021-01-14 14:22:02 +03:00
ff4124d1f0 Resolved the bug about crash with an empty history chat 2021-01-12 20:15:21 +03:00
15342f3c53 self nick in the chat fix, hovering message feature 2021-01-08 00:50:12 +03:00
270a32db9e achive from the beginning memorizing bugfix, limitation of the requests in the model 2020-08-21 23:57:48 +03:00
e0ef1ef797 Some basic message painting 2020-08-21 00:32:30 +03:00
e1eea2f3a2 made the first prototype, scrolling and word wrapping seems to be working 2020-08-17 13:27:14 +03:00
e54cff0f0c changed my mind, gonna implement the feed on qt instead of qml, first tries, nothing working yet 2020-08-16 00:48:28 +03:00
4e6bd04b02 experimenting with qml 2020-08-12 19:55:01 +03:00
38159eafeb first compiling prototype, doesnt work yet 2020-08-12 01:49:51 +03:00
164 changed files with 7943 additions and 1969 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "external/qxmpp"] [submodule "external/qxmpp"]
path = external/qxmpp path = external/qxmpp
url = https://github.com/qxmpp-project/qxmpp.git url = https://github.com/qxmpp-project/qxmpp.git
[submodule "external/signal-protocol-c"]
path = external/signal-protocol-c
url = https://github.com/signalapp/libsignal-protocol-c.git

View File

@ -6,10 +6,14 @@
- requesting the history of the current chat after reconnection - requesting the history of the current chat after reconnection
- global availability (in drop down list) gets restored 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 - 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
### Improvements ### Improvements
- slightly reduced the traffic on the startup by not requesting history of all MUCs - slightly reduced the traffic on the startup by not requesting history of all MUCs
- completely rewritten message feed, now it works way faster
- 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
## Squawk 0.1.5 (Jul 29, 2020) ## Squawk 0.1.5 (Jul 29, 2020)
### Bug fixes ### Bug fixes

View File

@ -1,117 +1,119 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.4)
project(squawk) project(squawk VERSION 0.1.6 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON) cmake_policy(SET CMP0076 NEW)
set(CMAKE_CXX_STANDARD 14) cmake_policy(SET CMP0079 NEW)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
include(GNUInstallDirs) include(GNUInstallDirs)
include_directories(.) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
find_package(Qt5Widgets CONFIG REQUIRED) add_executable(squawk)
find_package(Qt5LinguistTools) target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
message("Build type: ${CMAKE_BUILD_TYPE}")
set(squawk_SRC
main.cpp
exception.cpp
signalcatcher.cpp
shared/global.cpp
shared/utils.cpp
shared/message.cpp
shared/vcard.cpp
shared/icons.cpp
)
set(squawk_HEAD
exception.h
signalcatcher.h
shared.h
shared/enums.h
shared/message.h
shared/global.h
shared/utils.h
shared/vcard.h
shared/icons.h
)
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)
option(SYSTEM_QXMPP "Use system qxmpp lib" ON) option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
option(SYSTEM_SIGNAL "Use system signal-protocol-c lib" ON)
option(WITH_KWALLET "Build KWallet support module" ON) option(WITH_KWALLET "Build KWallet support module" ON)
option(WITH_KIO "Build KIO support module" ON)
# Dependencies
## Qt
find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
## QXmpp
if (SYSTEM_QXMPP) if (SYSTEM_QXMPP)
find_package(QXmpp CONFIG) find_package(QXmpp CONFIG)
if (NOT QXmpp_FOUND) if (NOT QXmpp_FOUND)
set(SYSTEM_QXMPP OFF) set(SYSTEM_QXMPP OFF)
message("QXmpp package wasn't found, trying to build with bundled QXmpp") message("QXmpp package wasn't found, trying to build with bundled QXmpp")
else() else ()
message("Building with system QXmpp") message("Building with system QXmpp")
endif() endif ()
endif() endif ()
if(NOT SYSTEM_QXMPP) if (NOT SYSTEM_QXMPP)
target_link_libraries(squawk PRIVATE qxmpp)
add_subdirectory(external/qxmpp) add_subdirectory(external/qxmpp)
endif() else ()
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
endif ()
# Signal
if (NOT SYSTEM_SIGNAL)
add_subdirectory(external/signal-protocol-c)
add_dependencies(squawk signal-protocol-c)
target_link_libraries(squawk PRIVATE signal-protocol-c)
else ()
find_package(Signal REQUIRED)
target_link_libraries(squawk PRIVATE Signal::Signal)
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) if (WITH_KWALLET)
find_package(KF5Wallet CONFIG) find_package(KF5Wallet CONFIG)
if (NOT KF5Wallet_FOUND) if (NOT KF5Wallet_FOUND)
set(WITH_KWALLET OFF) set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built") message("KWallet package wasn't found, KWallet support module wouldn't be built")
else() else ()
add_definitions(-DWITH_KWALLET) target_compile_definitions(squawk PRIVATE WITH_KWALLET)
message("Building with support of KWallet") message("Building with support of KWallet")
endif() endif ()
endif() endif ()
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC}) ## LMDB
target_link_libraries(squawk Qt5::Widgets) find_package(LMDB REQUIRED)
target_link_libraries(squawk PRIVATE lmdb)
# OpenSSL
find_package(OpenSSL REQUIRED)
target_link_libraries(squawk PRIVATE OpenSSL::Crypto)
# Misc
target_link_libraries(squawk PRIVATE simpleCrypt)
target_link_libraries(squawk PRIVATE uuid)
# Build type
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif ()
message("Build type: ${CMAKE_BUILD_TYPE}")
target_compile_options(squawk PRIVATE
"-Wall;-Wextra"
"$<$<CONFIG:DEBUG>:-g>"
"$<$<CONFIG:RELEASE>:-O3>"
)
add_subdirectory(ui)
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(external/simpleCrypt) add_subdirectory(external/simpleCrypt)
add_subdirectory(packaging)
target_link_libraries(squawk squawkUI) add_subdirectory(plugins)
target_link_libraries(squawk squawkCORE) add_subdirectory(resources)
target_link_libraries(squawk uuid) add_subdirectory(shared)
add_subdirectory(translations)
add_dependencies(${CMAKE_PROJECT_NAME} translations) add_subdirectory(ui)
add_subdirectory(qomemo)
# Install the executable # Install the executable
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
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)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)

View File

@ -13,7 +13,8 @@
- lmdb - lmdb
- CMake 3.0 or higher - CMake 3.0 or higher
- qxmpp 1.1.0 or higher - qxmpp 1.1.0 or higher
- kwallet (optional) - KDE Frameworks: kwallet (optional)
- KDE Frameworks: KIO (optional)
### Getting ### Getting
@ -67,6 +68,7 @@ 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`) - `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`) - `SYSTEM_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_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`)
## License ## License

47
cmake/FindLMDB.cmake Normal file
View File

@ -0,0 +1,47 @@
#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 lmdb
HINTS ${LMDB_ROOT_DIR}/lib
)
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
)

15
cmake/FindSignal.cmake Normal file
View File

@ -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 ()

View File

@ -1,46 +1,27 @@
cmake_minimum_required(VERSION 3.0) target_sources(squawk PRIVATE
project(squawkCORE)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
find_package(Qt5Network CONFIG REQUIRED)
find_package(Qt5Xml CONFIG REQUIRED)
set(squawkCORE_SRC
squawk.cpp
account.cpp account.cpp
archive.cpp account.h
rosteritem.cpp
contact.cpp
conference.cpp
storage.cpp
networkaccess.cpp
adapterFuctions.cpp adapterFuctions.cpp
handlers/messagehandler.cpp archive.cpp
handlers/rosterhandler.cpp archive.h
) conference.cpp
conference.h
contact.cpp
contact.h
main.cpp
networkaccess.cpp
networkaccess.h
rosteritem.cpp
rosteritem.h
signalcatcher.cpp
signalcatcher.h
squawk.cpp
squawk.h
storage.cpp
storage.h
urlstorage.cpp
urlstorage.h
)
add_subdirectory(handlers)
add_subdirectory(passwordStorageEngines) add_subdirectory(passwordStorageEngines)
# Tell CMake to create the helloworld executable
add_library(squawkCORE ${squawkCORE_SRC})
if(SYSTEM_QXMPP)
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 Qt5::Gui)
target_link_libraries(squawkCORE Qt5::Xml)
target_link_libraries(squawkCORE qxmpp)
target_link_libraries(squawkCORE lmdb)
target_link_libraries(squawkCORE simpleCrypt)
if (WITH_KWALLET)
target_link_libraries(squawkCORE kwalletPSE)
endif()

View File

@ -47,7 +47,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
network(p_net), network(p_net),
passwordType(Shared::AccountPassword::plain), passwordType(Shared::AccountPassword::plain),
mh(new MessageHandler(this)), mh(new MessageHandler(this)),
rh(new RosterHandler(this)) rh(new RosterHandler(this)),
omemo(new QXmpp::Omemo::Manager())
{ {
config.setUser(p_login); config.setUser(p_login);
config.setDomain(p_server); config.setDomain(p_server);
@ -84,12 +85,14 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived); QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived); QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded); QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError); QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
client.addExtension(rcpm); client.addExtension(rcpm);
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived); QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
client.addExtension(omemo.get());
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + name; path += "/" + name;
@ -155,8 +158,9 @@ Account::~Account()
reconnectTimer->stop(); reconnectTimer->stop();
} }
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded); QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError); QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
delete mh; delete mh;
delete rh; delete rh;
@ -402,9 +406,6 @@ QString Core::Account::getFullJid() const {
void Core::Account::sendMessage(const Shared::Message& data) { void Core::Account::sendMessage(const Shared::Message& data) {
mh->sendMessage(data);} mh->sendMessage(data);}
void Core::Account::sendMessage(const Shared::Message& data, const QString& path) {
mh->sendMessage(data, path);}
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg) void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
{ {
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) { if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
@ -434,13 +435,13 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
if (contact == 0) { if (contact == 0) {
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping"; qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
emit responseArchive(jid, std::list<Shared::Message>()); emit responseArchive(jid, std::list<Shared::Message>(), true);
return; return;
} }
if (state != Shared::ConnectionState::connected) { if (state != Shared::ConnectionState::connected) {
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping"; qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
emit responseArchive(contact->jid, std::list<Shared::Message>()); emit responseArchive(contact->jid, std::list<Shared::Message>(), false);
} }
contact->requestHistory(count, before); contact->requestHistory(count, before);
@ -552,9 +553,11 @@ void Core::Account::onClientError(QXmppClient::Error err)
case QXmppStanza::Error::NotAuthorized: case QXmppStanza::Error::NotAuthorized:
errorText = "Authentication error"; errorText = "Authentication error";
break; break;
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
case QXmppStanza::Error::PaymentRequired: case QXmppStanza::Error::PaymentRequired:
errorText = "Payment is required"; errorText = "Payment is required";
break; break;
#endif
case QXmppStanza::Error::RecipientUnavailable: case QXmppStanza::Error::RecipientUnavailable:
errorText = "Recipient is unavailable"; errorText = "Recipient is unavailable";
break; break;
@ -909,3 +912,16 @@ void Core::Account::handleDisconnection()
ownVCardRequestInProgress = false; ownVCardRequestInProgress = false;
} }
void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
{
RosterItem* contact = static_cast<RosterItem*>(sender());
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
if (last) {
qDebug() << "The response contains the first accounted message";
}
emit responseArchive(contact->jid, list, last);
}
void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
mh->requestChangeMessage(jid, messageId, data);}

View File

@ -30,23 +30,24 @@
#include <map> #include <map>
#include <set> #include <set>
#include <QXmppRosterManager.h>
#include <QXmppCarbonManager.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMucManager.h>
#include <QXmppClient.h>
#include <QXmppBookmarkManager.h> #include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h> #include <QXmppBookmarkSet.h>
#include <QXmppCarbonManager.h>
#include <QXmppClient.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMessageReceiptManager.h>
#include <QXmppMucManager.h>
#include <QXmppRosterManager.h>
#include <QXmppUploadRequestManager.h> #include <QXmppUploadRequestManager.h>
#include <QXmppVCardIq.h> #include <QXmppVCardIq.h>
#include <QXmppVCardManager.h> #include <QXmppVCardManager.h>
#include <QXmppMessageReceiptManager.h> #include <qomemo/qxmpp_omemo_manager.h>
#include "shared.h"
#include "contact.h"
#include "conference.h" #include "conference.h"
#include "contact.h"
#include "networkaccess.h" #include "networkaccess.h"
#include "shared/shared.h"
#include "handlers/messagehandler.h" #include "handlers/messagehandler.h"
#include "handlers/rosterhandler.h" #include "handlers/rosterhandler.h"
@ -88,7 +89,6 @@ public:
void setPasswordType(Shared::AccountPassword pt); void setPasswordType(Shared::AccountPassword pt);
QString getFullJid() const; QString getFullJid() const;
void sendMessage(const Shared::Message& data); void sendMessage(const Shared::Message& data);
void sendMessage(const Shared::Message& data, const QString& path);
void requestArchive(const QString& jid, int count, const QString& before); void requestArchive(const QString& jid, int count, const QString& before);
void subscribeToContact(const QString& jid, const QString& reason); void subscribeToContact(const QString& jid, const QString& reason);
void unsubscribeFromContact(const QString& jid, const QString& reason); void unsubscribeFromContact(const QString& jid, const QString& reason);
@ -97,6 +97,7 @@ public:
void addContactToGroupRequest(const QString& jid, const QString& groupName); void addContactToGroupRequest(const QString& jid, const QString& groupName);
void removeContactFromGroupRequest(const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
void renameContactRequest(const QString& jid, const QString& newName); 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 setRoomJoined(const QString& jid, bool joined);
void setRoomAutoJoin(const QString& jid, bool joined); void setRoomAutoJoin(const QString& jid, bool joined);
@ -127,14 +128,14 @@ signals:
void removePresence(const QString& jid, const QString& name); void removePresence(const QString& jid, const QString& name);
void message(const Shared::Message& data); void message(const Shared::Message& data);
void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data); void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void responseArchive(const QString& jid, const std::list<Shared::Message>& list); void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
void error(const QString& text); void error(const QString& text);
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data); 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 changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& jid, const QString& nickName); void removeRoomParticipant(const QString& jid, const QString& nickName);
void receivedVCard(const QString& jid, const Shared::VCard& card); 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 uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
void uploadFileError(const QString& messageId, const QString& error); void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
private: private:
QString name; QString name;
@ -166,6 +167,8 @@ private:
MessageHandler* mh; MessageHandler* mh;
RosterHandler* rh; RosterHandler* rh;
QScopedPointer<QXmpp::Omemo::Manager> omemo;
private slots: private slots:
void onClientStateChange(QXmppClient::State state); void onClientStateChange(QXmppClient::State state);
void onClientError(QXmppClient::Error err); void onClientError(QXmppClient::Error err);
@ -183,6 +186,7 @@ private slots:
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items); void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info); void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
private: private:
void handleDisconnection(); void handleDisconnection();

View File

@ -271,6 +271,8 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
bool hadStanzaId = msg.getStanzaId().size() > 0; bool hadStanzaId = msg.getStanzaId().size() > 0;
QDateTime oTime = msg.getTime(); QDateTime oTime = msg.getTime();
bool idChange = msg.change(data); bool idChange = msg.change(data);
QDateTime nTime = msg.getTime();
bool orderChange = oTime != nTime;
MDB_val lmdbKey, lmdbData; MDB_val lmdbKey, lmdbData;
QByteArray ba; QByteArray ba;
@ -280,15 +282,21 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
lmdbKey.mv_size = strId.size(); lmdbKey.mv_size = strId.size();
lmdbKey.mv_data = (char*)strId.c_str(); lmdbKey.mv_data = (char*)strId.c_str();
int rc; int rc;
if (idChange || orderChange) {
if (idChange) { if (idChange) {
rc = mdb_del(txn, main, &lmdbKey, &lmdbData); rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
} else {
quint64 ostamp = oTime.toMSecsSinceEpoch();
lmdbData.mv_data = (quint8*)&ostamp;
lmdbData.mv_size = 8;
rc = mdb_del(txn, order, &lmdbData, &lmdbKey);
}
if (rc == 0) { if (rc == 0) {
strId = msg.getId().toStdString(); strId = msg.getId().toStdString();
lmdbKey.mv_size = strId.size(); lmdbKey.mv_size = strId.size();
lmdbKey.mv_data = (char*)strId.c_str(); lmdbKey.mv_data = (char*)strId.c_str();
quint64 stamp = nTime.toMSecsSinceEpoch();
quint64 stamp = oTime.toMSecsSinceEpoch();
lmdbData.mv_data = (quint8*)&stamp; lmdbData.mv_data = (quint8*)&stamp;
lmdbData.mv_size = 8; lmdbData.mv_size = 8;
rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0); rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
@ -502,8 +510,9 @@ long unsigned int Core::Archive::size() const
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_stat stat; MDB_stat stat;
mdb_stat(txn, order, &stat); mdb_stat(txn, order, &stat);
size_t amount = stat.ms_entries;
mdb_txn_abort(txn); mdb_txn_abort(txn);
return stat.ms_entries; return amount;
} }
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id) std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
@ -603,10 +612,10 @@ void Core::Archive::setFromTheBeginning(bool is)
MDB_txn *txn; MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn); mdb_txn_begin(environment, NULL, 0, &txn);
bool success = setStatValue("beginning", is, txn); bool success = setStatValue("beginning", is, txn);
if (success != 0) { if (success) {
mdb_txn_abort(txn);
} else {
mdb_txn_commit(txn); mdb_txn_commit(txn);
} else {
mdb_txn_abort(txn);
} }
} }
} }

View File

@ -25,7 +25,7 @@
#include <QMimeType> #include <QMimeType>
#include "shared/message.h" #include "shared/message.h"
#include "exception.h" #include "shared/exception.h"
#include <lmdb.h> #include <lmdb.h>
#include <list> #include <list>

View File

@ -0,0 +1,6 @@
target_sources(squawk PRIVATE
messagehandler.cpp
messagehandler.h
rosterhandler.cpp
rosterhandler.h
)

View File

@ -23,7 +23,6 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
QObject(), QObject(),
acc(account), acc(account),
pendingStateMessages(), pendingStateMessages(),
pendingMessages(),
uploadingSlotsQueue() uploadingSlotsQueue()
{ {
} }
@ -168,14 +167,16 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
id = source.id(); id = source.id();
#endif #endif
target.setId(id); target.setId(id);
if (target.getId().size() == 0) { QString messageId = target.getId();
if (messageId.size() == 0) {
target.generateRandomId(); //TODO out of desperation, I need at least a random ID target.generateRandomId(); //TODO out of desperation, I need at least a random ID
messageId = target.getId();
} }
target.setFrom(source.from()); target.setFrom(source.from());
target.setTo(source.to()); target.setTo(source.to());
target.setBody(source.body()); target.setBody(source.body());
target.setForwarded(forwarded); target.setForwarded(forwarded);
target.setOutOfBandUrl(source.outOfBandUrl());
if (guessing) { if (guessing) {
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) { if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
outgoing = true; outgoing = true;
@ -189,6 +190,12 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
} else { } else {
target.setCurrentTime(); 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) void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
@ -232,11 +239,23 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
} }
} }
void Core::MessageHandler::sendMessage(Shared::Message data) void Core::MessageHandler::sendMessage(const Shared::Message& data)
{
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
prepareUpload(data);
} else {
performSending(data);
}
}
void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
{ {
QString jid = data.getPenPalJid(); QString jid = data.getPenPalJid();
QString id = data.getId(); QString id = data.getId();
QString oob = data.getOutOfBandUrl();
RosterItem* ri = acc->rh->getRosterItem(jid); RosterItem* ri = acc->rh->getRosterItem(jid);
bool sent = false;
QMap<QString, QVariant> changes;
if (acc->state == Shared::ConnectionState::connected) { if (acc->state == Shared::ConnectionState::connected) {
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread()); QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
@ -245,23 +264,16 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
#endif #endif
msg.setId(id); msg.setId(id);
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
msg.setOutOfBandUrl(data.getOutOfBandUrl()); msg.setOutOfBandUrl(oob);
msg.setReceiptRequested(true); msg.setReceiptRequested(true);
bool sent = acc->client.sendPacket(msg); sent = acc->client.sendPacket(msg);
if (sent) { if (sent) {
data.setState(Shared::Message::State::sent); data.setState(Shared::Message::State::sent);
} else { } else {
data.setState(Shared::Message::State::error); data.setState(Shared::Message::State::error);
data.setErrorText("Couldn't send message via QXMPP library check out logs"); data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
}
if (ri != 0) {
ri->appendMessageToArchive(data);
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
}
} }
} else { } else {
@ -269,41 +281,74 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
data.setErrorText("You are is offline or reconnecting"); data.setErrorText("You are is offline or reconnecting");
} }
emit acc->changeMessage(jid, id, { Shared::Message::State mstate = data.getState();
{"state", static_cast<uint>(data.getState())}, changes.insert("state", static_cast<uint>(mstate));
{"errorText", data.getErrorText()} if (mstate == Shared::Message::State::error) {
}); changes.insert("errorText", data.getErrorText());
}
if (oob.size() > 0) {
changes.insert("outOfBandUrl", oob);
}
if (!newMessage) {
changes.insert("stamp", data.getTime());
}
if (ri != 0) {
if (newMessage) {
ri->appendMessageToArchive(data);
} else {
ri->changeMessage(id, changes);
}
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
} else {
pendingStateMessages.erase(id);
}
}
emit acc->changeMessage(jid, id, changes);
} }
void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path) void Core::MessageHandler::prepareUpload(const Shared::Message& data)
{ {
if (acc->state == Shared::ConnectionState::connected) { 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); QString url = acc->network->getFileRemoteUrl(path);
if (url.size() != 0) { if (url.size() != 0) {
sendMessageWithLocalUploadedFile(data, url); sendMessageWithLocalUploadedFile(data, url);
} else { } else {
if (acc->network->isUploading(path, data.getId())) { if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
pendingMessages.emplace(data.getId(), data); ri->appendMessageToArchive(data);
pendingStateMessages.insert(std::make_pair(id, jid));
} else { } else {
if (acc->um->serviceFound()) { if (acc->um->serviceFound()) {
QFileInfo file(path); QFileInfo file(path);
if (file.exists() && file.isReadable()) { if (file.exists() && file.isReadable()) {
uploadingSlotsQueue.emplace_back(path, data); ri->appendMessageToArchive(data);
pendingStateMessages.insert(std::make_pair(id, jid));
uploadingSlotsQueue.emplace_back(path, id);
if (uploadingSlotsQueue.size() == 1) { if (uploadingSlotsQueue.size() == 1) {
acc->um->requestUploadSlot(file); acc->um->requestUploadSlot(file);
} }
} else { } else {
onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it"); 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"; qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
} }
} else { } else {
onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account"); handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services"; qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
} }
} }
} }
} else { } else {
onFileUploadError(data.getId(), "Account is offline or reconnecting"); handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping"; qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
} }
} }
@ -314,12 +359,12 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
if (uploadingSlotsQueue.size() == 0) { if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested"; qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
} else { } else {
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front(); const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
const QString& mId = pair.second.getId(); const QString& mId = pair.second;
acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders()); QString palJid = pendingStateMessages.at(mId);
pendingMessages.emplace(mId, pair.second); acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
uploadingSlotsQueue.pop_front();
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) { if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first); acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
} }
@ -328,44 +373,111 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
{ {
QString err(request.error().text());
if (uploadingSlotsQueue.size() == 0) { if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested"; qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
qDebug() << request.error().text(); qDebug() << err;
} else { } else {
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front(); const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text(); qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text()); handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) { if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first); acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
} }
uploadingSlotsQueue.pop_front();
} }
} }
void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url) void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
{ {
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId); QMap<QString, QVariant> cData = {
if (itr != pendingMessages.end()) { {"attachPath", path}
sendMessageWithLocalUploadedFile(itr->second, url); };
pendingMessages.erase(itr); 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::onFileUploadError(const QString& messageId, const QString& errMsg) void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
{ {
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId); if (up) {
if (itr != pendingMessages.end()) { for (const Shared::MessageInfo& info : msgs) {
pendingMessages.erase(itr); if (info.account == acc->getName()) {
handleUploadError(info.jid, info.messageId, text);
}
}
} }
} }
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url) void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
{
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
pendingStateMessages.erase(jid);
requestChangeMessage(jid, messageId, {
{"state", static_cast<uint>(Shared::Message::State::error)},
{"errorText", errorText}
});
}
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
{
for (const Shared::MessageInfo& info : msgs) {
if (info.account == acc->getName()) {
RosterItem* ri = acc->rh->getRosterItem(info.jid);
if (ri != 0) {
Shared::Message msg = ri->getMessage(info.messageId);
sendMessageWithLocalUploadedFile(msg, path, false);
} else {
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
}
}
}
}
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
{ {
msg.setOutOfBandUrl(url); msg.setOutOfBandUrl(url);
if (msg.getBody().size() == 0) { if (msg.getBody().size() == 0) { //not sure why, but most messages do that
msg.setBody(url); msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
} }
sendMessage(msg); performSending(msg, newMessage);
//TODO removal/progress update //TODO removal/progress update
} }
static const std::set<QString> allowerToChangeKeys({
"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 (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
allSupported = false; //to make a message to look like if it was edited
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
break; //because the underlying tech assumes that the change is initiated by user
} //not by system
}
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";
}
}
}

View File

@ -28,6 +28,7 @@
#include <QXmppHttpUploadIq.h> #include <QXmppHttpUploadIq.h>
#include <shared/message.h> #include <shared/message.h>
#include <shared/messageinfo.h>
namespace Core { namespace Core {
@ -44,8 +45,7 @@ public:
MessageHandler(Account* account); MessageHandler(Account* account);
public: public:
void sendMessage(Shared::Message data); void sendMessage(const Shared::Message& data);
void sendMessage(const Shared::Message& data, const QString& path);
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const; void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
public slots: public slots:
@ -55,20 +55,24 @@ public slots:
void onReceiptReceived(const QString& jid, const QString& id); void onReceiptReceived(const QString& jid, const QString& id);
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot); void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request); void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
void onFileUploaded(const QString& messageId, const QString& url); void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
void onFileUploadError(const QString& messageId, const QString& errMsg); void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, 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: private:
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false); 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); 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 logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url); void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
void performSending(Shared::Message data, bool newMessage = true);
void prepareUpload(const Shared::Message& data);
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
private: private:
Account* acc; Account* acc;
std::map<QString, QString> pendingStateMessages; std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
std::map<QString, Shared::Message> pendingMessages; std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
}; };
} }

View File

@ -190,7 +190,7 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact) void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
{ {
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory); connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse); connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged); connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged); connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard); connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
@ -315,14 +315,6 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
} }
} }
void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
{
RosterItem* contact = static_cast<RosterItem*>(sender());
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
emit acc->responseArchive(contact->jid, list);
}
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid) Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
{ {
RosterItem* item = 0; RosterItem* item = 0;

View File

@ -86,7 +86,6 @@ private slots:
void onContactGroupRemoved(const QString& group); void onContactGroupRemoved(const QString& group);
void onContactNameChanged(const QString& name); void onContactNameChanged(const QString& name);
void onContactSubscriptionStateChanged(Shared::SubscriptionState state); void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
void onContactHistoryResponse(const std::list<Shared::Message>& list);
void onContactAvatarChanged(Shared::Avatar, const QString& path); void onContactAvatarChanged(Shared::Avatar, const QString& path);
private: private:

View File

@ -16,23 +16,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "ui/squawk.h" #include "../shared/global.h"
#include "core/squawk.h" #include "../shared/messageinfo.h"
#include "../ui/squawk.h"
#include "signalcatcher.h" #include "signalcatcher.h"
#include "shared/global.h" #include "squawk.h"
#include <QtWidgets/QApplication>
#include <QtCore/QThread>
#include <QtCore/QObject>
#include <QSettings>
#include <QTranslator>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QSettings>
#include <QStandardPaths> #include <QStandardPaths>
#include <QTranslator>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtWidgets/QApplication>
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
qRegisterMetaType<Shared::Message>("Shared::Message"); qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
qRegisterMetaType<Shared::VCard>("Shared::VCard"); qRegisterMetaType<Shared::VCard>("Shared::VCard");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>"); qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
qRegisterMetaType<QSet<QString>>("QSet<QString>"); qRegisterMetaType<QSet<QString>>("QSet<QString>");
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState"); qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
qRegisterMetaType<Shared::Availability>("Shared::Availability"); qRegisterMetaType<Shared::Availability>("Shared::Availability");
@ -96,10 +99,7 @@ int main(int argc, char *argv[])
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount); QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
QObject::connect(&w, qOverload<const QString&, const Shared::Message&>(&Squawk::sendMessage), QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
squawk, qOverload<const QString&, const Shared::Message&>(&Core::Squawk::sendMessage));
QObject::connect(&w, qOverload<const QString&, const Shared::Message&, const QString&>(&Squawk::sendMessage),
squawk, qOverload<const QString&, const Shared::Message&, const QString&>(&Core::Squawk::sendMessage));
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact); QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
@ -109,14 +109,14 @@ int main(int argc, char *argv[])
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin); QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest); QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest); QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest); QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest); QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest); QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest); QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard); QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard); QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword); QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
@ -141,11 +141,10 @@ int main(int argc, char *argv[])
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant); QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant); QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant); QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse); QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress); QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError); QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress); QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword); QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);

View File

@ -16,13 +16,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QtWidgets/QApplication>
#include <QtCore/QDir>
#include "networkaccess.h" #include "networkaccess.h"
Core::NetworkAccess::NetworkAccess(QObject* parent): Core::NetworkAccess::NetworkAccess(QObject* parent):
QObject(parent), QObject(parent),
running(false), running(false),
manager(0), manager(0),
files("files"), storage("fileURLStorage"),
downloads(), downloads(),
uploads() uploads()
{ {
@ -33,60 +37,31 @@ Core::NetworkAccess::~NetworkAccess()
stop(); stop();
} }
void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url) void Core::NetworkAccess::downladFile(const QString& url)
{ {
std::map<QString, Transfer*>::iterator itr = downloads.find(url); std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) { if (itr != downloads.end()) {
Transfer* dwn = itr->second; qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
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 { } else {
try { try {
QString path = files.getRecord(url); std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
QFileInfo info(path); if (p.first.size() > 0) {
QFileInfo info(p.first);
if (info.exists() && info.isFile()) { if (info.exists() && info.isFile()) {
emit fileLocalPathResponse(messageId, path); emit downloadFileComplete(p.second, p.first);
} else { } else {
files.removeRecord(url); startDownload(p.second, url);
emit fileLocalPathResponse(messageId, ""); }
} else {
startDownload(p.second, url);
} }
} catch (const Archive::NotFound& e) { } catch (const Archive::NotFound& e) {
emit fileLocalPathResponse(messageId, ""); qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
storage.addFile(url);
startDownload(std::list<Shared::MessageInfo>(), url);
} catch (const Archive::Unknown& e) { } catch (const Archive::Unknown& e) {
qDebug() << "Error requesting file path:" << e.what(); qDebug() << "Error requesting file path:" << e.what();
emit fileLocalPathResponse(messageId, ""); emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
}
}
}
void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
{
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
Transfer* 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 (const Archive::NotFound& e) {
startDownload(messageId, url);
} catch (const Archive::Unknown& e) {
qDebug() << "Error requesting file path:" << e.what();
emit downloadFileError(messageId, QString("Database error: ") + e.what());
} }
} }
} }
@ -95,7 +70,7 @@ void Core::NetworkAccess::start()
{ {
if (!running) { if (!running) {
manager = new QNetworkAccessManager(); manager = new QNetworkAccessManager();
files.open(); storage.open();
running = true; running = true;
} }
} }
@ -103,7 +78,7 @@ void Core::NetworkAccess::start()
void Core::NetworkAccess::stop() void Core::NetworkAccess::stop()
{ {
if (running) { if (running) {
files.close(); storage.close();
manager->deleteLater(); manager->deleteLater();
manager = 0; manager = 0;
running = false; running = false;
@ -128,9 +103,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
qreal total = bytesTotal; qreal total = bytesTotal;
qreal progress = received/total; qreal progress = received/total;
dwn->progress = progress; dwn->progress = progress;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { emit loadFileProgress(dwn->messages, progress, false);
emit downloadFileProgress(*mItr, progress);
}
} }
} }
@ -146,9 +119,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
if (errorText.size() > 0) { if (errorText.size() > 0) {
itr->second->success = false; itr->second->success = false;
Transfer* dwn = itr->second; Transfer* dwn = itr->second;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { emit loadFileError(dwn->messages, errorText, false);
emit downloadFileError(*mItr, errorText);
}
} }
} }
} }
@ -276,50 +247,43 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
void Core::NetworkAccess::onDownloadFinished() void Core::NetworkAccess::onDownloadFinished()
{ {
QString path("");
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender()); QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString(); QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url); std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
if (itr == downloads.end()) { if (itr == downloads.end()) {
qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping"; qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
} else { } else {
Transfer* dwn = itr->second; Transfer* dwn = itr->second;
if (dwn->success) { if (dwn->success) {
qDebug() << "download success for" << url; qDebug() << "download success for" << url;
QStringList hops = url.split("/"); QStringList hops = url.split("/");
QString fileName = hops.back(); QString fileName = hops.back();
QStringList parts = fileName.split("."); QString jid;
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/"; if (dwn->messages.size() > 0) {
QString suffix(""); jid = dwn->messages.front().jid;
QStringList::const_iterator sItr = parts.begin();
QString realName = *sItr;
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
suffix += "." + (*sItr);
}
QString postfix("");
QFileInfo proposedName(path + realName + postfix + suffix);
int counter = 0;
while (proposedName.exists()) {
postfix = 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);
path = proposedName.absoluteFilePath();
QFile file(path); QFile file(path);
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly)) {
file.write(dwn->reply->readAll()); file.write(dwn->reply->readAll());
file.close(); file.close();
files.addRecord(url, path); storage.setPath(url, path);
qDebug() << "file" << path << "was successfully downloaded"; qDebug() << "file" << path << "was successfully downloaded";
} else { } else {
qDebug() << "couldn't save file" << path; qDebug() << "couldn't save file" << path;
path = ""; path = QString();
} }
} }
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { if (path.size() > 0) {
emit fileLocalPathResponse(*mItr, path); emit downloadFileComplete(dwn->messages, path);
} else {
//TODO do I need to handle the failure here or it's already being handled in error?
//emit loadFileError(dwn->messages, path, false);
}
} }
dwn->reply->deleteLater(); dwn->reply->deleteLater();
@ -328,9 +292,9 @@ void Core::NetworkAccess::onDownloadFinished()
} }
} }
void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url) void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
{ {
Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0}); Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
QNetworkRequest req(url); QNetworkRequest req(url);
dwn->reply = manager->get(req); dwn->reply = manager->get(req);
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress); connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
@ -341,7 +305,7 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
#endif #endif
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished); connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
downloads.insert(std::make_pair(url, dwn)); downloads.insert(std::make_pair(url, dwn));
emit downloadFileProgress(messageId, 0); emit loadFileProgress(dwn->messages, 0, false);
} }
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
@ -350,16 +314,16 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
QString url = rpl->url().toString(); QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url); std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
if (itr == uploads.end()) { if (itr == uploads.end()) {
qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping"; qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
} else { } else {
QString errorText = getErrorText(code); QString errorText = getErrorText(code);
if (errorText.size() > 0) { if (errorText.size() > 0) {
itr->second->success = false; itr->second->success = false;
Transfer* upl = itr->second; Transfer* upl = itr->second;
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { emit loadFileError(upl->messages, errorText, true);
emit uploadFileError(*mItr, errorText);
}
} }
//TODO deletion?
} }
} }
@ -369,17 +333,14 @@ void Core::NetworkAccess::onUploadFinished()
QString url = rpl->url().toString(); QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url); std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
if (itr == downloads.end()) { if (itr == downloads.end()) {
qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping"; qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
} else { } else {
Transfer* upl = itr->second; Transfer* upl = itr->second;
if (upl->success) { if (upl->success) {
qDebug() << "upload success for" << url; qDebug() << "upload success for" << url;
files.addRecord(upl->url, upl->path);
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { storage.addFile(upl->messages, upl->url, upl->path);
emit fileLocalPathResponse(*mItr, upl->path); emit uploadFileComplete(upl->messages, upl->url);
emit uploadFileComplete(*mItr, upl->url);
}
} }
upl->reply->deleteLater(); upl->reply->deleteLater();
@ -403,94 +364,29 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
qreal total = bytesTotal; qreal total = bytesTotal;
qreal progress = received/total; qreal progress = received/total;
upl->progress = progress; upl->progress = progress;
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { emit loadFileProgress(upl->messages, progress, true);
emit uploadFileProgress(*mItr, progress);
}
}
}
void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
{
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
QNetworkRequest req(url);
QFile* file = new QFile(path);
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(url, upl));
emit downloadFileProgress(messageId, 0);
} else {
qDebug() << "couldn't upload file" << path;
emit uploadFileError(messageId, "Error opening file");
delete file;
}
}
void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path)
{
std::map<QString, Transfer*>::iterator itr = uploads.find(url);
if (itr != uploads.end()) {
Transfer* upl = itr->second;
std::set<QString>::const_iterator mItr = upl->messages.find(messageId);
if (mItr == upl->messages.end()) {
upl->messages.insert(messageId);
}
emit uploadFileProgress(messageId, upl->progress);
} else {
try {
QString ePath = files.getRecord(url);
if (ePath == path) {
QFileInfo info(path);
if (info.exists() && info.isFile()) {
emit fileLocalPathResponse(messageId, path);
} else {
files.removeRecord(url);
startUpload(messageId, url, path);
}
} else {
QFileInfo info(path);
if (info.exists() && info.isFile()) {
files.changeRecord(url, path);
emit fileLocalPathResponse(messageId, path);
} else {
files.removeRecord(url);
startUpload(messageId, url, path);
}
}
} catch (const Archive::NotFound& e) {
startUpload(messageId, url, path);
} catch (const Archive::Unknown& e) {
qDebug() << "Error requesting file path on upload:" << e.what();
emit uploadFileError(messageId, QString("Database error: ") + e.what());
}
} }
} }
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
{ {
return ""; //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url QString p;
try {
p = storage.getUrl(path);
} catch (const Archive::NotFound& err) {
} catch (...) {
throw;
}
return p;
} }
bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId) void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
{
return false; //TODO this is a way to avoid parallel uploading of the same files by different chats
// message is is supposed to be added to the uploading messageids list
// the result should be true if there was an uploading file with this path
// message id can be empty, then it's just to check and not to add
}
void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
{ {
QFile* file = new QFile(path); QFile* file = new QFile(path);
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file}); Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
QNetworkRequest req(put); QNetworkRequest req(put);
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) { for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8()); req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
@ -506,10 +402,99 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
#endif #endif
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished); connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
uploads.insert(std::make_pair(put.toString(), upl)); uploads.insert(std::make_pair(put.toString(), upl));
emit downloadFileProgress(messageId, 0); emit loadFileProgress(upl->messages, 0, true);
} else { } else {
qDebug() << "couldn't upload file" << path; qDebug() << "couldn't upload file" << path;
emit uploadFileError(messageId, "Error opening file"); emit loadFileError(upl->messages, "Error opening file", true);
delete file; 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 = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
path += "/" + QApplication::applicationName();
if (jid.size() > 0) {
path += "/" + jid;
}
QDir location(path);
if (!location.exists()) {
bool res = location.mkpath(path);
if (!res) {
return "";
} else {
return path;
}
}
return path;
}
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
{
QStringList parts = name.split(".");
QString suffix("");
QStringList::const_iterator sItr = parts.begin();
QString realName = *sItr;
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
suffix += "." + (*sItr);
}
QString postfix("");
QFileInfo proposedName(path + "/" + realName + suffix);
int counter = 0;
while (proposedName.exists()) {
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
proposedName = QFileInfo(path + "/" + realName + count + suffix);
}
return proposedName.absoluteFilePath();
}
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);
}

View File

@ -29,13 +29,15 @@
#include <set> #include <set>
#include "storage.h" #include "urlstorage.h"
namespace Core { namespace Core {
/** /**
* @todo write docs * @todo write docs
*/ */
//TODO Need to describe how to get rid of records when file is no longer reachable;
class NetworkAccess : public QObject class NetworkAccess : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -48,26 +50,27 @@ public:
void stop(); void stop();
QString getFileRemoteUrl(const QString& path); QString getFileRemoteUrl(const QString& path);
bool isUploading(const QString& path, const QString& messageId = ""); QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers); 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: signals:
void fileLocalPathResponse(const QString& messageId, const QString& path); void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
void downloadFileProgress(const QString& messageId, qreal value); void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
void downloadFileError(const QString& messageId, const QString& path); void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
void uploadFileProgress(const QString& messageId, qreal value); void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
void uploadFileError(const QString& messageId, const QString& path);
void uploadFileComplete(const QString& messageId, const QString& url);
public slots: public slots:
void fileLocalPathRequest(const QString& messageId, const QString& url); void downladFile(const QString& url);
void downladFileRequest(const QString& messageId, const QString& url); void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
void uploadFileRequest(const QString& messageId, const QString& url, const QString& path); void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
private: private:
void startDownload(const QString& messageId, const QString& url); void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
void startUpload(const QString& messageId, const QString& url, const QString& path);
QString getErrorText(QNetworkReply::NetworkError code); QString getErrorText(QNetworkReply::NetworkError code);
QString prepareDirectory(const QString& jid);
QString checkFileName(const QString& name, const QString& path);
private slots: private slots:
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
@ -80,12 +83,12 @@ private slots:
private: private:
bool running; bool running;
QNetworkAccessManager* manager; QNetworkAccessManager* manager;
Storage files; UrlStorage storage;
std::map<QString, Transfer*> downloads; std::map<QString, Transfer*> downloads;
std::map<QString, Transfer*> uploads; std::map<QString, Transfer*> uploads;
struct Transfer { struct Transfer {
std::set<QString> messages; std::list<Shared::MessageInfo> messages;
qreal progress; qreal progress;
QNetworkReply* reply; QNetworkReply* reply;
bool success; bool success;

View File

@ -1,37 +1,9 @@
cmake_minimum_required(VERSION 3.0)
project(pse)
if (WITH_KWALLET) if (WITH_KWALLET)
set(CMAKE_AUTOMOC ON) target_sources(squawk PRIVATE
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
set(kwalletPSE_SRC
kwallet.cpp kwallet.cpp
kwallet.h
) )
add_library(kwalletPSE ${kwalletPSE_SRC}) add_subdirectory(wrappers)
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) endif ()
target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
target_link_libraries(kwalletPSE Qt5::Core)
set(kwalletW_SRC
wrappers/kwallet.cpp
)
add_library(kwalletWrapper SHARED ${kwalletW_SRC})
target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
target_link_libraries(kwalletWrapper KF5::Wallet)
target_link_libraries(kwalletWrapper Qt5::Core)
install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()

View File

@ -0,0 +1,2 @@
add_library(kwalletWrapper SHARED kwallet.cpp)
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)

View File

@ -122,7 +122,22 @@ void Core::RosterItem::nextRequest()
{ {
if (syncronizing) { if (syncronizing) {
if (requestedCount != -1) { if (requestedCount != -1) {
emit historyResponse(responseCache); bool last = false;
if (archiveState == beginning || archiveState == complete) {
QString firstId = archive->oldestId();
if (responseCache.size() == 0) {
if (requestedBefore == firstId) {
last = true;
}
} else {
if (responseCache.front().getId() == firstId) {
last = true;
}
}
} else if (archiveState == empty && responseCache.size() == 0) {
last = true;
}
emit historyResponse(responseCache, last);
} }
} }
if (requestCache.size() > 0) { if (requestCache.size() > 0) {
@ -360,6 +375,11 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
archiveState = complete; archiveState = complete;
archive->setFromTheBeginning(true); archive->setFromTheBeginning(true);
} }
if (added == 0 && wasEmpty) {
archiveState = empty;
nextRequest();
break;
}
if (requestedCount != -1) { if (requestedCount != -1) {
QString before; QString before;
if (responseCache.size() > 0) { if (responseCache.size() > 0) {
@ -378,7 +398,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
} catch (const Archive::Empty& e) { } catch (const Archive::Empty& e) {
} }
if (!found || requestedCount > responseCache.size()) { if (!found || requestedCount > int(responseCache.size())) {
if (archiveState == complete) { if (archiveState == complete) {
nextRequest(); nextRequest();
} else { } else {
@ -529,7 +549,7 @@ void Core::RosterItem::clearArchiveRequests()
requestedBefore = ""; requestedBefore = "";
for (const std::pair<int, QString>& pair : requestCache) { for (const std::pair<int, QString>& pair : requestCache) {
if (pair.first != -1) { if (pair.first != -1) {
emit historyResponse(responseCache); //just to notify those who still waits with whatever happened to be left in caches yet emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet
} }
responseCache.clear(); responseCache.clear();
} }
@ -549,3 +569,20 @@ void Core::RosterItem::downgradeDatabaseState()
archiveState = ArchiveState::chunk; 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);
}

View File

@ -78,10 +78,12 @@ public:
void clearArchiveRequests(); void clearArchiveRequests();
void downgradeDatabaseState(); void downgradeDatabaseState();
Shared::Message getMessage(const QString& id);
signals: signals:
void nameChanged(const QString& name); void nameChanged(const QString& name);
void subscriptionStateChanged(Shared::SubscriptionState state); 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 needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
void avatarChanged(Shared::Avatar, const QString& path); void avatarChanged(Shared::Avatar, const QString& path);
void requestVCard(const QString& jid); void requestVCard(const QString& jid);

View File

@ -32,11 +32,10 @@ Core::Squawk::Squawk(QObject* parent):
,kwallet() ,kwallet()
#endif #endif
{ {
connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError); connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress); connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError);
#ifdef WITH_KWALLET #ifdef WITH_KWALLET
if (kwallet.supportState() == PSE::KWallet::success) { if (kwallet.supportState() == PSE::KWallet::success) {
@ -168,7 +167,7 @@ void Core::Squawk::addAccount(
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard); connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError); connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
QMap<QString, QVariant> map = { QMap<QString, QVariant> map = {
{"login", login}, {"login", login},
@ -336,17 +335,6 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
itr->second->sendMessage(data); itr->second->sendMessage(data);
} }
void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug("An attempt to send a message with non existing account, skipping");
return;
}
itr->second->sendMessage(data, path);
}
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before) void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
{ {
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
@ -357,10 +345,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
itr->second->requestArchive(jid, count, before); 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()); 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) void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
@ -604,14 +592,9 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
itr->second->addRoomRequest(jid, nick, password, autoJoin); 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); network.downladFile(url);
}
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
{
network.downladFileRequest(messageId, url);
} }
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName) void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
@ -762,3 +745,26 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString&
emit changeAccount(login, {{"password", password}}); emit changeAccount(login, {{"password", password}});
accountReady(); accountReady();
} }
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";
}
}
}

View File

@ -51,31 +51,39 @@ public:
signals: signals:
void quit(); void quit();
void ready(); void ready();
void newAccount(const QMap<QString, QVariant>&); void newAccount(const QMap<QString, QVariant>&);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data); void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account); void removeAccount(const QString& account);
void addGroup(const QString& account, const QString& name); void addGroup(const QString& account, const QString& name);
void removeGroup(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 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);
void removeContact(const QString& account, const QString& jid, const QString& group); 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 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 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 removePresence(const QString& account, const QString& jid, const QString& name);
void stateChanged(Shared::Availability state); void stateChanged(Shared::Availability state);
void accountMessage(const QString& account, const Shared::Message& data); 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 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 changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
void removeRoom(const QString& account, const QString jid); 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 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 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 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 fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
void downloadFileProgress(const QString& messageId, qreal value); void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
void uploadFileError(const QString& messageId, const QString& error); void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void uploadFileProgress(const QString& messageId, qreal value); void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void responseVCard(const QString& jid, const Shared::VCard& card); 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 changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void requestPassword(const QString& account); void requestPassword(const QString& account);
@ -83,15 +91,18 @@ signals:
public slots: public slots:
void start(); void start();
void stop(); void stop();
void newAccountRequest(const QMap<QString, QVariant>& map); void newAccountRequest(const QMap<QString, QVariant>& map);
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map); void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
void removeAccountRequest(const QString& name); void removeAccountRequest(const QString& name);
void connectAccount(const QString& account); void connectAccount(const QString& account);
void disconnectAccount(const QString& account); void disconnectAccount(const QString& account);
void changeState(Shared::Availability state); void changeState(Shared::Availability state);
void sendMessage(const QString& account, const Shared::Message& data); void sendMessage(const QString& account, const Shared::Message& data);
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
void requestArchive(const QString& account, const QString& jid, int count, const QString& before); 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 subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(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); void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
@ -99,15 +110,18 @@ public slots:
void removeContactRequest(const QString& account, const QString& jid); void removeContactRequest(const QString& account, const QString& jid);
void renameContactRequest(const QString& account, const QString& jid, const QString& newName); 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 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 setRoomJoined(const QString& account, const QString& jid, bool joined);
void setRoomAutoJoin(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 addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void removeRoomRequest(const QString& account, const QString& jid); 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 requestVCard(const QString& account, const QString& jid);
void uploadVCard(const QString& account, const Shared::VCard& card); void uploadVCard(const QString& account, const Shared::VCard& card);
void responsePassword(const QString& account, const QString& password); void responsePassword(const QString& account, const QString& password);
void onLocalPathInvalid(const QString& path);
private: private:
typedef std::deque<Account*> Accounts; typedef std::deque<Account*> Accounts;
@ -146,7 +160,7 @@ private slots:
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data); void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void onAccountRemovePresence(const QString& jid, const QString& name); void onAccountRemovePresence(const QString& jid, const QString& name);
void onAccountMessage(const Shared::Message& data); 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 onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data); void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
void onAccountRemoveRoom(const QString jid); void onAccountRemoveRoom(const QString jid);
@ -155,6 +169,8 @@ private slots:
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick); void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data); void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
void onWalletOpened(bool success); void onWalletOpened(bool success);
void onWalletResponsePassword(const QString& login, const QString& password); void onWalletResponsePassword(const QString& login, const QString& password);
void onWalletRejectPassword(const QString& login); void onWalletRejectPassword(const QString& login);

491
core/urlstorage.cpp Normal file
View File

@ -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;
}

99
core/urlstorage.h Normal file
View File

@ -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
external/signal-protocol-c vendored Submodule

@ -0,0 +1 @@
Subproject commit 3a83a4f4ed2302ff6e68ab569c88793b50c22d28

View File

@ -1,16 +1,10 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0)
project(simplecrypt) project(simplecrypt LANGUAGES CXX)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED) find_package(Qt5 COMPONENTS Core REQUIRED)
set(simplecrypt_SRC add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
simplecrypt.cpp
)
# Tell CMake to create the helloworld executable
add_library(simpleCrypt ${simplecrypt_SRC})
# Use the Widgets module from Qt 5.
target_link_libraries(simpleCrypt Qt5::Core) target_link_libraries(simpleCrypt Qt5::Core)

3
packaging/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
configure_file(squawk.desktop squawk.desktop COPYONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)

4
plugins/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
if (WITH_KIO)
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
endif ()

View File

@ -0,0 +1,8 @@
#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);
}

25
qomemo/CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
target_sources(squawk PRIVATE
bundle.cpp
bundle.h
database.cpp
database.h
device.cpp
device.h
device_key_storage.cpp
device_key_storage.h
device_service.cpp
device_service.h
key.cpp
key.h
qomemo.cpp
qomemo.h
sce.cpp
sce.h
user_device_list.cpp
user_device_list.h
qxmpp_omemo_manager.cpp
qxmpp_omemo_manager.h
)
add_subdirectory(signal)
add_subdirectory(variant)

5
qomemo/bundle.cpp Normal file
View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "bundle.h"

34
qomemo/bundle.h Normal file
View File

@ -0,0 +1,34 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QList>
#include <QString>
#include <QBuffer>
class QXmppPubSubIq;
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class PreKey {
public:
int id;
QByteArray data;
};
class Bundle {
public:
QByteArray spk;
int spkId;
QByteArray spks;
QByteArray ik;
QList<PreKey> prekeys;
};
} // namespace QXmpp::Omemo

180
qomemo/database.cpp Normal file
View File

@ -0,0 +1,180 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "database.h"
#include "bundle.h"
#include <QDebug>
#include <QDir>
#include <QException>
#include <QStandardPaths>
#include <QString>
using namespace QXmpp::Omemo;
Database::Database(QString jid) : jid(std::move(jid)) {
auto cacheLocation =
QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
auto path = QString("%1/.omemo/%2").arg(cacheLocation, jid);
QDir cache(path);
if (!cache.exists() && !cache.mkpath(path)) {
qWarning() << "Could not create:" << path;
throw QException();
}
mdb_env_create(&env);
mdb_env_set_maxdbs(env, 5);
mdb_env_set_mapsize(env, 512UL * 1024UL * 1024UL);
mdb_env_open(env, path.toStdString().c_str(), 0, 0664);
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
mdb_dbi_open(txn, "keys", MDB_CREATE, &dbiKeys);
mdb_dbi_open(txn, "devices", MDB_CREATE, &dbiDevices);
mdb_dbi_open(txn, "identity_keys", MDB_CREATE, &dbiIdentityKeys);
mdb_txn_commit(txn);
}
Database::~Database() {
mdb_dbi_close(env, dbiKeys);
mdb_dbi_close(env, dbiDevices);
mdb_dbi_close(env, dbiIdentityKeys);
mdb_env_close(env);
}
std::optional<QByteArray> Database::loadIdentityKeySecret(int deviceId) { return std::nullopt; }
bool Database::saveIdentityKeySecret(int deviceId,
const QByteArray &identityKeySecret) {
MDB_val mdbKey, mdbValue;
auto key = QString("%1/secret").arg(QString::number(deviceId)).toStdString();
mdbKey.mv_data = key.data();
mdbKey.mv_size = key.size();
mdbValue.mv_data = const_cast<char *>(identityKeySecret.data());
mdbValue.mv_size = identityKeySecret.size();
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
auto err = mdb_put(txn, dbiIdentityKeys, &mdbKey, &mdbValue, MDB_NOOVERWRITE);
if (!err) {
mdb_txn_commit(txn);
return true;
}
qWarning() << "could not save identity key secret:" << mdb_strerror(err);
mdb_txn_abort(txn);
return false;
}
std::optional<int> Database::loadActiveDeviceId() {
MDB_val key, value;
key.mv_data = (void *) "active";
key.mv_size = sizeof("active");
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
auto err = mdb_get(txn, dbiIdentityKeys, &key, &value);
if (err) {
qWarning() << "could not load active device id:" << mdb_strerror(err);
mdb_txn_abort(txn);
return std::nullopt;
}
if (value.mv_size != sizeof(int)) {
qWarning() << "mv_size is" << value.mv_size << "instead of" << sizeof(int);
mdb_txn_abort(txn);
return std::nullopt;
}
auto id = *reinterpret_cast<int *>(value.mv_data);
mdb_txn_abort(txn);
return id;
}
bool Database::saveActiveDeviceId(int deviceId) {
MDB_val key, value;
key.mv_data = (void *) "active";
key.mv_size = sizeof("active");
value.mv_data = &deviceId;
value.mv_size = sizeof(deviceId);
MDB_txn *txn;
mdb_txn_begin(env, nullptr, 0, &txn);
auto err = mdb_put(txn, dbiIdentityKeys, &key, &value, 0);
if (err) {
qWarning() << "could not save active device id" << mdb_strerror(err);
return false;
}
err = mdb_txn_commit(txn);
if (err) {
qWarning() << "could not save active device id" << mdb_strerror(err);
return false;
}
return true;
}
bool Database::saveIdentityKey(int deviceId, const QByteArray &identityKey) {
return false;
}
std::optional<QByteArray> Database::loadIdentityKey(int deviceId) {
return std::nullopt;
}
bool Database::containsPreKey() {
return false;
}
std::optional<KeyPair> Database::loadPreKey(int deviceId, int id) {
return std::nullopt;
}
bool Database::savePreKey(int deviceId, int id, const KeyPair &preKey) {
return false;
}
std::optional<Bundle> Database::loadBundle(int deviceId) {
Bundle result{};
auto ik = loadIdentityKey(deviceId);
result.ik = ik.value();
auto spk = loadSignedPreKey(deviceId);
result.spk = spk->key.publicKey;
result.spks = spk->signature;
result.spkId = spk->id;
// PreKeys
return result;
}
bool Database::saveBundle(int deviceId, const Bundle &bundle) {
return false;
}
std::optional<SignedPreKey> Database::loadSignedPreKey(int deviceId) {
return std::optional<SignedPreKey>();
}
bool Database::saveSignedPreKey(int deviceId, const SignedPreKey &signedPreKey) {
return false;
}

56
qomemo/database.h Normal file
View File

@ -0,0 +1,56 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <QBuffer>
#include <QString>
#include <lmdb.h>
#include "key.h"
namespace QXmpp::Omemo {
class Bundle;
class Database {
public:
explicit Database(QString jid);
~Database();
Database(const Database &) = delete;
Database(Database &&) = delete;
Database &operator=(const Database &) = delete;
// For local user
std::optional<int> loadActiveDeviceId();
bool saveActiveDeviceId(int deviceId);
std::optional<QByteArray> loadIdentityKeySecret(int deviceId);
bool saveIdentityKeySecret(int deviceId, const QByteArray &identityKeySecret);
std::optional<Bundle> loadBundle(int deviceId);
bool saveBundle(int deviceId, const Bundle& bundle);
// For any user
std::optional<QByteArray> loadIdentityKey(int deviceId);
bool saveIdentityKey(int deviceId, const QByteArray &identityKey);
bool containsPreKey();
std::optional<KeyPair> loadPreKey(int deviceId, int id);
bool savePreKey(int deviceId, int id, const KeyPair &preKey);
std::optional<SignedPreKey> loadSignedPreKey(int deviceId);
bool saveSignedPreKey(int deviceId, const SignedPreKey& signedPreKey);
const QString jid;
private:
MDB_env *env{};
MDB_dbi dbiDevices{};
MDB_dbi dbiKeys{};
MDB_dbi dbiPreKeys{};
MDB_dbi dbiIdentityKeys{};
};
} // namespace QXmpp::Omemo

5
qomemo/device.cpp Normal file
View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "device.h"

25
qomemo/device.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <QList>
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class Device {
public:
int id;
};
class DeviceList {
public:
QList<Device> devices;
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "device_key_storage.h"
#include <QRandomGenerator>
int QXmpp::Omemo::DeviceKeyStorage::generateDeviceId() {
QRandomGenerator random{};
return 1 + random.bounded(INT32_MAX - 1);
}
QXmpp::Omemo::DeviceKeyStorage::DeviceKeyStorage(int deviceId) : deviceId(deviceId) {}

View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
namespace QXmpp::Omemo {
class DeviceKeyStorage {
public:
static int generateDeviceId();
explicit DeviceKeyStorage(int deviceId);
const int deviceId;
};
} // namespace QXmpp::Omemo

35
qomemo/device_service.cpp Normal file
View File

@ -0,0 +1,35 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "device_service.h"
#include "device.h"
using namespace QXmpp::Omemo;
DeviceService::DeviceService(QObject *parent) : QObject(parent) {}
void DeviceService::onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list) {
for (const auto &device : list.devices) {
qInfo() << "Got device for" << jid << ":" << device.id;
}
}
QSharedPointer<Database> DeviceService::getDatabase(const QString &jid) {
if (!databases.contains(jid)) {
databases.insert(jid, QSharedPointer<Database>::create(jid));
}
return databases[jid];
}
void DeviceService::addIdentity(const QString &jid, int deviceId, const QByteArray& publicKey) {
auto db = getDatabase(jid);
db->saveIdentityKey(deviceId, publicKey);
}
void DeviceService::onDeviceListNotFound(const QString &jid) {
qInfo() << "Device list not found:" << jid;
}

38
qomemo/device_service.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QBuffer>
#include "qomemo.h"
#include "user_device_list.h"
#include "database.h"
#include <QXmppClient.h>
namespace QXmpp::Omemo {
class DeviceList;
class DeviceService : public QObject {
Q_OBJECT
public:
explicit DeviceService(QObject *parent);
QSharedPointer<Database> getDatabase(const QString& jid);
public slots:
void addIdentity(const QString& jid, int deviceId, const QByteArray& publicKey);
void onDeviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void onDeviceListNotFound(const QString &jid);
private:
QMap<QString, QSharedPointer<Database>> databases{};
};
} // namespace QXmpp::Omemo

6
qomemo/key.cpp Normal file
View File

@ -0,0 +1,6 @@
/*
* Created by victoria on 2021-05-15.
*/
#include "key.h"

27
qomemo/key.h Normal file
View File

@ -0,0 +1,27 @@
/*
* Created by victoria on 2021-05-15.
*/
#pragma once
#include <optional>
#include <QByteArray>
namespace QXmpp::Omemo {
class KeyPair {
public:
QByteArray publicKey{};
std::optional<QByteArray> secretKey{ std::nullopt };
};
class SignedPreKey {
public:
int id{ 0 };
KeyPair key{};
QByteArray signature{};
};
}

94
qomemo/qomemo.cpp Normal file
View File

@ -0,0 +1,94 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qomemo.h"
#include "sce.h"
#include <shared/qxmppfactories.h>
#include <QBuffer>
#include <QDebug>
#include <QDomDocument>
using namespace QXmpp::Factories;
QXmppElement QXmpp::Omemo::EncryptedMessage::header() const {
auto result = createElement("header");
result.setAttribute("sid", QString::number(fromDeviceId));
auto ivNode = createElement("iv");
ivNode.setValue(iv);
for (const auto &key : keys) {
result.appendChild(key.toXml());
}
return result;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::toXml() const {
auto result = createElement("encrypted", "eu.siacs.conversations.axolotl");
result.appendChild(header());
// TODO: Payload is optional
result.appendChild(payload());
return result;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::payload() const {
QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
QXmlStreamWriter writer(&buffer);
message.toXml(&writer);
QDomDocument doc;
doc.setContent(buffer.data(), true);
QXmppElement root(doc.documentElement());
root.setTagName("payload");
return root;
}
QXmppElement QXmpp::Omemo::EncryptedMessage::content() const {
auto envelope = createElement("content", "urn:xmpp:sce:0");
envelope.appendChild(payload());
if (!from.isEmpty()) {
auto fromNode = createElement("from");
fromNode.setAttribute("jid", from);
envelope.appendChild(fromNode);
}
if (!to.isEmpty()) {
auto toNode = createElement("to");
toNode.setAttribute("jid", to);
envelope.appendChild(toNode);
}
if (!timestamp.isNull()) {
auto timeNode = createElement("time");
timeNode.setAttribute("stamp", timestamp.toString(Qt::DateFormat::ISODate));
envelope.appendChild(timeNode);
}
auto rpad = createElement("rpad");
rpad.setValue(QXmpp::Sce::generatePadding());
envelope.appendChild(rpad);
return envelope;
}
QXmppElement QXmpp::Omemo::MessageKey::toXml() const {
auto result = createElement("key");
result.setAttribute("rid", QString::number(receivingDeviceId));
if (prekey)
result.setAttribute("prekey", "true");
result.setValue(key);
return result;
}

42
qomemo/qomemo.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QXmppPubSubIq.h>
#include <QDateTime>
#include <QXmppMessage.h>
namespace QXmpp::Omemo {
class MessageKey {
public:
[[nodiscard]] QXmppElement toXml() const;
int receivingDeviceId{};
bool prekey{};
QString key{};
};
class EncryptedMessage {
public:
[[nodiscard]] QXmppElement header() const;
[[nodiscard]] QXmppElement content() const;
[[nodiscard]] QXmppElement toXml() const;
[[nodiscard]] QXmppElement payload() const;
int fromDeviceId{};
QList<MessageKey> keys{};
QString from{};
QString to{};
QDateTime timestamp{};
QString iv{};
QXmppMessage message{};
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,292 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "qxmpp_omemo_manager.h"
#include "qomemo/signal/context.h"
#include "bundle.h"
#include "device.h"
#include "variant/conversations.h"
#include <QDomElement>
#include <QXmppClient.h>
#include <QXmppPubSubIq.h>
#include <iostream>
#include <QRandomGenerator64>
#include <external/signal-protocol-c/src/signal_protocol_internal.h>
using namespace QXmpp::Omemo;
Manager::Manager()
: deviceService{new DeviceService(this)},
omemoVariant(new Variant::Conversations),
signalContext(new Signal::Context) {
connect(this, &Manager::deviceListReceived, deviceService.get(), &DeviceService::onDeviceListReceived);
connect(this, &Manager::deviceListNotFound, deviceService.get(), &DeviceService::onDeviceListNotFound);
connect(this, &Manager::deviceListNotFound, this, [this](const QString &jid) {
if (jid == client()->configuration().jidBare())
generateDeviceListForSelf();
});
connect(this, &Manager::deviceListReceived, this, [this](const QString &jid, const DeviceList &currentList) {
if (jid == client()->configuration().jidBare())
generateDeviceForSelfIfNeeded(currentList);
});
}
bool QXmpp::Omemo::Manager::handleStanza(const QDomElement &stanza) {
QString str{};
QTextStream info(&str);
stanza.save(info, 4);
std::cout << str.toStdString();
if (handleDeviceList(stanza) || handleMissingDeviceList(stanza) || handleEncryptedMessage(stanza))
return true;
return false;
}
bool Manager::handleDeviceList(const QDomElement &stanza) {
if (!(stanza.tagName() == "iq"))
return false;
if (!(stanza.attribute("type") == "result"))
return false;
auto pubsub = stanza.firstChildElement("pubsub");
if (pubsub.isNull())
return false;
auto items = pubsub.firstChildElement("items");
if (!(items.attribute("node") == omemoVariant->getDeviceListNode()))
return false;
auto deviceList = omemoVariant->latestDeviceListFromPubSubNode(items);
if (!deviceList.has_value())
return false;
emit deviceListReceived(stanza.attribute("from"), deviceList.value());
return true;
}
bool Manager::handleMissingDeviceList(const QDomElement &stanza) {
if (stanza.tagName() != "iq")
return false;
if (stanza.attribute("type") != "error")
return false;
auto pubsub = stanza.firstChildElement("pubsub");
if (pubsub.isNull())
return false;
auto items = pubsub.firstChildElement("items");
if (items.attribute("node") != omemoVariant->getDeviceListNode())
return false;
auto error = stanza.firstChildElement("error");
if (error.namespaceURI() != "jabber:client" || error.attribute("code") != "404")
return false;
qDebug() << "Got 404 deviceList for" << stanza.attribute("from");
emit deviceListNotFound(stanza.attribute("from"));
return true;
}
bool Manager::handleEncryptedMessage(const QDomElement &stanza) {
if (stanza.tagName() != "message")
return false;
auto encrypted = stanza.firstChildElement("encrypted");
if (encrypted.isNull())
return false;
qDebug() << "!!!! Got encrypted message!!";
return true;
}
void QXmpp::Omemo::Manager::setClient(QXmppClient *client) {
QXmppClientExtension::setClient(client);
if (!client)
return;
QObject::connect(client, &QXmppClient::connected, this,
&Manager::fetchOwnDevices);
}
void QXmpp::Omemo::Manager::fetchOwnDevices() {
QXmppPubSubIq iq{};
iq.setFrom(client()->configuration().jid());
iq.setTo(client()->configuration().jidBare());
iq.setType(QXmppIq::Get);
iq.setQueryNode(omemoVariant->getDeviceListNode());
client()->sendPacket(iq);
}
QSharedPointer<DeviceService> Manager::getDeviceService() {
return deviceService;
}
void Manager::publishDeviceList(const DeviceList &deviceList) {
// QXmppPubSubIq iq{};
// iq.setFrom(client()->configuration().jid());
// iq.setType(QXmppIq::Set);
// iq.setQueryNode(omemoVariant->getDeviceListNode());
// iq.setItems()
auto iq = omemoVariant->deviceListSetIq(deviceList);
iq.setFrom(client()->configuration().jid());
QString str{};
QXmlStreamWriter info(&str);
iq.toXml(&info);
qInfo() << str;
client()->sendPacket(iq);
}
void Manager::generateDeviceListForSelf() {
qInfo() << "Generate device for self...";
generateDeviceForSelfIfNeeded(DeviceList());
}
void Manager::publishBundle(int deviceId, const Bundle &bundle) {
auto iq = omemoVariant->bundleSetIq(deviceId, bundle);
iq.setFrom(client()->configuration().jid());
QString str{};
QXmlStreamWriter info(&str);
iq.toXml(&info);
qInfo() << str;
client()->sendPacket(iq);
}
void Manager::generateDeviceForSelfIfNeeded(const DeviceList &currentList) {
auto db = deviceService->getDatabase(client()->configuration().jidBare());
auto activeId = db->loadActiveDeviceId();
if (activeId.has_value()) {
qInfo() << "Current device:" << *activeId;
bool found = false;
for (const auto &d : currentList.devices)
if (d.id == *activeId)
found = true;
if (found)
return;
qInfo() << "Could not find device" << *activeId << ", generating new one";
}
qInfo() << "Generating device";
auto deviceId = QRandomGenerator64::system()->bounded(INT32_MAX);
db->saveActiveDeviceId(deviceId);
Device device{};
device.id = deviceId;
auto updatedList = currentList;
updatedList.devices.push_back(device);
publishDeviceList(updatedList);
auto bundle = generateAndSaveBundle(deviceId);
publishBundle(deviceId, bundle);
}
Bundle Manager::generateAndSaveBundle(int deviceId) {
auto database = deviceService->getDatabase(client()->configuration().jidBare());
Bundle result{};
ec_key_pair *ecKeyPair;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecKeyPair);
signal_buffer *ecPublic;
ec_public_key_serialize(&ecPublic, ec_key_pair_get_public(ecKeyPair));
signal_buffer *ecSecret;
ec_private_key_serialize(&ecSecret, ec_key_pair_get_private(ecKeyPair));
QByteArray identityKey((const char *) signal_buffer_const_data(ecPublic), (int) signal_buffer_len(ecPublic));
QByteArray identityKeySecret((const char *) signal_buffer_const_data(ecSecret), (int) signal_buffer_len(ecSecret));
database->saveIdentityKey(deviceId, identityKey);
database->saveIdentityKeySecret(deviceId, identityKeySecret);
result.ik = identityKey;
// Generate SPK
ec_key_pair *ecSpk;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &ecSpk);
signal_buffer *spkPublic, *spkSecret;
ec_public_key_serialize(&spkPublic, ec_key_pair_get_public(ecSpk));
ec_private_key_serialize(&spkSecret, ec_key_pair_get_private(ecSpk));
// Generate SPKs
signal_buffer *signature;
curve_calculate_signature(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &signature,
ec_key_pair_get_private(ecKeyPair), signal_buffer_const_data(spkPublic),
signal_buffer_len(spkPublic));
SignedPreKey spk{};
spk.id = 1;
spk.key.publicKey = QByteArray((const char *) signal_buffer_const_data(spkPublic), (int) signal_buffer_len(spkPublic));
spk.key.secretKey = QByteArray((const char *) signal_buffer_const_data(spkSecret), (int) signal_buffer_len(spkSecret));
spk.signature = QByteArray((const char *) signal_buffer_const_data(signature), (int) signal_buffer_len(signature));
result.spk = spk.key.publicKey;
result.spks = spk.signature;
result.spkId = 1;
// Generate 100 PK
for (auto i = 1; i <= 100; ++i) {
ec_key_pair *currentPreKey;
curve_generate_key_pair(signalContext->temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped(), &currentPreKey);
signal_buffer *pkPublic, *pkSecret;
ec_public_key_serialize(&pkPublic, ec_key_pair_get_public(currentPreKey));
ec_private_key_serialize(&pkSecret, ec_key_pair_get_private(currentPreKey));
KeyPair preKey{};
preKey.publicKey = QByteArray((const char *) signal_buffer_const_data(pkPublic), (int) signal_buffer_len(pkPublic));
preKey.secretKey = QByteArray((const char *) signal_buffer_const_data(pkSecret), (int) signal_buffer_len(pkSecret));
database->savePreKey(deviceId, i, preKey);
PreKey pk{};
pk.data = preKey.publicKey;
pk.id = i;
result.prekeys.append(pk);
signal_buffer_free(pkPublic);
signal_buffer_free(pkSecret);
SIGNAL_UNREF(currentPreKey);
}
signal_buffer_free(signature);
signal_buffer_free(ecPublic);
signal_buffer_free(ecSecret);
SIGNAL_UNREF(ecKeyPair);
signal_buffer_free(spkPublic);
signal_buffer_free(spkSecret);
SIGNAL_UNREF(ecSpk);
return result;
}

View File

@ -0,0 +1,64 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include "device_service.h"
#include "qomemo.h"
#include "variant/omemo_base.h"
#include <memory>
#include <QXmppClientExtension.h>
namespace Signal {
class Context;
}
namespace QXmpp::Omemo {
class Manager : public QXmppClientExtension {
Q_OBJECT;
public:
Manager();
~Manager() override = default;
bool handleStanza(const QDomElement &stanza) override;
bool handleDeviceList(const QDomElement& stanza);
bool handleMissingDeviceList(const QDomElement& stanza);
bool handleEncryptedMessage(const QDomElement& stanza);
QSharedPointer<DeviceService> getDeviceService();
Bundle generateAndSaveBundle(int deviceId);
public slots:
void fetchOwnDevices();
void publishDeviceList(const QXmpp::Omemo::DeviceList& deviceList);
void generateDeviceListForSelf();
void generateDeviceForSelfIfNeeded(const QXmpp::Omemo::DeviceList &currentList);
void publishBundle(int deviceId, const QXmpp::Omemo::Bundle& bundle);
signals:
void deviceListReceived(const QString &jid, const QXmpp::Omemo::DeviceList &list);
void deviceListNotFound(const QString &jid);
protected:
void setClient(QXmppClient *client) override;
private:
QSharedPointer<DeviceService> deviceService;
QScopedPointer<Variant::Base> omemoVariant;
std::shared_ptr<Signal::Context> signalContext;
};
} // namespace QXmpp::Omemo

26
qomemo/sce.cpp Normal file
View File

@ -0,0 +1,26 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "sce.h"
#include <QRandomGenerator>
#define RPAD_ALPHABET "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
constexpr int RPAD_MAX_LENGTH = 200;
QString QXmpp::Sce::generatePadding() {
QRandomGenerator random{};
QString result{};
QString alphabet{QStringLiteral(RPAD_ALPHABET)};
auto length = random.bounded(RPAD_MAX_LENGTH);
result.resize(length);
for (auto i = 0; i < length; ++i) {
result[i] = alphabet[random.bounded(alphabet.length())];
}
return result;
}

14
qomemo/sce.h Normal file
View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QDateTime>
#include <QXmppElement.h>
namespace QXmpp::Sce {
QString generatePadding();
}

View File

@ -0,0 +1,9 @@
target_sources(squawk PRIVATE
context.cpp
context.h
util.cpp
util.h
)
add_subdirectory(crypto)
add_subdirectory(stores)

28
qomemo/signal/context.cpp Normal file
View File

@ -0,0 +1,28 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "context.h"
#include "crypto/crypto.h"
using namespace Signal;
Context::Context() : cryptoProvider{ Signal::Crypto::createProvider() } {
signal_context_create(&ctx, nullptr);
signal_context_set_crypto_provider(ctx, &cryptoProvider);
}
Context::~Context() {
signal_context_destroy(ctx);
}
std::unique_ptr<Crypto::ECKeyPair> Context::generateCurveKeyPair() {
auto result = std::unique_ptr<Crypto::ECKeyPair>();
// TODO
return result;
}
signal_context *Context::temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped() {
return ctx;
}

32
qomemo/signal/context.h Normal file
View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include "crypto/ec.h"
#include <memory>
#include <signal/signal_protocol.h>
namespace Signal {
class Context {
public:
Context();
~Context();
Context(const Context &) = delete;
Context(Context &&) = delete;
Context &operator=(const Context &) = delete;
std::unique_ptr<Crypto::ECKeyPair> generateCurveKeyPair();
signal_context *temporaryGetContextUnsafeForRawAccessThatNeedsToBeWrapped();
private:
signal_crypto_provider cryptoProvider{};
signal_context *ctx{nullptr};
};
} // namespace Signal

View File

@ -0,0 +1,12 @@
target_sources(squawk PRIVATE
aes_openssl.cpp
aes_openssl.h
crypto.cpp
crypto.h
hmac_sha256_openssl.cpp
hmac_sha256_openssl.h
sha512_digest_openssl.cpp
sha512_digest_openssl.h
ec.cpp
ec.h
)

View File

@ -0,0 +1,187 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "aes_openssl.h"
#include <memory>
extern "C" {
#include <openssl/evp.h>
#include <openssl/opensslv.h>
}
using namespace Signal::Crypto;
class EVPCipherCtxWrapper {
public:
EVPCipherCtxWrapper() {
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
ctx = EVP_CIPHER_CTX_new();
#else
ctx = new EVP_CIPHER_CTX;
EVP_CIPHER_CTX_init(ctx);
#endif
}
~EVPCipherCtxWrapper() {
if (good()) {
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
EVP_CIPHER_CTX_free(ctx);
#else
EVP_CIPHER_CTX_cleanup(ctx);
delete ctx;
#endif
}
}
EVPCipherCtxWrapper(const EVPCipherCtxWrapper &) = delete;
EVPCipherCtxWrapper(EVPCipherCtxWrapper &&) = delete;
EVPCipherCtxWrapper &operator=(const EVPCipherCtxWrapper &) = delete;
[[nodiscard]] bool good() const { return ctx != nullptr; }
[[nodiscard]] EVP_CIPHER_CTX *operator*() const { return ctx; }
private:
EVP_CIPHER_CTX *ctx{nullptr};
};
static const EVP_CIPHER *aes_cipher(int cipher, size_t key_len) {
if (cipher == SG_CIPHER_AES_CBC_PKCS5) {
if (key_len == 16) {
return EVP_aes_128_cbc();
} else if (key_len == 24) {
return EVP_aes_192_cbc();
} else if (key_len == 32) {
return EVP_aes_256_cbc();
}
} else if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
if (key_len == 16) {
return EVP_aes_128_ctr();
} else if (key_len == 24) {
return EVP_aes_192_ctr();
} else if (key_len == 32) {
return EVP_aes_256_ctr();
}
}
return nullptr;
}
int Aes::encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *) {
const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
if (!evp_cipher) {
fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
return SG_ERR_UNKNOWN;
}
if (iv_len != 16) {
fprintf(stderr, "invalid AES IV size: %zu\n", iv_len);
return SG_ERR_UNKNOWN;
}
if (plaintext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
fprintf(stderr, "invalid plaintext length: %zu\n", plaintext_len);
return SG_ERR_UNKNOWN;
}
EVPCipherCtxWrapper ctx{};
if (!ctx.good()) {
fprintf(stderr, "could not create context\n");
return SG_ERR_UNKNOWN;
}
auto result = EVP_EncryptInit_ex(*ctx, evp_cipher, nullptr, key, iv);
if (!result) {
fprintf(stderr, "cannot initialize cipher\n");
return SG_ERR_UNKNOWN;
}
if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
result = EVP_CIPHER_CTX_set_padding(*ctx, 0);
if (!result) {
fprintf(stderr, "cannot set padding\n");
return SG_ERR_UNKNOWN;
}
}
auto out_buf = std::make_unique<uint8_t>(plaintext_len + EVP_CIPHER_block_size(evp_cipher));
int out_len = 0;
result = EVP_EncryptUpdate(*ctx, out_buf.get(), &out_len, plaintext,
plaintext_len);
if (!result) {
fprintf(stderr, "cannot encrypt plaintext\n");
return SG_ERR_UNKNOWN;
}
int final_len = 0;
result = EVP_EncryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len);
if (!result) {
fprintf(stderr, "cannot finish encrypting plaintext\n");
return SG_ERR_UNKNOWN;
}
*output = signal_buffer_create(out_buf.get(), out_len + final_len);
return result;
}
int Aes::decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *) {
const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
if (!evp_cipher) {
fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
return SG_ERR_INVAL;
}
if (iv_len != 16) {
fprintf(stderr, "invalid AES IV size: %zu\n", iv_len);
return SG_ERR_INVAL;
}
if (ciphertext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
fprintf(stderr, "invalid ciphertext length: %zu\n", ciphertext_len);
return SG_ERR_UNKNOWN;
}
EVPCipherCtxWrapper ctx{};
if (!ctx.good()) {
fprintf(stderr, "could not create context\n");
return SG_ERR_UNKNOWN;
}
auto result = EVP_DecryptInit_ex(*ctx, evp_cipher, nullptr, key, iv);
if (!result) {
fprintf(stderr, "cannot initialize cipher\n");
return SG_ERR_UNKNOWN;
}
if (cipher == SG_CIPHER_AES_CTR_NOPADDING) {
result = EVP_CIPHER_CTX_set_padding(*ctx, 0);
if (!result) {
fprintf(stderr, "cannot set padding\n");
return SG_ERR_UNKNOWN;
}
}
auto out_buf = std::make_unique<uint8_t>(ciphertext_len + EVP_CIPHER_block_size(evp_cipher));
int out_len = 0;
result = EVP_DecryptUpdate(*ctx, out_buf.get(), &out_len, ciphertext, ciphertext_len);
if (!result) {
fprintf(stderr, "cannot decrypt ciphertext\n");
return SG_ERR_UNKNOWN;
}
int final_len = 0;
result = EVP_DecryptFinal_ex(*ctx, out_buf.get() + out_len, &final_len);
if (!result) {
fprintf(stderr, "cannot finish decrypting ciphertext\n");
return SG_ERR_UNKNOWN;
}
*output = signal_buffer_create(out_buf.get(), out_len + final_len);
return result;
}

View File

@ -0,0 +1,17 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto::Aes {
int encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data);
int decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv,
size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data);
} // namespace Signal::Crypto::Aes

View File

@ -0,0 +1,40 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "crypto.h"
extern "C" {
#include <openssl/rand.h>
}
#include "aes_openssl.h"
#include "hmac_sha256_openssl.h"
#include "sha512_digest_openssl.h"
int random_func(uint8_t *data, size_t len, void *) {
if (RAND_bytes(data, len)) {
return 0;
} else {
return SG_ERR_UNKNOWN;
}
}
signal_crypto_provider Signal::Crypto::createProvider() {
signal_crypto_provider result{};
result.random_func = random_func;
result.hmac_sha256_init_func = HmacSha256::init;
result.hmac_sha256_update_func = HmacSha256::update;
result.hmac_sha256_final_func = HmacSha256::final;
result.hmac_sha256_cleanup_func = HmacSha256::cleanup;
result.sha512_digest_init_func = Sha512::init;
result.sha512_digest_update_func = Sha512::update;
result.sha512_digest_final_func = Sha512::final;
result.sha512_digest_cleanup_func = Sha512::cleanup;
result.encrypt_func = Aes::encrypt;
result.decrypt_func = Aes::decrypt;
result.user_data = nullptr;
return result;
}

View File

@ -0,0 +1,13 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto {
signal_crypto_provider createProvider();
}

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-06-17.
*/
#include "ec.h"

20
qomemo/signal/crypto/ec.h Normal file
View File

@ -0,0 +1,20 @@
/*
* Created by victoria on 2021-06-17.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto {
class ECKeyPair {
public:
ECKeyPair();
~ECKeyPair();
private:
ec_key_pair *ec;
};
}

View File

@ -0,0 +1,71 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "hmac_sha256_openssl.h"
extern "C" {
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
}
using namespace Signal::Crypto;
int HmacSha256::init(void **hmac_context, const uint8_t *key, size_t key_len, void *) {
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
HMAC_CTX *ctx = HMAC_CTX_new();
if (!ctx) {
return SG_ERR_NOMEM;
}
#else
auto ctx = new HMAC_CTX;
HMAC_CTX_init(ctx);
#endif
*hmac_context = ctx;
if (HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), nullptr) != 1) {
return SG_ERR_UNKNOWN;
}
return SG_SUCCESS;
}
int HmacSha256::update(void *hmac_context, const uint8_t *data, size_t data_len, void *) {
auto ctx = static_cast<HMAC_CTX *>(hmac_context);
int result = HMAC_Update(ctx, data, data_len);
return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN;
}
int HmacSha256::final(void *hmac_context, signal_buffer **output, void *) {
auto ctx = static_cast<HMAC_CTX *>(hmac_context);
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int len = 0;
if (HMAC_Final(ctx, md, &len) != 1) {
return SG_ERR_UNKNOWN;
}
signal_buffer *output_buffer = signal_buffer_create(md, len);
if (!output_buffer) {
return SG_ERR_NOMEM;
}
*output = output_buffer;
return SG_SUCCESS;
}
void HmacSha256::cleanup(void *hmac_context, void *) {
if (hmac_context) {
auto ctx = static_cast<HMAC_CTX *>(hmac_context);
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
HMAC_CTX_free(ctx);
#else
HMAC_CTX_cleanup(ctx);
delete ctx;
#endif
}
}

View File

@ -0,0 +1,16 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto::HmacSha256 {
int init(void **hmac_context, const uint8_t *key, size_t key_len, void *);
int update(void *hmac_context, const uint8_t *data, size_t data_len, void *);
int final(void *hmac_context, signal_buffer **output, void *);
void cleanup(void *hmac_context, void *);
} // namespace Signal::Crypto::HmacSha256

View File

@ -0,0 +1,67 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "sha512_digest_openssl.h"
extern "C" {
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
}
using namespace Signal::Crypto;
int Sha512::init(void **digest_context, void *) {
auto ctx = EVP_MD_CTX_create();
if (!ctx) {
return SG_ERR_NOMEM;
}
auto result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr);
if (result == 1) {
*digest_context = ctx;
return SG_SUCCESS;
}
EVP_MD_CTX_destroy(ctx);
return SG_ERR_UNKNOWN;
}
int Sha512::update(void *digest_context, const uint8_t *data, size_t data_len, void *) {
auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
auto result = EVP_DigestUpdate(ctx, data, data_len);
return (result == 1) ? SG_SUCCESS : SG_ERR_UNKNOWN;
}
int Sha512::final(void *digest_context, signal_buffer **output, void *) {
auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int len = 0;
auto result = EVP_DigestFinal_ex(ctx, md, &len);
if (result != 1) {
return SG_ERR_UNKNOWN;
}
result = EVP_DigestInit_ex(ctx, EVP_sha512(), nullptr);
if (result != 1) {
return SG_ERR_UNKNOWN;
}
signal_buffer *output_buffer = signal_buffer_create(md, len);
if (!output_buffer) {
return SG_ERR_NOMEM;
}
*output = output_buffer;
return SG_SUCCESS;
}
void Sha512::cleanup(void *digest_context, void *) {
auto ctx = static_cast<EVP_MD_CTX *>(digest_context);
EVP_MD_CTX_destroy(ctx);
}

View File

@ -0,0 +1,16 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Crypto::Sha512 {
int init(void **digest_context, void *);
int update(void *digest_context, const uint8_t *data, size_t data_len, void *);
int final(void *digest_context, signal_buffer **output, void *);
void cleanup(void *digest_context, void *);
} // namespace Signal::Crypto::Sha512

View File

@ -0,0 +1,14 @@
target_sources(squawk PRIVATE
identity_key_store.cpp
identity_key_store.h
pre_key_store.cpp
pre_key_store.h
sender_key_store.cpp
sender_key_store.h
session_store.cpp
session_store.h
signed_pre_key_store.cpp
signed_pre_key_store.h
store_context.cpp
store_context.h
)

View File

@ -0,0 +1,70 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "identity_key_store.h"
#include "qomemo/signal/util.h"
#include <utility>
Signal::Store::IdentityKeyStore::IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId)
: jid(std::move(jid)), deviceId(deviceId), deviceService(deviceService) {
database = deviceService.getDatabase(jid);
}
int Signal::Store::IdentityKeyStore::getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data) {
auto pk = database->loadIdentityKey(deviceId);
auto sk = database->loadIdentityKeySecret(deviceId);
*public_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(pk->data()), pk->size());
*private_data = signal_buffer_create(reinterpret_cast<const uint8_t *>(sk->data()), sk->size());
return 0;
}
int Signal::Store::IdentityKeyStore::getLocalRegistrationId(uint32_t *registration_id) {
// TODO: Figure out what registration id is used for
*registration_id = 1;
return 0;
}
int Signal::Store::IdentityKeyStore::saveIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto identityKey = Signal::Util::byteArray(key_data, key_len);
deviceService.getDatabase(identityJid)->saveIdentityKey(address->device_id, identityKey);
return 0;
}
int Signal::Store::IdentityKeyStore::isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data,
size_t key_len) {
auto identityJid = Signal::Util::jidFromAddress(address);
auto actualIdentityKey = deviceService.getDatabase(identityJid)->loadIdentityKey(address->device_id);
if (!actualIdentityKey.has_value()) {
return 1;
}
auto givenIdentityKey = Signal::Util::byteArray(key_data, key_len);
return givenIdentityKey == actualIdentityKey ? 1 : 0;
}
void Signal::Store::IdentityKeyStore::fillCallbacks(signal_protocol_identity_key_store &store) {
store.get_identity_key_pair = [](signal_buffer **public_data, signal_buffer **private_data, void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->getIdentityKeyPair(public_data, private_data);
};
store.get_local_registration_id = [](void *ptr, uint32_t *registrationId) {
return static_cast<IdentityKeyStore *>(ptr)->getLocalRegistrationId(registrationId);
};
store.is_trusted_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len,
void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->isTrustedIdentity(address, key_data, key_len);
};
store.save_identity = [](const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *ptr) {
return static_cast<IdentityKeyStore *>(ptr)->saveIdentity(address, key_data, key_len);
};
}

View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#include "qomemo/device_service.h"
namespace Signal::Store {
class IdentityKeyStore {
public:
IdentityKeyStore(QXmpp::Omemo::DeviceService &deviceService, QString jid, int deviceId);
int getIdentityKeyPair(signal_buffer **public_data, signal_buffer **private_data);
int getLocalRegistrationId(uint32_t *registration_id);
int saveIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
int isTrustedIdentity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len);
void fillCallbacks(signal_protocol_identity_key_store &store);
const QString jid;
const int deviceId;
private:
QXmpp::Omemo::DeviceService &deviceService;
QSharedPointer<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

View File

@ -0,0 +1,38 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "pre_key_store.h"
Signal::Store::PreKeyStore::PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database) : database(
std::move(database)) {}
int Signal::Store::PreKeyStore::containsPreKey(uint32_t pre_key_id) {
return 0;
}
int Signal::Store::PreKeyStore::loadPreKey(signal_buffer **record, uint32_t pre_key_id) {
return 0;
}
int Signal::Store::PreKeyStore::storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len) {
return 0;
}
int Signal::Store::PreKeyStore::removePreKey(uint32_t pre_key_id) { return 0; }
void Signal::Store::PreKeyStore::fillCallbacks(signal_protocol_pre_key_store &store) {
store.contains_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->containsPreKey(id);
};
store.load_pre_key = [](signal_buffer **record, uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->loadPreKey(record, id);
};
store.remove_pre_key = [](uint32_t id, void *ptr) {
return static_cast<PreKeyStore *>(ptr)->removePreKey(id);
};
store.store_pre_key = [](uint32_t id, uint8_t *record, size_t size,
void *ptr) {
return static_cast<PreKeyStore *>(ptr)->storePreKey(id, record, size);
};
}

View File

@ -0,0 +1,28 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#include "qomemo/device_service.h"
namespace Signal::Store {
class PreKeyStore {
public:
explicit PreKeyStore(QSharedPointer<QXmpp::Omemo::Database> database);
int containsPreKey(uint32_t pre_key_id);
int loadPreKey(signal_buffer **record, uint32_t pre_key_id);
int storePreKey(uint32_t pre_key_id, uint8_t *record, size_t record_len);
int removePreKey(uint32_t pre_key_id);
void fillCallbacks(signal_protocol_pre_key_store &store);
private:
QSharedPointer<QXmpp::Omemo::Database> database;
};
} // namespace Signal::Store

View File

@ -0,0 +1,30 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "sender_key_store.h"
int Signal::Store::SenderKeyStore::loadSenderKey(signal_buffer **record, signal_buffer **user_record,
const signal_protocol_sender_key_name *sender_key_name) {
return 0;
}
int Signal::Store::SenderKeyStore::storeSenderKey(const signal_protocol_sender_key_name *sender_key_name,
uint8_t *record, size_t record_len, uint8_t *user_record,
size_t user_record_len) {
return 0;
}
void Signal::Store::SenderKeyStore::fillCallbacks(signal_protocol_sender_key_store &store) {
store.user_data = nullptr;
store.destroy_func = nullptr;
store.load_sender_key = [](signal_buffer **record, signal_buffer **user_record,
const signal_protocol_sender_key_name *sender_key_name, void *ptr) {
return static_cast<SenderKeyStore *>(ptr)->loadSenderKey(record, user_record, sender_key_name);
};
store.store_sender_key = [](const signal_protocol_sender_key_name *sender_key_name, uint8_t *record,
size_t record_len, uint8_t *user_record, size_t user_record_len, void *ptr) {
return static_cast<SenderKeyStore *>(ptr)->storeSenderKey(sender_key_name, record, record_len, user_record,
user_record_len);
};
}

View File

@ -0,0 +1,21 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Store {
class SenderKeyStore {
public:
int storeSenderKey(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len,
uint8_t *user_record, size_t user_record_len);
int loadSenderKey(signal_buffer **record, signal_buffer **user_record,
const signal_protocol_sender_key_name *sender_key_name);
void fillCallbacks(signal_protocol_sender_key_store &store);
};
} // namespace Signal::Store

View File

@ -0,0 +1,57 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "session_store.h"
int Signal::Store::SessionStore::loadSession(signal_buffer **record, signal_buffer **user_record,
const signal_protocol_address *address) {
return 0;
}
int Signal::Store::SessionStore::getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len) {
return 0;
}
int Signal::Store::SessionStore::storeSession(const signal_protocol_address *address, uint8_t *record,
size_t record_len, uint8_t *user_record, size_t user_record_len) {
return 0;
}
int Signal::Store::SessionStore::containsSession(const signal_protocol_address *address) {
return 0;
}
int Signal::Store::SessionStore::deleteSession(const signal_protocol_address *address) {
return 0;
}
int Signal::Store::SessionStore::deleteAllSessions(const char *name, size_t name_len) {
return 0;
}
void Signal::Store::SessionStore::fillCallbacks(signal_protocol_session_store &store) {
store.user_data = nullptr;
store.destroy_func = nullptr;
store.load_session_func = [](signal_buffer **record, signal_buffer **user_record,
const signal_protocol_address *address, void *ptr) {
return static_cast<SessionStore *>(ptr)->loadSession(record, user_record, address);
};
store.get_sub_device_sessions_func = [](signal_int_list **sessions, const char *name, size_t name_len, void *ptr) {
return static_cast<SessionStore *>(ptr)->getSubDeviceSessions(sessions, name, name_len);
};
store.store_session_func = [](const signal_protocol_address *address, uint8_t *record, size_t record_len,
uint8_t *user_record, size_t user_record_len, void *ptr) {
return static_cast<SessionStore *>(ptr)->storeSession(address, record, record_len, user_record,
user_record_len);
};
store.contains_session_func = [](const signal_protocol_address *address, void *ptr) {
return static_cast<SessionStore *>(ptr)->containsSession(address);
};
store.delete_session_func = [](const signal_protocol_address *address, void *ptr) {
return static_cast<SessionStore *>(ptr)->deleteSession(address);
};
store.delete_all_sessions_func = [](const char *name, size_t name_len, void *ptr) {
return static_cast<SessionStore *>(ptr)->deleteAllSessions(name, name_len);
};
}

View File

@ -0,0 +1,24 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Store {
class SessionStore {
public:
int loadSession(signal_buffer **record, signal_buffer **user_record, const signal_protocol_address *address);
int getSubDeviceSessions(signal_int_list **sessions, const char *name, size_t name_len);
int storeSession(const signal_protocol_address *address, uint8_t *record, size_t record_len,
uint8_t *user_record, size_t user_record_len);
int containsSession(const signal_protocol_address *address);
int deleteSession(const signal_protocol_address *address);
int deleteAllSessions(const char *name, size_t name_len);
void fillCallbacks(signal_protocol_session_store &store);
};
} // namespace Signal::Store

View File

@ -0,0 +1,43 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "signed_pre_key_store.h"
int Signal::Store::SignedPreKeyStore::loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id) {
return 0;
}
int
Signal::Store::SignedPreKeyStore::storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len) {
return 0;
}
int Signal::Store::SignedPreKeyStore::containsSignedPreKey(uint32_t signed_pre_key_id) {
return 0;
}
int Signal::Store::SignedPreKeyStore::removeSignedPreKey(uint32_t signed_pre_key_id) {
return 0;
}
void Signal::Store::SignedPreKeyStore::fillCallbacks(signal_protocol_signed_pre_key_store &store) {
store.user_data = nullptr;
store.destroy_func = nullptr;
store.load_signed_pre_key = [](signal_buffer **record, uint32_t signed_pre_key_id, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->loadSignedPreKey(
record, signed_pre_key_id);
};
store.store_signed_pre_key = [](uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->storeSignedPreKey(
signed_pre_key_id, record, record_len);
};
store.contains_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->containsSignedPreKey(
signed_pre_key_id);
};
store.remove_signed_pre_key = [](uint32_t signed_pre_key_id, void *ptr) {
return static_cast<SignedPreKeyStore *>(ptr)->removeSignedPreKey(
signed_pre_key_id);
};
}

View File

@ -0,0 +1,21 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
namespace Signal::Store {
class SignedPreKeyStore {
public:
int loadSignedPreKey(signal_buffer **record, uint32_t signed_pre_key_id);
int storeSignedPreKey(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len);
int containsSignedPreKey(uint32_t signed_pre_key_id);
int removeSignedPreKey(uint32_t signed_pre_key_id);
void fillCallbacks(signal_protocol_signed_pre_key_store &store);
};
} // namespace Signal::Store

View File

@ -0,0 +1,25 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "store_context.h"
Signal::Store::Context::Context(signal_context *global) : identityKeyStore(), preKeyStore(), senderKeyStore(), sessionStore(), signedPreKeyStore() {
signal_protocol_store_context_create(&ctx, global);
identityKeyStore->fillCallbacks(iks);
preKeyStore->fillCallbacks(pks);
senderKeyStore->fillCallbacks(sks);
sessionStore->fillCallbacks(ss);
signedPreKeyStore->fillCallbacks(spks);
signal_protocol_store_context_set_identity_key_store(ctx, &iks);
signal_protocol_store_context_set_pre_key_store(ctx, &pks);
signal_protocol_store_context_set_sender_key_store(ctx, &sks);
signal_protocol_store_context_set_session_store(ctx, &ss);
signal_protocol_store_context_set_signed_pre_key_store(ctx, &spks);
}
Signal::Store::Context::~Context() {
signal_protocol_store_context_destroy(ctx);
}

View File

@ -0,0 +1,42 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <signal/signal_protocol.h>
#include "identity_key_store.h"
#include "pre_key_store.h"
#include "sender_key_store.h"
#include "session_store.h"
#include "signed_pre_key_store.h"
namespace Signal::Store {
class Context {
public:
explicit Context(signal_context *global);
~Context();
Context(const Context &) = delete;
Context(Context &&) = delete;
Context &operator=(const Context &) = delete;
private:
signal_protocol_store_context *ctx{nullptr};
signal_protocol_identity_key_store iks{};
signal_protocol_pre_key_store pks{};
signal_protocol_sender_key_store sks{};
signal_protocol_session_store ss{};
signal_protocol_signed_pre_key_store spks{};
std::unique_ptr<IdentityKeyStore> identityKeyStore;
std::unique_ptr<PreKeyStore> preKeyStore;
std::unique_ptr<SenderKeyStore> senderKeyStore;
std::unique_ptr<SessionStore> sessionStore;
std::unique_ptr<SignedPreKeyStore> signedPreKeyStore;
};
} // namespace Signal::Store

14
qomemo/signal/util.cpp Normal file
View File

@ -0,0 +1,14 @@
/*
* Created by victoria on 2021-05-15.
*/
#include "util.h"
QString Signal::Util::jidFromAddress(const signal_protocol_address *address) {
// TODO: Validate this
return QString::fromRawData(reinterpret_cast<const QChar *>(address->name), static_cast<int>(address->name_len));
}
QByteArray Signal::Util::byteArray(const uint8_t *data, size_t len) {
return QByteArray::fromRawData(reinterpret_cast<const char *>(data), static_cast<int>(len));
}

18
qomemo/signal/util.h Normal file
View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-15.
*/
#pragma once
#include <signal/signal_protocol.h>
#include <QString>
#include <QByteArray>
namespace Signal::Util {
QString jidFromAddress(const signal_protocol_address *address);
QByteArray byteArray(const uint8_t *data, size_t len);
}

View File

@ -0,0 +1,9 @@
/*
* Created by victoria on 2021-05-12.
*/
#include "user_device_list.h"
#include <utility>
QXmpp::Omemo::UserDeviceList::UserDeviceList(QString jid) : jid(std::move(jid)) {}

18
qomemo/user_device_list.h Normal file
View File

@ -0,0 +1,18 @@
/*
* Created by victoria on 2021-05-12.
*/
#pragma once
#include <QString>
namespace QXmpp::Omemo {
class UserDeviceList {
public:
explicit UserDeviceList(QString jid);
const QString jid;
};
} // namespace QXmpp::Omemo

View File

@ -0,0 +1 @@
target_sources(squawk PRIVATE xep0384.cpp xep0384.h conversations.cpp conversations.h omemo_base.cpp omemo_base.h)

View File

@ -0,0 +1,205 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "conversations.h"
#include "qomemo/bundle.h"
#include "qomemo/device.h"
#include "shared/qxmppfactories.h"
#include <QDebug>
#include <QXmppIq.h>
#include <QXmppPubSubIq.h>
using namespace QXmpp::Omemo;
using namespace QXmpp::Factories;
QXmppElement Variant::Conversations::deviceToXml(const Device &device) {
auto result = createElement("device");
result.setAttribute("id", QString::number(device.id));
return result;
}
Device Variant::Conversations::deviceFromXml(const QXmppElement &xml) {
Device result{};
if (!elementMatches(xml, "device"))
return result;
result.id = xml.attribute("id").toInt();
return result;
}
QXmppElement
Variant::Conversations::deviceListToXml(const DeviceList &deviceList) {
auto element = createElement("list", "eu.siacs.conversations.axolotl");
for (const auto &device : deviceList.devices) {
element.appendChild(deviceToXml(device));
}
return element;
}
std::optional<DeviceList> Variant::Conversations::latestDeviceListFromPubSubNode(const QXmppElement &items) {
auto item = items.firstChildElement("item");
if (item.isNull())
return std::nullopt;
auto list = item.firstChildElement("list");
if (list.isNull())
return std::nullopt;
if (!elementMatches(list, "list", "eu.siacs.conversations.axolotl"))
return std::nullopt;
DeviceList result{};
auto deviceElement = list.firstChildElement("device");
while (!deviceElement.isNull()) {
result.devices.push_back(deviceFromXml(deviceElement));
deviceElement = deviceElement.nextSiblingElement("device");
}
return result;
}
QXmppIq Variant::Conversations::deviceListSetIq(const DeviceList &deviceList) {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
auto item = createElement("item");
item.appendChild(deviceListToXml(deviceList));
auto publish = createElement("publish");
publish.setAttribute("node", getDeviceListNode());
publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
pubSub.appendChild(publish);
pubSub.appendChild(createOpenPublishOptions());
iq.setExtensions({ pubSub });
return iq;
}
QString Variant::Conversations::getDeviceListNode() const {
return "eu.siacs.conversations.axolotl.devicelist";
}
QXmppIq Variant::Conversations::bundleSetIq(int deviceId, const Bundle &bundle) {
QXmppIq iq{};
iq.setType(QXmppIq::Set);
auto item = createElement("item");
item.appendChild(bundleToXml(bundle));
auto publish = createElement("publish");
publish.setAttribute(
"node",
QString("eu.siacs.conversations.axolotl.bundles:%1").arg(deviceId));
publish.appendChild(item);
auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub");
pubSub.appendChild(publish);
pubSub.appendChild(createOpenPublishOptions());
iq.setExtensions({ pubSub });
return iq;
}
QXmppElement Variant::Conversations::bundleToXml(const Bundle &bundle) {
auto spkNode = createElement("signedPreKeyPublic");
spkNode.setAttribute("signedPreKeyId", QString::number(bundle.spkId));
spkNode.setValue(bundle.spk.toBase64());
auto spksNode = createElement("signedPreKeySignature");
spksNode.setValue(bundle.spks.toBase64());
auto ikNode = createElement("identityKey");
ikNode.setValue(bundle.ik.toBase64());
auto prekeysNode = createElement("prekeys");
for (const auto &pk : bundle.prekeys) {
prekeysNode.appendChild(preKeyToXml(pk));
}
auto result = createElement("bundle", "eu.siacs.conversations.axolotl");
result.appendChild(spkNode);
result.appendChild(spksNode);
result.appendChild(ikNode);
result.appendChild(prekeysNode);
return result;
}
QXmppElement Variant::Conversations::preKeyToXml(const PreKey &pk) {
auto x = createElement("preKeyPublic");
x.setAttribute("preKeyId", QString::number(pk.id));
x.setValue(pk.data.toBase64());
return x;
}
std::optional<PreKey> Variant::Conversations::preKeyFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "preKeyPublic"))
return std::nullopt;
auto pk = PreKey();
pk.id = xml.attribute("preKeyId").toInt();
pk.data = QByteArray::fromBase64Encoding(xml.value().toUtf8()).decoded;
return pk;
}
std::optional<Bundle> Variant::Conversations::bundleFromXml(const QXmppElement &xml) {
if (!elementMatches(xml, "bundle", "eu.siacs.conversations.axolotl"))
return std::nullopt;
auto spkNode = xml.firstChildElement("signedPreKeyPublic");
if (spkNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeyPublic'";
return std::nullopt;
}
Bundle result{};
result.spk = QByteArray::fromBase64Encoding(spkNode.value().toUtf8()).decoded;
result.spkId = spkNode.attribute("signedPreKeyId").toInt();
auto spksNode = xml.firstChildElement("signedPreKeySignature");
if (spksNode.isNull()) {
qWarning() << "'bundle': missing 'signedPreKeySignature'";
return std::nullopt;
}
result.spks = QByteArray::fromBase64Encoding(spksNode.value().toUtf8()).decoded;
auto ikNode = xml.firstChildElement("identityKey");
if (ikNode.isNull()) {
qWarning() << "'bundle': missing 'identityKey'";
return std::nullopt;
}
result.ik = QByteArray::fromBase64Encoding(ikNode.value().toUtf8()).decoded;
auto prekeysNode = xml.firstChildElement("prekeys");
auto pkNode = prekeysNode.firstChildElement("preKeyPublic");
result.prekeys.clear();
while (!pkNode.isNull()) {
auto maybePk = preKeyFromXml(pkNode);
if (maybePk)
result.prekeys.push_back(*maybePk);
pkNode = pkNode.nextSiblingElement("preKeyPublic");
}
return result;
}

View File

@ -0,0 +1,32 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include "omemo_base.h"
namespace QXmpp::Omemo::Variant {
class Conversations : public Base {
public:
~Conversations() override = default;
QXmppElement deviceToXml(const Device &device) override;
Device deviceFromXml(const QXmppElement &xml) override;
[[nodiscard]] QString getDeviceListNode() const override;
QXmppElement deviceListToXml(const DeviceList &deviceList) override;
std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &items) override;
QXmppIq deviceListSetIq(const DeviceList &deviceList) override;
QXmppElement preKeyToXml(const PreKey &pk) override;
std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) override;
QXmppElement bundleToXml(const Bundle &bundle) override;
std::optional<Bundle> bundleFromXml(const QXmppElement &xml) override;
QXmppIq bundleSetIq(int deviceId, const Bundle &bundle) override;
};
} // namespace QXmpp::Omemo::Variant

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "omemo_base.h"

View File

@ -0,0 +1,45 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
#include <optional>
class QString;
class QXmppElement;
class QXmppIq;
namespace QXmpp::Omemo {
class PreKey;
class Bundle;
class Device;
class DeviceList;
namespace Variant {
class Base {
public:
virtual ~Base() = default;
virtual QXmppElement deviceToXml(const Device &device) = 0;
virtual Device deviceFromXml(const QXmppElement &xml) = 0;
[[nodiscard]] virtual QString getDeviceListNode() const = 0;
virtual QXmppElement deviceListToXml(const DeviceList &deviceList) = 0;
virtual std::optional<DeviceList> latestDeviceListFromPubSubNode(const QXmppElement &xml) = 0;
virtual QXmppIq deviceListSetIq(const DeviceList &deviceList) = 0;
virtual QXmppElement preKeyToXml(const PreKey &pk) = 0;
virtual std::optional<PreKey> preKeyFromXml(const QXmppElement &xml) = 0;
virtual QXmppElement bundleToXml(const Bundle& bundle) = 0;
virtual std::optional<Bundle> bundleFromXml(const QXmppElement& xml) = 0;
virtual QXmppIq bundleSetIq(int deviceId, const Bundle& bundle) = 0;
};
} // namespace Variant
} // namespace QXmpp::Omemo

View File

@ -0,0 +1,5 @@
/*
* Created by victoria on 2021-05-13.
*/
#include "xep0384.h"

7
qomemo/variant/xep0384.h Normal file
View File

@ -0,0 +1,7 @@
/*
* Created by victoria on 2021-05-13.
*/
#pragma once
namespace QXmpp::Omemo {}

14
resources/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
target_sources(squawk PRIVATE resources.qrc)
configure_file(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})
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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 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

Some files were not shown because too many files have changed in this diff Show More