Compare commits

..

214 Commits

Author SHA1 Message Date
321f0b03c8
Fix build for qt 5, removed some debug messages 2024-12-14 18:08:50 +02:00
d4cec645b5
qt6 build 2024-12-14 01:53:04 +02:00
a04693e39d
fix: build without KConfig is possible once more 2024-11-19 18:45:11 +02:00
3cce057545
fix: omitting from attribute not to wreck the stream
fix: build without kwallet
2024-11-18 22:43:46 +02:00
9a44ae1fa5
SimpleCrypt password jamming is now optional 2024-11-17 20:25:33 +02:00
85ff6c25ba
find boos cmake new policy
magick instead of convert rendering images
2024-10-27 20:02:34 +02:00
3cc7db8eff
A workaround to store plugins in a subdirectory 2024-10-27 19:33:03 +02:00
Benson Muite
ff9a591d6d
Private libraries directory 2024-10-27 19:33:03 +02:00
8e3f10caff Merge pull request 'Add appdata file' (#94) from bmckwm/squawk:appdata into master
Reviewed-on: blue/squawk#94
Reviewed-by: Blue <blue@macaw.me>
2024-10-13 07:37:05 +00:00
8bfe88929f Merge pull request 'Fix license text error' (#90) from bmckwm/squawk:license into master
Reviewed-on: blue/squawk#90
Reviewed-by: Blue <blue@macaw.me>
2024-10-13 07:35:29 +00:00
9927bdc38b Merge pull request 'Update image link' (#87) from bmckwm/squawk:image-link-update into master
Reviewed-on: blue/squawk#87
Reviewed-by: Blue <blue@macaw.me>
2024-10-13 07:34:34 +00:00
Benson Muite
8083859541 Fix license text error 2024-10-07 13:45:15 +03:00
Benson Muite
030c374139 Update image link 2024-10-06 19:29:38 +03:00
Benson Muite
2c61b82924 Add appdata file 2024-10-06 19:26:44 +03:00
fb843a1346
ci 2024-02-04 13:51:16 -03:00
8d82d340a4
0.2.3 preparation, typo fix, readme changes 2024-02-04 13:36:51 -03:00
acd60eaba2
Fixing build without omemo, release preparation, unnecessary inheritance removed, info widget fix 2024-02-04 09:44:19 -03:00
829777935f
fixes for bundled LMDBAL build 2024-02-01 13:10:52 -03:00
0be2648849
Now avatars are properly autogenerated, reduced vCard spam 2024-01-31 20:22:49 -03:00
93c5be412e
trying linter settings 2023-11-17 21:52:33 -03:00
8f5325b291
beginning of keys setting 2023-11-16 21:08:40 -03:00
75554c7451
refactorng 2023-11-14 20:23:39 -03:00
00af582287
Own omemo key display, a bit of CMake clean up 2023-11-13 19:05:26 -03:00
19835af3cf
some debug, fix a crash removing a currently selected contact 2023-11-12 19:55:32 -03:00
e31ef78e71
some refactoring, some improvements 2023-11-10 19:26:16 -03:00
be466fbad1
removed Order, resolved a crash on several files being uploaded simultaniuosly 2023-11-09 19:36:30 -03:00
0a530bfa93
encrypted messages now are displayed in the feed 2023-11-06 20:57:08 -03:00
637eb702a8
cant believe it, first ever encrypted messages! 2023-11-05 16:29:44 -03:00
a7d1a28f29
some work towards encryption 2023-11-04 22:12:15 -03:00
297e08ba41
somewhat working... 2023-11-03 20:13:45 -03:00
9d688e8596
full transition to lmdbal, DOESNT WORK, DONT TAKE! 2023-11-02 19:55:11 -03:00
23ec80ccba
cleanup some warnings suppression 2023-08-15 12:28:25 -03:00
5fbb03fc46
transitioned urlstorage to LMDBAL, made it possible to build against latest qxmpp 2023-04-15 15:07:27 -03:00
81cf0f8d34
transition to LMDBAL 2023-03-27 21:45:29 +03:00
69d797fe51
showing the button for encryption if there is at least one omemo key, trust summary update calculations 2023-03-18 02:50:04 +03:00
4f295fee3c
trust summary gui delivery 2023-03-17 23:59:51 +03:00
fffef9876a
Refactoring, account destruction fix, some thoughts about where to store contact settings (omemo enable status for instance) 2023-03-16 22:38:05 +03:00
283e9ebc4d
some thoughts on detecting condition for enablining or showing the button for encryption in chat window 2023-03-15 21:17:44 +03:00
21b40a9ccb
Client node now displays in all participants and presences, some additional checkups before querying empty clients, refactoring 2023-03-14 22:49:58 +03:00
76a9c5da0c
extracted clientId from clientInfo to use it in the presence information later 2023-03-13 22:07:10 +03:00
8ec0af3205
transition to QXMppCarbonManagerV2 if QXmpp version is heigher than 1.5.0 2023-03-12 01:38:54 +03:00
4b68da458f
debugged a crash, keys are now fetching, refactored main, added some exceptions instead of ints, debugged termination process 2023-03-11 19:46:23 +03:00
927bdf0dab
DONT TAKE, BROKEN! first application of delay manager in code, reception of bundles 2023-03-10 21:43:31 +03:00
5ba97ecc25
some hopefully final preparations for delay manager 2023-03-08 23:28:48 +03:00
9fff409630
some more thinking about delay manager 2023-03-07 21:45:01 +03:00
99fd001292
some more thinking about delay manager 2023-03-05 01:36:53 +03:00
2d8f32c257
some ideas over delay manager 2023-03-04 00:27:12 +03:00
77dd28b600
some further work on omemo, far from done yet 2023-03-02 21:17:06 +03:00
6f32e99593
an idea how to manage info object better 2023-03-01 22:32:41 +03:00
ec362cef55
some further thinking of info widget 2023-02-21 23:27:28 +03:00
e4a2728ef8
hopefully end of refactoring of vcard to Info widget 2023-02-20 21:12:32 +03:00
bf11d8a74e
keeping with the refactoring 2023-02-03 21:43:13 +03:00
edf1ee60cd
keep going on refactoring vcard 2023-02-02 21:39:38 +03:00
4af16b75bf
started refactoring of the VCard UI 2023-02-01 18:56:00 +03:00
bb304ce774
just some unfinished thoughts 2023-01-30 20:52:26 +03:00
3c6b611a41
keeping up with qxmpp 2023-01-29 20:26:54 +03:00
73d83f55af
context menu to trust or distrust keys 2023-01-15 21:17:38 +03:00
b72a837754
trust level display in delegate, list size tweaking 2023-01-14 18:34:14 +03:00
d4bf7e599a
better way to solve yesterday font problem, small visual avatar rendering fix 2023-01-12 20:56:01 +03:00
15fb4bbd62
some thoughts about fonts, lastInteraction and label into keyDelegate 2023-01-11 23:45:38 +03:00
2aed8a1209
a bit better drawing of a key fingerprint 2023-01-08 18:16:41 +03:00
78ef3664f7
some initial delegate stuff 2023-01-07 17:30:22 +03:00
5aa0f4bca9
some initial classes for keys form 2023-01-03 18:27:03 +03:00
b45a73b723
some initial work and thoughts about encryption 2023-01-01 20:25:51 +03:00
758a9d95f3
replaced one structure, stored omemo support in Global object 2022-12-29 01:41:59 +03:00
dfe72ca36c
support of the new managers in account code, new states, new lambdas, even launches now, even receives some bundles 2022-12-27 01:01:01 +03:00
db3bc358a7
work progress: trust manager. DOESN'T START! 2022-12-19 18:43:24 +03:00
0b61b6e928
Some work on omemo handler, NOT DONE, BUILD FAILS! 2022-12-15 02:08:08 +03:00
820dc845ea
BUILD FAILS! some ideas of storage and cache 2022-09-03 14:39:42 +03:00
87973b3b67
first attempts to build against upstream qxmpp 2022-08-29 21:34:25 +03:00
b6ba022bff
removed own VCard request at the start if the presence doesn't show that the avatar changed, little refactoring 2022-08-27 14:39:24 +03:00
7b2b7ee5d5
first thought about forms, discovering contact pep support 2022-08-26 01:49:49 +03:00
c50cd1140e
first ever received and cached client data! 2022-08-25 01:41:06 +03:00
037dabbe06
some new shared classes, little reorganization, preparation to cache client info 2022-08-22 23:29:43 +03:00
2ae75a4b91
New object for cached database, also ClientInfo class 2022-08-20 00:28:59 +03:00
d162494ec8
Better way to store expanded elements in roster, several clean ups, translations 2022-08-17 19:25:35 +03:00
7e9eed2075
First tray attempt, seems to be working 2022-08-15 19:40:07 +03:00
7192286aeb
fix some bugs about disabled menus 2022-06-03 09:44:48 +03:00
645b92ba51
release 0.2.2 preparation 2022-05-05 20:46:49 +03:00
80c5e2f2b4
added en lolcalization file, actualized localizations 2022-05-04 19:20:30 +03:00
1f065f23e6
double click word selection handle, sigint sermentation fault fix 2022-05-03 12:17:08 +03:00
3c48577eee
selection message body now actually working 2022-05-02 22:25:50 +03:00
0340db7f2f
first successfull attempt to visualize selection on message body 2022-05-01 23:19:52 +03:00
c3a45ec58c
merge conflicts, text copying from context menu in message line 2022-04-30 21:41:25 +03:00
7ba94e9deb
link clicking and hovering in message body now works! 2022-04-29 00:29:44 +03:00
eac87e713f
seem to have found a text block, to activate with the click later 2022-04-28 00:08:59 +03:00
d86e2c28a0
an attempt to display text in a better way with QTextDocument + QTextBrowser 2022-04-27 01:17:53 +03:00
2fcc432aef
some polish 2022-04-26 23:08:25 +03:00
e58213b294
Now notifications have actions! Some more usefull functions to roster model 2022-04-24 18:52:29 +03:00
3916aec358
unread messages count now is displayed on the launcher icon 2022-04-23 16:58:08 +03:00
721d3a1a89
refactoring: UI squawk now belongs to a new class, it enables me doing trayed mode, when main window is destroyed 2022-04-22 18:26:18 +03:00
83cb220175
better notification sending, edited message now modifies notification (or sends), little structure change 2022-04-19 20:24:41 +03:00
18859cb960
first ideas for notifications 2022-04-18 19:54:42 +03:00
4c20a314f0
a crash fix on one of archive corner cases 2022-04-17 16:25:15 +03:00
51ac1ac709
first attempt 2022-04-17 14:58:46 +03:00
8f949277f6
actual pasword reasking on failed authentication 2022-04-14 11:13:27 +03:00
ce686e121b
account removal bugfix, some testing 2022-04-13 22:02:48 +03:00
f64e5c2df0
account connect/disconnect now activate/deactivate, it's a bit less contraversial; async account password asking new concept 2022-04-12 23:33:10 +03:00
2c26c7e264
ui squawk refactoring 2022-04-11 18:45:12 +03:00
69e0c88d8d
account refactoring, pep support discovery started 2022-04-08 19:18:15 +03:00
82d54ba4df
Report bugs tab and thanks to tab in about widget 2022-04-07 18:26:43 +03:00
1b66fda318
License is now can be viewed locally, some organization name packaging issies 2022-04-05 22:00:56 +03:00
9f746d203b
new tab in About: components 2022-04-04 23:49:01 +03:00
27377e0ec5
first attempt to make About window 2022-04-03 23:53:46 +03:00
4baa3bccbf
new screenshot 2022-04-02 16:09:11 +03:00
4786388822
0.2.1 2022-04-02 15:53:23 +03:00
62f02c18d7
now you can't edit messages with attachments: no other client actually allowes that, and if I edit they don't handle it properly anyway 2022-04-02 15:34:36 +03:00
1fcd403dba
testing, solved unhandled exception, conditions to restrict old message to be edited, license un some files that used to miss them 2022-04-01 00:32:22 +03:00
5f6691067a
minor bugfixes about message body, automatic focus and that quirk with font becomming bigger 2022-03-29 19:05:24 +03:00
788c6ca556
now it's possible to fix your messages 2022-03-28 23:25:33 +03:00
bf4a27f35d
Bug with the edited message fixed, some further work on message correction 2022-03-27 22:05:31 +03:00
0823b35148
removed unused old message line files, first thoughts on message edition 2022-02-20 22:10:09 +03:00
73b1b58a96
Downloads folder now is movable 2022-02-19 21:31:49 +03:00
d8b5ccb2da
downloaded files now stored with squawk:// prefix, that way I can move downloads folder without messing up the database 2022-02-19 00:27:09 +03:00
243edff8bd
first thoughts about downloads path changing 2022-02-17 20:26:15 +03:00
da19eb86bb
color theme setting is now working 2022-01-27 20:44:32 +03:00
0ff9f12157
new optional KDE Frameworks plugin to support system color schemes 2022-01-26 23:53:44 +03:00
802e2f11a1
may be a bit better quit handling 2022-01-25 23:35:55 +03:00
c708c33a92
basic theme changing 2022-01-21 22:02:50 +03:00
a8a7ce2538
some more thoughts about settings widgets 2022-01-19 23:46:42 +03:00
841e526e59
just some toying with designer 2022-01-17 23:52:07 +03:00
6bee149e6b
started to work on settings 2022-01-16 22:54:57 +03:00
62a59eb7a1
Added logs for Shura to help me to debug a download attachment issue 2022-01-15 15:36:49 +03:00
296328f12d
a bit of polish 2022-01-11 23:50:42 +03:00
4d3ba6b11f
0.2.0 finalization 2022-01-09 17:32:23 +03:00
8a2658e4fc
message bubbles, avatar rounding, roster adjusments 2022-01-09 01:28:29 +03:00
9ac0ca10f3
avatar painting is returned to delegate; sender names now are not painted in every message 2022-01-07 17:02:49 +03:00
7130e674c4
some warnings fixed, new way of drawing avatars in message line 2022-01-05 22:29:34 +03:00
e27ae1a82f Merge pull request 'remove ./signalcatcher_win32.cpp' (#58) from shunf4/squawk:feat/adapt_win_osx_msgf into messageFeed
Reviewed-on: blue/squawk#58
2021-10-16 15:41:02 +00:00
1aa2b5a539 Merge pull request 'Fixes for Windows' (#57) from shunf4/squawk:fix/win_fix into messageFeed
Reviewed-on: blue/squawk#57
2021-10-16 15:40:48 +00:00
43bfaf9b7e Merge pull request 'don't save settings on quit, if readSettings() not finished' (#56) from shunf4/squawk:fix/wait_init_before_exit into messageFeed
Reviewed-on: blue/squawk#56
2021-10-16 15:39:41 +00:00
b19dafef33 Merge pull request 'allow receiving and storing messages with the same timestamp' (#55) from shunf4/squawk:fix/handle_msg_same_tm into messageFeed
Reviewed-on: blue/squawk#55
2021-10-16 15:39:05 +00:00
aeaa6b1b28 Merge pull request 'fix: request latest history' (#54) from shunf4/squawk:fix/correctly_query_history into messageFeed
Reviewed-on: blue/squawk#54
2021-10-16 15:37:59 +00:00
e47ba603e0 Merge pull request 'fix: use fallback icons on buttons, when no supported theme is installed' (#53) from shunf4/squawk:fix/btn_icons into messageFeed
Reviewed-on: blue/squawk#53
2021-10-16 15:36:57 +00:00
8fece95aa2 Merge pull request 'fix: respect password type when addAccount' (#52) from shunf4/squawk:fix/addAccount_pw_type into messageFeed
Reviewed-on: blue/squawk#52
2021-10-16 15:34:37 +00:00
893ff53aa8 Merge pull request 'feature: paste image in chat' (#51) from shunf4/squawk:feat/paste_img into messageFeed
Reviewed-on: blue/squawk#51
2021-10-16 15:34:00 +00:00
39f2f3d975 feat: copy pasted image file to download folder after successful upload 2021-10-16 00:21:02 +08:00
52551c1ce0 pasteImageAction should be a class member; refactor messageEditor's context menu callback into a member function 2021-10-13 20:06:33 +08:00
50d710de04 remove ./signalcatcher_win32.cpp 2021-10-13 11:41:48 +08:00
332131796c Merge pull request 'port to Windows (mingw64) and macOS; create appveyor.yml' (#50) from shunf4/squawk:feat/adapt_win_osx_msgf into messageFeed
Reviewed-on: blue/squawk#50
2021-10-11 10:40:46 +00:00
3a70df21f8 feat: paste image in chat 2021-10-06 23:09:18 +08:00
a24e8382d1 correctly retrieve latest archived messages per XEP-0313 2021-10-06 23:01:11 +08:00
d20fd84d39 respect password type when adding account, preventing loading bad password 2021-10-06 22:55:23 +08:00
5862f1552b don't save settings on quit, if readSettings() not finished 2021-10-06 22:52:20 +08:00
ebeb4089eb add fallback icons for buttons 2021-10-06 22:45:10 +08:00
a53126d8bc messages may have the same timestamp, put MDB_DUPSORT flag with order db 2021-10-06 22:04:29 +08:00
7db269acb5 Fixes for Windows
1. On Windows, the lmdb file is immediately allocated at full size. So we have to limit the size.

2. Windows need an organization name for QSettings to work. So an organization name is added for Windows target.
2021-10-06 19:15:45 +08:00
67e5f9744e fix ci macos matrix item 2021-10-06 18:50:00 +08:00
d0bdb374a0 add flag -fno-sized-deallocation, eliminating _ZdlPvm 2021-10-06 18:47:59 +08:00
8b3752ef47 fix ci for macos; adjust ci for linux executable with changed rpath 2021-10-06 18:12:26 +08:00
8c6ac1a21d add icns to macos bundle; use macdeployqt post build 2021-10-06 17:34:33 +08:00
1c0802c8ca fix win32 ci by place LMDB_INCLUDE_DIRS in core/ 2021-10-06 01:51:01 +08:00
d84a33e144 fix linux ci by finding and linking thread libraries 2021-10-06 01:33:58 +08:00
4f6946a5fc add LMDB_INCLUDE_DIRS as include dir, fixing win32 ci 2021-10-06 01:28:08 +08:00
f3153ef1db ci: add appveyor.yml 2021-10-06 01:17:30 +08:00
41d9fa7a1c fix macos bundle build 2021-10-06 01:07:47 +08:00
261b34b712 add forgotten cmake/MacOSXBundleInfo.plist.in 2021-10-06 00:55:39 +08:00
d1f108e69d update docs for win32 build 2021-10-06 00:53:50 +08:00
1e37aa762c generate app bundle for macOS 2021-10-06 00:48:25 +08:00
a1f3c00a54 remove dependency uuid 2021-10-06 00:15:25 +08:00
923afe2420 [Fix] Merge branch 'feat/adapt_win_osx' into upstream_messageFeed 2021-10-05 23:45:37 +08:00
faa7d396a5 add liblmdb.so as possible lmdb lib name 2021-10-05 16:09:31 +08:00
c55b7c6baf update docs for removed uuid dep and LMDB_DIR var 2021-10-05 15:20:25 +08:00
6764d8ea11 remove dependency libuuid 2021-10-05 12:56:36 +08:00
5fbb96618f adjust CMakeLists.txt, to prepare for win32 and macos builds 2021-10-05 12:49:06 +08:00
d89c030e66
translation verification
Portugues Brazil localization added provided by most welcome Bruno Fontes
2021-09-22 23:43:03 +03:00
5787af8a4f
Merge remote-tracking branch 'origin/master' into messageFeed 2021-09-22 23:09:48 +03:00
1706b93b59 Merge pull request 'Add Brazilian Portuguese translations' (#49) from brunofontes/squawk:ptBR_translations into master
Reviewed-on: blue/squawk#49
2021-09-22 20:08:54 +00:00
3f09b8f838
Date dividers between messages from different dates 2021-09-22 01:17:43 +03:00
87c216b491
Add Brazilian Portuguese translations 2021-07-21 19:37:31 -03:00
5f925217fc
edit icon next to the message showing if the message was edited and what was there a first 2021-05-25 01:06:05 +03:00
3f1fba4de2
doovers for failed messages, some corner cases fixes with handling errors during message sending 2021-05-23 01:03:14 +03:00
ddfaa63a24
big image preview optimisations, preview positioning fix, memory leaks fix 2021-05-17 23:32:44 +03:00
721f6daa36
fix bug when everything was treated as animation, bug with not working group amount of messages, handled the situation when preview is painted but the file was lost 2021-05-17 00:52:59 +03:00
0d584c5aba
message preview refactor, several bugs about label size, animations are now playing in previews 2021-05-16 01:07:49 +03:00
4307262f6e basic error download/upload files handling, need more testing 2021-05-14 22:49:38 +03:00
bd07c49755 Merge pull request 'Refactor CMakeLists' (#46) from vae/squawk:build-refactor into messageFeed
Reviewed-on: blue/squawk#46
2021-05-11 23:31:59 +00:00
vae
8e99cc2969
build: plugins/, passwordStorageEngines/wrappers/ as shared libs 2021-05-12 02:01:02 +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
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
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
277 changed files with 28689 additions and 10097 deletions

View File

@ -0,0 +1,45 @@
name: Squawk Release workflow
run-name: ${{ gitea.actor }} is running Squawk Release workflow on release ${{ gitea.event.release.tag_name }}
on:
release:
types: [published]
jobs:
Archlinux:
runs-on: archlinux
steps:
- name: Download the release tarball
run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz
- name: Calculate SHA256 for the tarball
run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV
- name: Unarchive tarball
run: tar -xvzf tarball.tar.gz
- name: Clone the AUR repository
run: |
echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key
chmod 600 key
GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/squawk.git aur
chmod 777 -R aur
cd aur
git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }}
- name: Copy PKGBUILD to the directory
run: cp squawk/packaging/Archlinux/PKGBUILD aur/
- name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO
working-directory: aur
run: |
sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD
sudo -u build makepkg --printsrcinfo > .SRCINFO
- name: Commit package to aur
working-directory: aur
run: |
git add PKGBUILD .SRCINFO
git commit -m "${{ gitea.event.release.body//\"/\\\" }}"
GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push

6
.gitmodules vendored
View File

@ -1,3 +1,9 @@
[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/storage"]
path = external/storage
url = https://git.macaw.me/blue/storage
[submodule "external/lmdbal"]
path = external/lmdbal
url = gitea@git.macaw.me:blue/lmdbal.git

3640
.uncrustify.cfg Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,97 @@
# Changelog # Changelog
## Squawk 0.2.0 (Unreleased) ## Squawk 0.2.4 (UNRELEASED)
### Bug fixes
- messages to the mucs get sent once again
### Improvements
- it's possible to build against Qt 6 now
- got rid of deprecated SimpleCrypt library
## Squawk 0.2.3 (February 04, 2024)
### Bug fixes
- "Add contact" and "Join conference" menu are enabled once again (pavavno)!
- availability is now read from the same section of config file it was stored
- automatic avatars (if a contact doesn't have one) get generated once again
### Improvements
- deactivated accounts now don't appear in combobox of "Add contact" and "Join conference" dialogues
- all of the expandable roster items now get saved between launches
- settings file on the disk is not rewritten every roster element expansion or collapse
- removed unnecessary own vcard request at sturtup (used to do it to discover my own avatar)
- vcard window now is Info system and it can display more information
- reduced vcard request spam in MUCs
### New features
- now you can enable tray icon from settings!
- there is a job queue now, this allowes to spread a bit the spam on the server at connection time
- squawk now queries clients of it's peers, you can see what programs other people use
## Squawk 0.2.2 (May 05, 2022)
### Bug fixes
- now when you remove an account it actually gets removed
- segfault on uninitialized Availability in some rare occasions
- fixed crash when you open a dialog with someone that has only error messages in archive
- message height is now calculated correctly on Chinese and Japanese paragraphs
- the app doesn't crash on SIGINT anymore
### Improvements
- there is a way to disable an account and it wouldn't connect when you change availability
- if you cancel password query an account becomes inactive and doesn't annoy you anymore
- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
- if left the password field empty and chose KWallet as a storage Squawk will try to get that password from KWallet before asking you to input it
- accounts now connect to the server asynchronously - if one is stopped on password prompt another is connecting
- actualized translations, added English localization file
### New features
- new "About" window with links, license, gratitudes
- if the authentication failed Squawk will ask again for your password and login
- now there is an amount of unread messages showing on top of Squawk launcher icon
- notifications now have buttons to open a conversation or to mark that message as read
## Squawk 0.2.1 (Apr 02, 2022)
### Bug fixes
- build in release mode now no longer spams warnings
- build now correctly installs all build plugin libs
- a bug where the correction message was received, the indication was on but the text didn't actually change
- message body now doesn't intecept context menu from the whole message
- message input now doesn't increase font when you remove everything from it
### Improvements
- reduced amount of places where platform specific path separator is used
- now message input is automatically focused when you open a dialog or a room
- what() method on unhandled exception now actually tells what happened
### New features
- the settings are here! You con config different stuff from there
- now it's possible to set up different qt styles from settings
- if you have KConfig nad KConfigWidgets packages installed - you can choose from global color schemes
- it's possible now to choose a folder where squawk is going to store downloaded files
- now you can correct your message
## Squawk 0.2.0 (Jan 10, 2022)
### Bug fixes ### Bug fixes
- carbon copies switches on again after reconnection - carbon copies switches on again after reconnection
- 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
- fallback icons for buttons, when no supported theme is installed (shunf4)
- better handling messages with no id (shunf4)
- removed dependency: uuid, now it's on Qt (shunf4)
- better requesting latest history (shunf4)
### Improvements ### 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 and looks cooler
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
- once uploaded local files don't get second time uploaded - the remote URL is reused
- way better compilation time (vae)
### New features
- pasting images from clipboard to attachment (shunf4)
- possible compilation for windows and macOS (shunf4)
## Squawk 0.1.5 (Jul 29, 2020) ## Squawk 0.1.5 (Jul 29, 2020)
### Bug fixes ### Bug fixes

View File

@ -1,158 +1,242 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.16)
project(squawk) project(squawk VERSION 0.2.4 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON) cmake_policy(SET CMP0076 NEW)
set(CMAKE_CXX_STANDARD 14) cmake_policy(SET CMP0077 NEW)
cmake_policy(SET CMP0079 NEW)
cmake_policy(SET CMP0167 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) set(WIN32_FLAG "")
find_package(Qt5LinguistTools) set(MACOSX_BUNDLE_FLAG "")
if (CMAKE_BUILD_TYPE STREQUAL "Release")
if(NOT CMAKE_BUILD_TYPE) if (WIN32)
set(CMAKE_BUILD_TYPE Debug) set(WIN32_FLAG WIN32)
endif(WIN32)
if (APPLE)
set(MACOSX_BUNDLE_FLAG MACOSX_BUNDLE)
endif(APPLE)
endif() endif()
if(CMAKE_COMPILER_IS_GNUCXX) add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra") target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
endif(CMAKE_COMPILER_IS_GNUCXX) option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
option(SYSTEM_LMDBAL "Use system lmdbal lib" ON)
option(WITH_KWALLET "Build KWallet support module" ON)
option(WITH_KIO "Build KIO support module" ON)
option(WITH_KCONFIG "Build KConfig support module" ON)
option(WITH_OMEMO "Build OMEMO support module" OFF) #it should be off by default untill I sort the problems out
# Dependencies
## Qt
if (NOT DEFINED QT_VERSION_MAJOR)
find_package(QT NAMES Qt6 Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
else ()
find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
endif()
find_package(Boost COMPONENTS)
target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
## OMEMO
if (WITH_OMEMO)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(OMEMO libomemo-c)
if (OMEMO_FOUND)
target_compile_definitions(squawk PRIVATE WITH_OMEMO)
message("Building with support of OMEMO")
else ()
message("libomemo-c package wasn't found, trying to build without OMEMO support")
set(WITH_OMEMO OFF)
endif ()
else ()
message("PKG_CONFIG module wasn't found, can not check libomemo-c support, trying to build without OMEMO support")
set(WITH_OMEMO OFF)
endif ()
endif ()
## KIO
if (WITH_KIO)
find_package(KF${QT_VERSION_MAJOR}KIO CONFIG)
if (NOT KF${QT_VERSION_MAJOR}KIO_FOUND)
set(WITH_KIO OFF)
message("KIO package wasn't found, KIO support modules wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KIO)
message("Building with support of KIO")
endif ()
endif ()
## KWallet
if (WITH_KWALLET)
find_package(KF${QT_VERSION_MAJOR}Wallet CONFIG)
if (NOT KF${QT_VERSION_MAJOR}Wallet_FOUND)
set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
message("Building with support of KWallet")
endif ()
endif ()
## KConfig
if (WITH_KCONFIG)
find_package(KF${QT_VERSION_MAJOR}Config CONFIG)
if (NOT KF${QT_VERSION_MAJOR}Config_FOUND)
set(WITH_KCONFIG OFF)
message("KConfig package wasn't found, KConfig support modules wouldn't be built")
else()
find_package(KF${QT_VERSION_MAJOR}ConfigWidgets CONFIG)
if (NOT KF${QT_VERSION_MAJOR}ConfigWidgets_FOUND)
set(WITH_KCONFIG OFF)
message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built")
else()
target_compile_definitions(squawk PRIVATE WITH_KCONFIG)
message("Building with support of KConfig")
message("Building with support of KConfigWidgets")
endif()
endif()
endif()
## QXmpp
if (SYSTEM_QXMPP)
if (WITH_OMEMO)
find_package(QXmppQt${QT_VERSION_MAJOR} CONFIG COMPONENTS Omemo)
else ()
find_package(QXmppQt${QT_VERSION_MAJOR} CONFIG)
endif ()
if (NOT QXmppQt${QT_VERSION_MAJOR}_FOUND)
set(SYSTEM_QXMPP OFF)
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
else ()
message("Building with system QXmpp")
endif ()
endif () #it's endif() + if() and not else() because I want it to have a fallback behaviour
if (NOT SYSTEM_QXMPP) #we can fail finding system QXmpp and this way we'll check bundled before failing completely
message("Building with bundled QXmpp")
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/base)
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/client)
target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src)
if (WITH_OMEMO)
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/omemo)
target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src/omemo)
set(BUILD_OMEMO ON)
set(BUILD_TESTS OFF)
else ()
set(BUILD_OMEMO OFF)
endif ()
add_subdirectory(external/qxmpp)
add_library(QXmpp::QXmpp ALIAS QXmppQt${QT_VERSION_MAJOR})
if (WITH_OMEMO)
target_include_directories(QXmppOmemoQt${QT_VERSION_MAJOR} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src)
add_library(QXmpp::Omemo ALIAS QXmppOmemoQt${QT_VERSION_MAJOR})
endif ()
endif ()
## LMDBAL
if (SYSTEM_LMDBAL)
find_package(lmdbalqt${QT_VERSION_MAJOR})
if (NOT lmdbalqt${QT_VERSION_MAJOR}_FOUND)
set(SYSTEM_LMDBAL OFF)
message("LMDBAL package wasn't found, trying to build with bundled LMDBAL")
else ()
message("Building with system LMDBAL")
endif ()
else()
message("Building with bundled LMDBAL")
set(BUILD_STATIC ON)
add_subdirectory(external/lmdbal)
add_library(LMDBALQT${QT_VERSION_MAJOR}::LMDBALQT${QT_VERSION_MAJOR} ALIAS LMDBAL)
endif()
find_package(OpenSSL REQUIRED)
## Linking
target_link_libraries(squawk
PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::DBus
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Xml
LMDBALQT${QT_VERSION_MAJOR}::LMDBALQT${QT_VERSION_MAJOR}
OpenSSL::Crypto
QXmpp::QXmpp
)
if (WITH_OMEMO)
target_link_libraries(squawk PRIVATE QXmpp::Omemo)
endif ()
## Link thread libraries on Linux
if(UNIX AND NOT APPLE)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(squawk PRIVATE Threads::Threads)
endif()
## Build type
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif ()
message("Build type: ${CMAKE_BUILD_TYPE}") message("Build type: ${CMAKE_BUILD_TYPE}")
if(CMAKE_COMPILER_IS_GNUCXX)
set (COMPILE_OPTIONS -fno-sized-deallocation) # for eliminating _ZdlPvm
if (CMAKE_BUILD_TYPE STREQUAL "Release")
list(APPEND COMPILE_OPTIONS -O3)
endif()
if (CMAKE_BUILD_TYPE STREQUAL Debug)
list(APPEND COMPILE_OPTIONS -g)
list(APPEND COMPILE_OPTIONS -Wall)
list(APPEND COMPILE_OPTIONS -Wextra)
endif()
set(squawk_SRC message("Compilation options: " ${COMPILE_OPTIONS})
main.cpp target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
exception.cpp endif(CMAKE_COMPILER_IS_GNUCXX)
shared/global.cpp
shared/utils.cpp
shared/message.cpp
shared/vcard.cpp
shared/icons.cpp
)
if (WIN32) # I am not really sure about this solution
list(APPEND squawk_SRC signalcatcher_win32.cpp) # This should enable plugins to be found in path like /usr/lib/squawk instead of just /usr/lib
else (WIN32) set(PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/squawk")
list(APPEND squawk_SRC signalcatcher.cpp) add_compile_definitions(PLUGIN_PATH="${PLUGIN_PATH}")
endif (WIN32)
set(squawk_HEAD add_subdirectory(main)
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)
configure_file(squawk.rc squawk.rc COPYONLY)
if (WIN32)
execute_process(COMMAND magick convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND magick convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND magick convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND magick convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND magick convert squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
else (WIN32)
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})
endif (WIN32)
if (APPLE)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset")
execute_process(COMMAND which convert WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} RESULT_VARIABLE ret)
message("AAA ${ret}")
execute_process(COMMAND echo $PATH WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} RESULT_VARIABLE ret)
message("AAA ${ret} $ENV{PATH}")
execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} RESULT_VARIABLE ret)
message("AAA ${ret}")
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns)
set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES
MACOSX_PACKAGE_LOCATION "Resources")
endif (APPLE)
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(WITH_KWALLET "Build KWallet support module" ON)
if (SYSTEM_QXMPP)
find_package(QXmpp CONFIG)
if (NOT QXmpp_FOUND)
set(SYSTEM_QXMPP OFF)
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
else()
message("Building with system QXmpp")
endif()
endif()
if(NOT SYSTEM_QXMPP)
add_subdirectory(external/qxmpp)
endif()
if (WITH_KWALLET)
find_package(KF5Wallet CONFIG)
if (NOT KF5Wallet_FOUND)
set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built")
else()
add_definitions(-DWITH_KWALLET)
message("Building with support of KWallet")
endif()
endif()
add_executable(squawk WIN32 ${squawk_SRC} ${squawk_HEAD} ${RCC} ${SQUAWK_WIN_RC} ${APP_ICON_MACOSX})
target_link_libraries(squawk Qt5::Widgets)
add_subdirectory(ui)
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(packaging)
add_subdirectory(plugins)
add_subdirectory(resources)
add_subdirectory(shared)
add_subdirectory(translations)
add_subdirectory(ui)
add_subdirectory(external/simpleCrypt) ## Install the executable
target_link_libraries(squawk squawkUI)
target_link_libraries(squawk squawkCORE)
add_dependencies(${CMAKE_PROJECT_NAME} translations)
# 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 README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
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) if (CMAKE_BUILD_TYPE STREQUAL "Release")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png) if (APPLE)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png) add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..."
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) COMMAND "${Qt${QT_VERSION_MAJOR}Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
)
endif(APPLE)
endif()

View File

@ -595,17 +595,17 @@ pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author> Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -4,16 +4,20 @@
[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/) [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
![Squawk screenshot](https://macaw.me/images/squawk/0.1.4.png) ![Squawk screenshot](https://macaw.me/projects/squawk/0.2.2.png)
### Prerequisites ### Prerequisites
- QT 5.12 *(lower versions might work but it wasn't tested)* - QT 5 or 6
- uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_ - CMake 3.10 or higher
- lmdb
- CMake 3.0 or higher
- qxmpp 1.1.0 or higher - qxmpp 1.1.0 or higher
- kwallet (optional) - LMDBAL (my own [library](https://git.macaw.me/blue/lmdbal) for lmdb)
- KDE Frameworks: kwallet (optional)
- KDE Frameworks: KIO (optional)
- KDE Frameworks: KConfig (optional)
- KDE Frameworks: KConfigWidgets (optional)
- Boost (just one little hpp from there)
- Imagemagick (for compilation, to rasterize an SVG logo)
### Getting ### Getting
@ -29,15 +33,19 @@ $ pacaur -S squawk
### Building ### Building
You can also clone the repo and build it from source You can also the repo and build it from source
Squawk requires Qt with SSL enabled. It uses CMake as build system. Squawk requires Qt with SSL enabled. It uses CMake as build system.
There are two ways to build, it depends whether you have qxmpp installed in your system Please check the prerequisites and install them before installation.
#### Building with system qxmpp ---
Here is what you do There are several ways to build Squawk. The one you need depends on whether you have `qxmpp` and `lmdbal` installed in your system.
#### Building with system dependencies
This is the easiest way but it requires you to have `qxmpp` and `lmdbal` installed as system packages. Here is what you do:
``` ```
$ git clone https://git.macaw.me/blue/squawk $ git clone https://git.macaw.me/blue/squawk
@ -50,6 +58,9 @@ $ cmake --build .
#### Building with bundled qxmpp #### Building with bundled qxmpp
If you don't have any of `qxmpp` or `lmdbal` (or both) installed the process is abit mor complicated.
On the configuration stage you need to enable one or both entries in the square brackets, depending on what package your system lacks.
Here is what you do Here is what you do
``` ```
@ -57,16 +68,47 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk
$ cd squawk $ cd squawk
$ mkdir build $ mkdir build
$ cd build $ cd build
$ cmake .. -D SYSTEM_QXMPP=False $ cmake .. [-D SYSTEM_QXMPP=False] [-D SYSTEM_LMDBAL=False]
$ cmake --build . $ cmake --build .
``` ```
#### For Windows (Mingw-w64) build
**Building for windows is not mainteined, but was possible in the past, you can try, but it probably won't work**
You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
The best way to acquire library `lmdb` and `boost` is through Msys2.
First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86_64-boost` by pacman.
Then you need to provide the cmake cache entry when calling cmake for configuration:
`<Msys2 Mingw64 Root Directory>`: e.g. `C:/msys64/mingw64`.
```
$ git clone --recurse-submodules https://git.macaw.me/blue/squawk
$ cd squawk
$ mkdir build
$ cd build
$ cmake .. -D SYSTEM_QXMPP=False -D SYSTEM_LMDBAL=False -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
$ cmake --build .
```
You can always refer to `appveyor.yml` to see how AppVeyor build squawk for windows.
### List of keys ### List of keys
Here is the list of keys you can pass to configuration phase of `cmake ..`. Here is the list of keys you can pass to configuration phase of `cmake ..`:
- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`) - `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`)
- `SYSTEM_LMDBAL` - `True` tries to link against `LMDABL` installed in the system, `False` builds bundled `LMDBAL` library (default is `True`)
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`) - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
- `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
- `WITH_OMEMO` - `True` builds the OMEMO encryption, requires `qxmpp` of version >= 1.5.0 built with OMEMO support. `False` disables OMEMO support (default is `False`)
- `QT_VERSION_MAJOR` - `6` builds against Qt 6, `5` builds against Qt 6, corresponding version of lmdbal and qxmpp should be installed. By default it picks your system default Qt
## License ## License

117
appveyor.yml Normal file
View File

@ -0,0 +1,117 @@
image:
- Visual Studio 2019
- "Previous Ubuntu1804"
- macOS-Mojave
branches:
except:
- gh-pages
for:
-
matrix:
only:
- image: Visual Studio 2019
environment:
QTDIR: C:\Qt\5.15.2\mingw81_64
QTTOOLDIR: C:\Qt\Tools\mingw810_64\bin
QTNINJADIR: C:\Qt\Tools\Ninja
install:
- set PATH=%QTTOOLDIR%;%QTNINJADIR%;%QTDIR%\bin;%PATH%
- git submodule update --init --recursive
before_build:
- choco install --yes zstandard
- choco install --yes --version=7.1.0.2 imagemagick.app
- set PATH=C:\Program Files\ImageMagick-7.1.0-Q16-HDRI;%PATH%
- mkdir lmdb
- cd lmdb
- curl -OL https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
- zstd -d ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
- tar -xvf ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar
- cd ..
- mkdir boost
- cd boost
- curl -OL https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
- zstd -d ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
- tar -xvf ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar
- cd ..
- mkdir build
- cd build
- cmake -GNinja -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=%QTDIR% -DLMDB_ROOT_DIR:PATH=C:/projects/squawk/lmdb/mingw64 -DBOOST_ROOT:PATH=C:/projects/squawk/boost/mingw64 ..
build_script:
- cmake --build .
- mkdir deploy
- cd deploy
- copy ..\squawk.exe .\
- copy ..\external\qxmpp\src\libqxmpp.dll .\
- windeployqt .\squawk.exe
- windeployqt .\libqxmpp.dll
- cd ..\..
artifacts:
- path: build/deploy/squawk.exe
name: Squawk executable (Qt 5.15.2)
- path: build/deploy
name: Squawk deployment with Qt Framework
-
matrix:
only:
- image: macOS-Mojave
install:
- brew install lmdb imagemagick boost
- git submodule update --init --recursive
before_build:
- mkdir build
- cd build
- cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.15.2/clang_64 ..
build_script:
- cmake --build .
after_build:
- zip -r squawk.app.zip squawk.app
artifacts:
- path: build/squawk.app/Contents/MacOS/squawk
name: Squawk executable (Qt 5.15.2)
- path: build/external/qxmpp/src/
name: QXMPP
- path: build/squawk.app.zip
name: Squawk Bundle with Qt Framework (Qt 5.15.2)
-
matrix:
only:
- image: "Previous Ubuntu1804"
install:
- ls $HOME/Qt
- sudo apt update
- sudo apt install -y liblmdb-dev liblmdb0 imagemagick mesa-common-dev libglu1-mesa-dev libboost-all-dev
- git submodule update --init --recursive
before_build:
- mkdir build
- cd build
- cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 -DCMAKE_BUILD_RPATH="\$ORIGIN" ..
build_script:
- cmake --build .
after_build:
- zip -r squawk.zip squawk -j external/qxmpp/src/libqxmpp*
artifacts:
- path: build/squawk.zip
name: Squawk executable and libraries (Qt 5.12)

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>
</dict>
</plist>

View File

@ -1,54 +1,34 @@
cmake_minimum_required(VERSION 3.0) set(SIGNALCATCHER_SOURCE signalcatcher.cpp)
project(squawkCORE) if(WIN32)
set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
endif(WIN32)
set(CMAKE_AUTOMOC ON) set(SOURCE_FILES
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
find_package(Qt5Network CONFIG REQUIRED)
find_package(Qt5Xml CONFIG REQUIRED)
find_path(LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "${LMDB_DIR}/include")
set(LMDB_LIBRARIES "${LMDB_DIR}/lib/liblmdb.a" )
message(${LMDB_INCLUDE_DIR})
message(${LMDB_LIBRARIES})
set(squawkCORE_SRC
squawk.cpp
account.cpp account.cpp
archive.cpp adapterfunctions.cpp
rosteritem.cpp
contact.cpp
conference.cpp conference.cpp
storage.cpp contact.cpp
networkaccess.cpp rosteritem.cpp
adapterFuctions.cpp ${SIGNALCATCHER_SOURCE}
handlers/messagehandler.cpp squawk.cpp
handlers/rosterhandler.cpp
) )
set(HEADER_FILES
account.h
adapterfunctions.h
conference.h
contact.h
rosteritem.h
signalcatcher.h
squawk.h
)
target_sources(squawk PRIVATE ${SOURCE_FILES})
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
add_subdirectory(handlers)
add_subdirectory(passwordStorageEngines) add_subdirectory(passwordStorageEngines)
add_subdirectory(components)
# Tell CMake to create the helloworld executable add_subdirectory(delayManager)
add_library(squawkCORE ${squawkCORE_SRC}) add_subdirectory(utils)
target_include_directories(squawkCORE PUBLIC ${LMDB_INCLUDE_DIR})
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 ${LMDB_LIBRARIES})
target_link_libraries(squawkCORE simpleCrypt)
if (WITH_KWALLET)
target_link_libraries(squawkCORE kwalletPSE)
endif()

File diff suppressed because it is too large Load Diff

View File

@ -15,9 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#pragma once
#ifndef CORE_ACCOUNT_H
#define CORE_ACCOUNT_H
#include <QObject> #include <QObject>
#include <QCryptographicHash> #include <QCryptographicHash>
@ -29,9 +27,14 @@
#include <map> #include <map>
#include <set> #include <set>
#include <list>
#include <QXmppRosterManager.h> #include <QXmppRosterManager.h>
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
#include <QXmppCarbonManagerV2.h>
#else
#include <QXmppCarbonManager.h> #include <QXmppCarbonManager.h>
#endif
#include <QXmppDiscoveryManager.h> #include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h> #include <QXmppMamManager.h>
#include <QXmppMucManager.h> #include <QXmppMucManager.h>
@ -39,32 +42,58 @@
#include <QXmppBookmarkManager.h> #include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h> #include <QXmppBookmarkSet.h>
#include <QXmppUploadRequestManager.h> #include <QXmppUploadRequestManager.h>
#include <QXmppVCardIq.h>
#include <QXmppVCardManager.h> #include <QXmppVCardManager.h>
#include <QXmppMessageReceiptManager.h> #include <QXmppMessageReceiptManager.h>
#include <QXmppPubSubManager.h>
#include "shared.h" #include <shared/shared.h>
#include <shared/identity.h>
#include <shared/info.h>
#include <shared/clientid.h>
#include "contact.h" #include "contact.h"
#include "conference.h" #include "conference.h"
#include "networkaccess.h" #include <core/components/networkaccess.h>
#include <core/delayManager/manager.h>
#include "handlers/messagehandler.h" #include "handlers/messagehandler.h"
#include "handlers/rosterhandler.h" #include "handlers/rosterhandler.h"
#include "handlers/vcardhandler.h"
#include "handlers/discoveryhandler.h"
namespace Core #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
{ #include <QXmppTrustManager.h>
#ifdef WITH_OMEMO
#include <QXmppOmemoManager.h>
#include "handlers/omemohandler.h"
#endif
#include "handlers/trusthandler.h"
#endif
class Account : public QObject namespace Core {
{
class Account : public QObject {
Q_OBJECT Q_OBJECT
friend class MessageHandler; friend class MessageHandler;
friend class RosterHandler; friend class RosterHandler;
friend class VCardHandler;
friend class DiscoveryHandler;
#ifdef WITH_OMEMO
friend class OmemoHandler;
friend class TrustHandler;
#endif
public: public:
enum class Error {
authentication,
other,
none
};
Account( Account(
const QString& p_login, const QString& p_login,
const QString& p_server, const QString& p_server,
const QString& p_password, const QString& p_password,
const QString& p_name, const QString& p_name,
bool p_active,
NetworkAccess* p_net, NetworkAccess* p_net,
QObject* parent = 0); QObject* parent = 0);
~Account(); ~Account();
@ -76,8 +105,12 @@ public:
QString getPassword() const; QString getPassword() const;
QString getResource() const; QString getResource() const;
QString getAvatarPath() const; QString getAvatarPath() const;
QString getBareJid() const;
QString getFullJid() const;
Shared::Availability getAvailability() const; Shared::Availability getAvailability() const;
Shared::AccountPassword getPasswordType() const; Shared::AccountPassword getPasswordType() const;
Error getLastError() const;
bool getActive() const;
void setName(const QString& p_name); void setName(const QString& p_name);
void setLogin(const QString& p_login); void setLogin(const QString& p_login);
@ -86,9 +119,8 @@ public:
void setResource(const QString& p_resource); void setResource(const QString& p_resource);
void setAvailability(Shared::Availability avail); void setAvailability(Shared::Availability avail);
void setPasswordType(Shared::AccountPassword pt); void setPasswordType(Shared::AccountPassword pt);
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 setActive(bool p_active);
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,18 +129,25 @@ 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 setContactEncryption(const QString& jid, Shared::EncryptionProtocol value);
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);
void removeRoomRequest(const QString& jid); void removeRoomRequest(const QString& jid);
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void uploadVCard(const Shared::VCard& card); void updateInfo(const Shared::Info& info);
void resendMessage(const QString& jid, const QString& id);
void replaceMessage(const QString& originalId, const Shared::Message& data);
void invalidatePassword();
void discoverInfo(const QString& address, const QString& node);
public slots: public slots:
void connect(); void connect();
void disconnect(); void disconnect();
void reconnect(); void reconnect();
void requestVCard(const QString& jid); void requestInfo(const QString& jid);
signals: signals:
void changed(const QMap<QString, QVariant>& data); void changed(const QMap<QString, QVariant>& data);
@ -127,14 +166,17 @@ 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 infoReady(const Shared::Info& info);
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);
void needPassword();
void infoDiscovered(const QString& address, const QString& node, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
void pepSupportChanged(Shared::Support support);
private: private:
QString name; QString name;
@ -143,7 +185,26 @@ private:
QXmppConfiguration config; QXmppConfiguration config;
QXmppPresence presence; QXmppPresence presence;
Shared::ConnectionState state; Shared::ConnectionState state;
MessageHandler* mh;
RosterHandler* rh;
VCardHandler* vh;
DiscoveryHandler* dh;
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
TrustHandler* th;
#endif
#ifdef WITH_OMEMO
OmemoHandler* oh;
QXmppOmemoManager* om;
#endif
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
QXmppTrustManager* tm;
QXmppCarbonManagerV2* cm;
QXmppPubSubManager* psm;
#else
QXmppCarbonManager* cm; QXmppCarbonManager* cm;
#endif
QXmppMamManager* am; QXmppMamManager* am;
QXmppMucManager* mm; QXmppMucManager* mm;
QXmppBookmarkManager* bm; QXmppBookmarkManager* bm;
@ -155,16 +216,14 @@ private:
bool reconnectScheduled; bool reconnectScheduled;
QTimer* reconnectTimer; QTimer* reconnectTimer;
std::set<QString> pendingVCardRequests;
QString avatarHash;
QString avatarType;
bool ownVCardRequestInProgress;
NetworkAccess* network; NetworkAccess* network;
DelayManager::Manager* delay;
Shared::AccountPassword passwordType; Shared::AccountPassword passwordType;
Error lastError;
MessageHandler* mh; Shared::Support pepSupport;
RosterHandler* rh; bool active;
bool notReadyPassword;
bool loadingOmemo;
private slots: private slots:
void onClientStateChange(QXmppClient::State state); void onClientStateChange(QXmppClient::State state);
@ -177,21 +236,13 @@ private slots:
void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete); void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
void onMamLog(QXmppLogger::MessageType type, const QString &msg); void onMamLog(QXmppLogger::MessageType type, const QString &msg);
void onVCardReceived(const QXmppVCardIq& card);
void onOwnVCardReceived(const QXmppVCardIq& card);
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items); void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
private: private:
void handleDisconnection(); void handleDisconnection();
void onReconnectTimer(); void onReconnectTimer();
void setPepSupport(Shared::Support support);
void runDiscoveryService();
}; };
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
} }
#endif // CORE_ACCOUNT_H

View File

@ -1,5 +1,5 @@
/* /*
* Squawk messenger. * Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me> * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -15,10 +15,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CORE_ADAPTER_FUNCTIONS_H
#define CORE_ADAPTER_FUNCTIONS_H
#include "account.h" #include "adapterfunctions.h"
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card) void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
{ {
@ -264,12 +262,10 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
phone.setType(phone.type() | QXmppVCardPhone::Preferred); phone.setType(phone.type() | QXmppVCardPhone::Preferred);
} }
} }
for (const std::pair<QString, QXmppVCardPhone>& phone : phones) { for (const std::pair<const QString, QXmppVCardPhone>& phone : phones) {
phs.push_back(phone.second); phs.push_back(phone.second);
} }
iq.setEmails(emails); iq.setEmails(emails);
iq.setPhones(phs); iq.setPhones(phs);
} }
#endif // CORE_ADAPTER_FUNCTIONS_H

47
core/adapterfunctions.h Normal file
View File

@ -0,0 +1,47 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_ADAPTER_FUNCTIONS_H
#define CORE_ADAPTER_FUNCTIONS_H
#include <QXmppVCardIq.h>
#include <QXmppTask.h>
#include <QXmppPromise.h>
#include <shared/vcard.h>
namespace Core {
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
template<typename T>
QXmppTask<T> makeReadyTask(T &&value) {
QXmppPromise<T> promise;
promise.finish(std::move(value));
return promise.task();
}
inline QXmppTask<void> makeReadyTask() {
QXmppPromise<void> promise;
promise.finish();
return promise.task();
}
}
#endif // CORE_ADAPTER_FUNCTIONS_H

View File

@ -1,988 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "archive.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <QStandardPaths>
#include <QDebug>
#include <QDataStream>
#include <QDir>
Core::Archive::Archive(const QString& p_jid, QObject* parent):
QObject(parent),
jid(p_jid),
opened(false),
fromTheBeginning(false),
environment(),
main(),
order(),
stats(),
avatars()
{
}
Core::Archive::~Archive()
{
close();
}
void Core::Archive::open(const QString& account)
{
if (!opened) {
mdb_env_create(&environment);
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + account + "/" + jid;
QDir cache(path);
if (!cache.exists()) {
bool res = cache.mkpath(path);
if (!res) {
throw Directory(path.toStdString());
}
}
mdb_env_set_maxdbs(environment, 5);
mdb_env_set_mapsize(environment, 16UL * 1024UL * 1024UL);
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
mdb_dbi_open(txn, "main", MDB_CREATE, &main);
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);
mdb_txn_commit(txn);
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
try {
fromTheBeginning = getStatBoolValue("beginning", txn);
} catch (const NotFound& e) {
fromTheBeginning = false;
}
std::string sJid = jid.toStdString();
AvatarInfo info;
bool hasAvatar = readAvatarInfo(info, sJid, txn);
mdb_txn_abort(txn);
if (hasAvatar) {
QFile ava(path + "/" + sJid.c_str() + "." + info.type);
if (!ava.exists()) {
bool success = dropAvatar(sJid);
if (!success) {
qDebug() << "error opening archive" << jid << "for account" << account
<< ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
}
}
}
opened = true;
}
}
void Core::Archive::close()
{
if (opened) {
mdb_dbi_close(environment, sid);
mdb_dbi_close(environment, avatars);
mdb_dbi_close(environment, stats);
mdb_dbi_close(environment, order);
mdb_dbi_close(environment, main);
mdb_env_close(environment);
opened = false;
}
}
bool Core::Archive::addElement(const Shared::Message& message)
{
if (!opened) {
throw Closed("addElement", jid.toStdString());
}
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
message.serialize(ds);
quint64 stamp = message.getTime().toMSecsSinceEpoch();
const std::string& id = message.getId().toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc == 0) {
MDB_val orderKey;
orderKey.mv_size = 8;
orderKey.mv_data = (uint8_t*) &stamp;
rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
if (rc) {
qDebug() << "An element couldn't be inserted into the index" << mdb_strerror(rc);
mdb_txn_abort(txn);
return false;
} else {
if (message.getStanzaId().size() > 0) {
const std::string& szid = message.getStanzaId().toStdString();
lmdbKey.mv_size = szid.size();
lmdbKey.mv_data = (char*)szid.c_str();
lmdbData.mv_size = id.size();
lmdbData.mv_data = (uint8_t*)id.data();
rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc) {
qDebug() << "An element stanzaId to id pair couldn't be inserted into the archive" << mdb_strerror(rc);
mdb_txn_abort(txn);
return false;
} else {
rc = mdb_txn_commit(txn);
if (rc) {
qDebug() << "A transaction error: " << mdb_strerror(rc);
return false;
}
return true;
}
} else {
rc = mdb_txn_commit(txn);
if (rc) {
qDebug() << "A transaction error: " << mdb_strerror(rc);
return false;
}
return true;
}
}
} else {
qDebug() << "An element couldn't been added to the archive, skipping" << mdb_strerror(rc);
mdb_txn_abort(txn);
return false;
}
}
void Core::Archive::clear()
{
if (!opened) {
throw Closed("clear", jid.toStdString());
}
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
mdb_drop(txn, main, 0);
mdb_drop(txn, order, 0);
mdb_drop(txn, stats, 0);
mdb_drop(txn, avatars, 0);
mdb_drop(txn, sid, 0);
mdb_txn_commit(txn);
}
Shared::Message Core::Archive::getElement(const QString& id) const
{
if (!opened) {
throw Closed("getElement", jid.toStdString());
}
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
try {
Shared::Message msg = getMessage(id.toStdString(), txn);
mdb_txn_abort(txn);
return msg;
} catch (...) {
mdb_txn_abort(txn);
throw;
}
}
bool Core::Archive::hasElement(const QString& id) const
{
if (!opened) {
throw Closed("hasElement", jid.toStdString());
}
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
bool has;
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.toStdString().c_str();
int rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
has = rc == 0;
mdb_txn_abort(txn);
return has;
}
Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn) const
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
if (rc == 0) {
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
QDataStream ds(&ba, QIODevice::ReadOnly);
Shared::Message msg;
msg.deserialize(ds);
return msg;
} else if (rc == MDB_NOTFOUND) {
throw NotFound(id, jid.toStdString());
} else {
throw Unknown(jid.toStdString(), mdb_strerror(rc));
}
}
void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
{
if (!opened) {
throw Closed("setMessageState", jid.toStdString());
}
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
std::string strId(id.toStdString());
try {
Shared::Message msg = getMessage(strId, txn);
bool hadStanzaId = msg.getStanzaId().size() > 0;
QDateTime oTime = msg.getTime();
bool idChange = msg.change(data);
MDB_val lmdbKey, lmdbData;
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
msg.serialize(ds);
lmdbKey.mv_size = strId.size();
lmdbKey.mv_data = (char*)strId.c_str();
int rc;
if (idChange) {
rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
if (rc == 0) {
strId = msg.getId().toStdString();
lmdbKey.mv_size = strId.size();
lmdbKey.mv_data = (char*)strId.c_str();
quint64 stamp = oTime.toMSecsSinceEpoch();
lmdbData.mv_data = (quint8*)&stamp;
lmdbData.mv_size = 8;
rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
if (rc != 0) {
throw Unknown(jid.toStdString(), mdb_strerror(rc));
}
} else {
throw Unknown(jid.toStdString(), mdb_strerror(rc));
}
}
if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) {
const std::string& szid = msg.getStanzaId().toStdString();
lmdbData.mv_size = szid.size();
lmdbData.mv_data = (char*)szid.c_str();
rc = mdb_put(txn, sid, &lmdbData, &lmdbKey, 0);
if (rc != 0) {
throw Unknown(jid.toStdString(), mdb_strerror(rc));
}
};
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
if (rc == 0) {
rc = mdb_txn_commit(txn);
} else {
throw Unknown(jid.toStdString(), mdb_strerror(rc));
}
} catch (...) {
mdb_txn_abort(txn);
throw;
}
}
Shared::Message Core::Archive::newest()
{
return edge(true);
}
QString Core::Archive::newestId()
{
if (!opened) {
throw Closed("newestId", jid.toStdString());
}
Shared::Message msg = newest();
return msg.getId();
}
QString Core::Archive::oldestId()
{
if (!opened) {
throw Closed("oldestId", jid.toStdString());
}
Shared::Message msg = oldest();
return msg.getId();
}
Shared::Message Core::Archive::oldest()
{
return edge(false);
}
Shared::Message Core::Archive::edge(bool end)
{
QString name;
MDB_cursor_op begin;
MDB_cursor_op iteration;
if (end) {
name = "newest";
begin = MDB_LAST;
iteration = MDB_PREV;
} else {
name = "oldest";
begin = MDB_FIRST;
iteration = MDB_NEXT;
}
if (!opened) {
throw Closed(name.toStdString(), jid.toStdString());
}
MDB_txn *txn;
MDB_cursor* cursor;
MDB_val lmdbKey, lmdbData;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
rc = mdb_cursor_open(txn, order, &cursor);
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, begin);
Shared::Message msg = getStoredMessage(txn, cursor, iteration, &lmdbKey, &lmdbData, rc);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
if (rc) {
qDebug() << "Error geting" << name << "message" << mdb_strerror(rc);
throw Empty(jid.toStdString());
} else {
return msg;
}
}
Shared::Message Core::Archive::getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc)
{
Shared::Message msg;
std::string sId;
while (true) {
if (rc) {
break;
}
sId = std::string((char*)value->mv_data, value->mv_size);
try {
msg = getMessage(sId, txn);
if (msg.serverStored()) {
break;
} else {
rc = mdb_cursor_get(cursor, key, value, op);
}
} catch (...) {
break;
}
}
return msg;
}
unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages)
{
if (!opened) {
throw Closed("addElements", jid.toStdString());
}
int success = 0;
int rc = 0;
MDB_val lmdbKey, lmdbData;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
std::list<Shared::Message>::const_iterator itr = messages.begin();
while (rc == 0 && itr != messages.end()) {
const Shared::Message& message = *itr;
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
message.serialize(ds);
quint64 stamp = message.getTime().toMSecsSinceEpoch();
const std::string& id = message.getId().toStdString();
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc == 0) {
MDB_val orderKey;
orderKey.mv_size = 8;
orderKey.mv_data = (uint8_t*) &stamp;
rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
if (rc) {
qDebug() << "An element couldn't be inserted into the index, aborting the transaction" << mdb_strerror(rc);
} else {
if (message.getStanzaId().size() > 0) {
const std::string& szid = message.getStanzaId().toStdString();
lmdbKey.mv_size = szid.size();
lmdbKey.mv_data = (char*)szid.c_str();
lmdbData.mv_size = id.size();
lmdbData.mv_data = (uint8_t*)id.data();
rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc) {
qDebug() << "During bulk add an element stanzaId to id pair couldn't be inserted into the archive, continuing without stanzaId" << mdb_strerror(rc);
}
}
success++;
}
} else {
if (rc == MDB_KEYEXIST) {
rc = 0;
} else {
qDebug() << "An element couldn't been added to the archive, aborting the transaction" << mdb_strerror(rc);
}
}
itr++;
}
if (rc != 0) {
mdb_txn_abort(txn);
success = 0;
} else {
mdb_txn_commit(txn);
}
return success;
}
long unsigned int Core::Archive::size() const
{
if (!opened) {
throw Closed("size", jid.toStdString());
}
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_stat stat;
mdb_stat(txn, order, &stat);
mdb_txn_abort(txn);
return stat.ms_entries;
}
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
{
if (!opened) {
throw Closed("getBefore", jid.toStdString());
}
std::list<Shared::Message> res;
MDB_cursor* cursor;
MDB_txn *txn;
MDB_val lmdbKey, lmdbData;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
rc = mdb_cursor_open(txn, order, &cursor);
if (id == "") {
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
if (rc) {
qDebug() << "Error getting before" << mdb_strerror(rc) << ", id:" << id;
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw Empty(jid.toStdString());
}
} else {
std::string stdId(id.toStdString());
try {
Shared::Message msg = getMessage(stdId, txn);
quint64 stamp = msg.getTime().toMSecsSinceEpoch();
lmdbKey.mv_data = (quint8*)&stamp;
lmdbKey.mv_size = 8;
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET);
if (rc) {
qDebug() << "Error getting before: couldn't set " << mdb_strerror(rc);
throw NotFound(stdId, jid.toStdString());
} else {
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV);
if (rc) {
qDebug() << "Error getting before, couldn't prev " << mdb_strerror(rc);
throw NotFound(stdId, jid.toStdString());
}
}
} catch (...) {
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw;
}
}
do {
MDB_val dKey, dData;
dKey.mv_size = lmdbData.mv_size;
dKey.mv_data = lmdbData.mv_data;
rc = mdb_get(txn, main, &dKey, &dData);
if (rc) {
qDebug() <<"Get error: " << mdb_strerror(rc);
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw NotFound(sId, jid.toStdString());
} else {
QByteArray ba((char*)dData.mv_data, dData.mv_size);
QDataStream ds(&ba, QIODevice::ReadOnly);
res.emplace_back();
Shared::Message& msg = res.back();
msg.deserialize(ds);
}
--count;
} while (count > 0 && mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV) == 0);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
return res;
}
bool Core::Archive::isFromTheBeginning()
{
if (!opened) {
throw Closed("isFromTheBeginning", jid.toStdString());
}
return fromTheBeginning;
}
void Core::Archive::setFromTheBeginning(bool is)
{
if (!opened) {
throw Closed("setFromTheBeginning", jid.toStdString());
}
if (fromTheBeginning != is) {
fromTheBeginning = is;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
bool success = setStatValue("beginning", is, txn);
if (success != 0) {
mdb_txn_abort(txn);
} else {
mdb_txn_commit(txn);
}
}
}
QString Core::Archive::idByStanzaId(const QString& stanzaId) const
{
if (!opened) {
throw Closed("idByStanzaId", jid.toStdString());
}
QString id;
std::string ssid = stanzaId.toStdString();
MDB_txn *txn;
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = ssid.size();
lmdbKey.mv_data = (char*)ssid.c_str();
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
int rc = mdb_get(txn, sid, &lmdbKey, &lmdbData);
if (rc == 0) {
id = QString::fromStdString(std::string((char*)lmdbData.mv_data, lmdbData.mv_size));
}
mdb_txn_abort(txn);
return id;
}
QString Core::Archive::stanzaIdById(const QString& id) const
{
if (!opened) {
throw Closed("stanzaIdById", jid.toStdString());
}
try {
Shared::Message msg = getElement(id);
return msg.getStanzaId();
} catch (const NotFound& e) {
return QString();
} catch (const Empty& e) {
return QString();
} catch (...) {
throw;
}
}
void Core::Archive::printOrder()
{
qDebug() << "Printing order";
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_cursor* cursor;
mdb_cursor_open(txn, order, &cursor);
MDB_val lmdbKey, lmdbData;
mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
do {
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
qDebug() << QString(sId.c_str());
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
}
void Core::Archive::printKeys()
{
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_cursor* cursor;
mdb_cursor_open(txn, main, &cursor);
MDB_val lmdbKey, lmdbData;
mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
do {
std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
qDebug() << QString(sId.c_str());
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
}
bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc;
rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
if (rc == MDB_NOTFOUND) {
throw NotFound(id, jid.toStdString());
} else if (rc) {
std::string err(mdb_strerror(rc));
qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
throw Unknown(jid.toStdString(), err);
} else {
uint8_t value = *(uint8_t*)(lmdbData.mv_data);
bool is;
if (value == 144) {
is = false;
} else if (value == 72) {
is = true;
} else {
qDebug() << "error retrieving boolean stat" << id.c_str() << ": stored value doesn't match any magic number, the answer is most probably wrong";
throw NotFound(id, jid.toStdString());
}
return is;
}
}
std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* txn)
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc;
rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
if (rc == MDB_NOTFOUND) {
throw NotFound(id, jid.toStdString());
} else if (rc) {
std::string err(mdb_strerror(rc));
qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
throw Unknown(jid.toStdString(), err);
} else {
std::string value((char*)lmdbData.mv_data, lmdbData.mv_size);
return value;
}
}
bool Core::Archive::setStatValue(const std::string& id, bool value, MDB_txn* txn)
{
uint8_t binvalue = 144;
if (value) {
binvalue = 72;
}
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = sizeof binvalue;
lmdbData.mv_data = &binvalue;
int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc);
return false;
}
return true;
}
bool Core::Archive::setStatValue(const std::string& id, const std::string& value, MDB_txn* txn)
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = value.size();
lmdbData.mv_data = (char*)value.c_str();
int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc);
return false;
}
return true;
}
bool Core::Archive::dropAvatar(const std::string& resource)
{
MDB_txn *txn;
MDB_val lmdbKey;
mdb_txn_begin(environment, NULL, 0, &txn);
lmdbKey.mv_size = resource.size();
lmdbKey.mv_data = (char*)resource.c_str();
int rc = mdb_del(txn, avatars, &lmdbKey, NULL);
if (rc != 0) {
mdb_txn_abort(txn);
return false;
} else {
mdb_txn_commit(txn);
return true;
}
}
bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource)
{
if (!opened) {
throw Closed("setAvatar", jid.toStdString());
}
AvatarInfo oldInfo;
bool hasAvatar = readAvatarInfo(oldInfo, resource);
std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString();
if (data.size() == 0) {
if (!hasAvatar) {
return false;
} else {
return dropAvatar(res);
}
} else {
const char* cep;
mdb_env_get_path(environment, &cep);
QString currentPath(cep);
bool needToRemoveOld = false;
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(data);
QByteArray newHash(hash.result());
if (hasAvatar) {
if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash) {
return false;
}
QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type);
if (oldAvatar.exists()) {
if (oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak")) {
needToRemoveOld = true;
} else {
qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
return false;
}
}
}
QMimeDatabase db;
QMimeType type = db.mimeTypeForData(data);
QString ext = type.preferredSuffix();
QFile newAvatar(currentPath + "/" + res.c_str() + "." + ext);
if (newAvatar.open(QFile::WriteOnly)) {
newAvatar.write(data);
newAvatar.close();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
MDB_val lmdbKey, lmdbData;
QByteArray value;
newInfo.type = ext;
newInfo.hash = newHash;
newInfo.autogenerated = generated;
newInfo.serialize(&value);
lmdbKey.mv_size = res.size();
lmdbKey.mv_data = (char*)res.c_str();
lmdbData.mv_size = value.size();
lmdbData.mv_data = value.data();
int rc = mdb_put(txn, avatars, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
}
mdb_txn_abort(txn);
return false;
} else {
mdb_txn_commit(txn);
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
oldAvatar.remove();
}
return true;
}
} else {
qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
}
return false;
}
}
}
bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const
{
if (!opened) {
throw Closed("readAvatarInfo", jid.toStdString());
}
std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
try {
bool success = readAvatarInfo(target, res, txn);
mdb_txn_abort(txn);
return success;
} catch (...) {
mdb_txn_abort(txn);
throw;
}
}
bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const std::string& res, MDB_txn* txn) const
{
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = res.size();
lmdbKey.mv_data = (char*)res.c_str();
int rc;
rc = mdb_get(txn, avatars, &lmdbKey, &lmdbData);
if (rc == MDB_NOTFOUND) {
return false;
} else if (rc) {
std::string err(mdb_strerror(rc));
qDebug() << "error reading avatar info for" << res.c_str() << "resource of" << jid << err.c_str();
throw Unknown(jid.toStdString(), err);
} else {
target.deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
return true;
}
}
void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const
{
if (!opened) {
throw Closed("readAllResourcesAvatars", jid.toStdString());
}
int rc;
MDB_val lmdbKey, lmdbData;
MDB_txn *txn;
MDB_cursor* cursor;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
mdb_cursor_open(txn, avatars, &cursor);
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
if (rc == 0) { //the db might be empty yet
do {
std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
QString res(sId.c_str());
if (res != jid) {
data.emplace(res, AvatarInfo());
data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
}
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
}
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
}
Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const
{
if (!opened) {
throw Closed("readAvatarInfo", jid.toStdString());
}
AvatarInfo info;
bool success = readAvatarInfo(info, resource);
if (success) {
return info;
} else {
throw NoAvatar(jid.toStdString(), resource.toStdString());
}
}
Core::Archive::AvatarInfo::AvatarInfo():
type(),
hash(),
autogenerated(false) {}
Core::Archive::AvatarInfo::AvatarInfo(const QString& p_type, const QByteArray& p_hash, bool p_autogenerated):
type(p_type),
hash(p_hash),
autogenerated(p_autogenerated) {}
void Core::Archive::AvatarInfo::deserialize(char* pointer, uint32_t size)
{
QByteArray data = QByteArray::fromRawData(pointer, size);
QDataStream in(&data, QIODevice::ReadOnly);
in >> type >> hash >> autogenerated;
}
void Core::Archive::AvatarInfo::serialize(QByteArray* ba) const
{
QDataStream out(ba, QIODevice::WriteOnly);
out << type << hash << autogenerated;
}

View File

@ -1,197 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_ARCHIVE_H
#define CORE_ARCHIVE_H
#include <QObject>
#include <QCryptographicHash>
#include <QMimeDatabase>
#include <QMimeType>
#include "shared/message.h"
#include "exception.h"
#include <lmdb.h>
#include <list>
namespace Core {
class Archive : public QObject
{
Q_OBJECT
public:
class AvatarInfo;
Archive(const QString& jid, QObject* parent = 0);
~Archive();
void open(const QString& account);
void close();
bool addElement(const Shared::Message& message);
unsigned int addElements(const std::list<Shared::Message>& messages);
Shared::Message getElement(const QString& id) const;
bool hasElement(const QString& id) const;
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
Shared::Message oldest();
QString oldestId();
Shared::Message newest();
QString newestId();
void clear();
long unsigned int size() const;
std::list<Shared::Message> getBefore(int count, const QString& id);
bool isFromTheBeginning();
void setFromTheBeginning(bool is);
bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
AvatarInfo getAvatarInfo(const QString& resource = "") const;
bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
QString idByStanzaId(const QString& stanzaId) const;
QString stanzaIdById(const QString& id) const;
public:
const QString jid;
public:
class Directory:
public Utils::Exception
{
public:
Directory(const std::string& p_path):Exception(), path(p_path){}
std::string getMessage() const{return "Can't create directory for database at " + path;}
private:
std::string path;
};
class Closed:
public Utils::Exception
{
public:
Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){}
std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;}
private:
std::string operation;
std::string account;
};
class NotFound:
public Utils::Exception
{
public:
NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){}
std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;}
private:
std::string key;
std::string account;
};
class Empty:
public Utils::Exception
{
public:
Empty(const std::string& acc):Exception(), account(acc){}
std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";}
private:
std::string account;
};
class Exist:
public Utils::Exception
{
public:
Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){}
std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";}
private:
std::string account;
std::string key;
};
class NoAvatar:
public Utils::Exception
{
public:
NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){
if (resource.size() == 0) {
resource = "for himself";
}
}
std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;}
private:
std::string element;
std::string resource;
};
class Unknown:
public Utils::Exception
{
public:
Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){}
std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;}
private:
std::string account;
std::string msg;
};
class AvatarInfo {
public:
AvatarInfo();
AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
void deserialize(char* pointer, uint32_t size);
void serialize(QByteArray* ba) const;
QString type;
QByteArray hash;
bool autogenerated;
};
private:
bool opened;
bool fromTheBeginning;
MDB_env* environment;
MDB_dbi main; //id to message
MDB_dbi order; //time to id
MDB_dbi stats;
MDB_dbi avatars;
MDB_dbi sid; //stanzaId to id
bool getStatBoolValue(const std::string& id, MDB_txn* txn);
std::string getStatStringValue(const std::string& id, MDB_txn* txn);
bool setStatValue(const std::string& id, bool value, MDB_txn* txn);
bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
bool readAvatarInfo(AvatarInfo& target, const std::string& res, MDB_txn* txn) const;
void printOrder();
void printKeys();
bool dropAvatar(const std::string& resource);
Shared::Message getMessage(const std::string& id, MDB_txn* txn) const;
Shared::Message getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc);
Shared::Message edge(bool end);
};
}
#endif // CORE_ARCHIVE_H

View File

@ -0,0 +1,15 @@
set(SOURCE_FILES
networkaccess.cpp
clientcache.cpp
urlstorage.cpp
archive.cpp
)
set(HEADER_FILES
networkaccess.h
clientcache.h
urlstorage.h
archive.h
)
target_sources(squawk PRIVATE ${SOURCE_FILES})

419
core/components/archive.cpp Normal file
View File

@ -0,0 +1,419 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "archive.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <QDebug>
Core::Archive::Archive(const QString& account, const QString& p_jid, QObject* parent):
QObject(parent),
jid(p_jid),
account(account),
opened(false),
db(account + "/" + jid),
messages(db.addStorage<QString, Shared::Message>("messages")),
order(db.addStorage<uint64_t, QString>("order", true)),
stats(db.addStorage<QString, QVariant>("stats")),
avatars(db.addStorage<QString, AvatarInfo>("avatars")),
stanzaIdToId(db.addStorage<QString, QString>("stanzaIdToId")),
cursor(order->createCursor())
{}
Core::Archive::~Archive() {
close();
}
void Core::Archive::open() {
db.open();
LMDBAL::WriteTransaction txn = db.beginTransaction();
AvatarInfo info;
bool hasAvatar = false;
try {
avatars->getRecord(jid, info, txn);
hasAvatar = true;
} catch (const LMDBAL::NotFound& e) {}
if (!hasAvatar)
return;
QFile ava(db.getPath() + "/" + jid + "." + info.type);
if (ava.exists())
return;
try {
avatars->removeRecord(jid, txn);
txn.commit();
} catch (const std::exception& e) {
qDebug() << e.what();
qDebug() << "error opening archive" << jid << "for account" << account
<< ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
}
}
void Core::Archive::close() {
db.close();
}
bool Core::Archive::addElement(const Shared::Message& message) {
QString id = message.getId();
qDebug() << "Adding message with id " << id;
try {
LMDBAL::WriteTransaction txn = db.beginTransaction();
messages->addRecord(id, message, txn);
order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
QString stanzaId = message.getStanzaId();
if (!stanzaId.isEmpty())
stanzaIdToId->addRecord(stanzaId, id, txn);
txn.commit();
return true;
} catch (const std::exception& e) {
qDebug() << "Could not add message with id " + id;
qDebug() << e.what();
}
return false;
}
void Core::Archive::clear() {
db.drop();
}
Shared::Message Core::Archive::getElement(const QString& id) const {
return messages->getRecord(id);
}
bool Core::Archive::hasElement(const QString& id) const {
return messages->checkRecord(id);
}
void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVariant>& data) {
LMDBAL::WriteTransaction txn = db.beginTransaction();
Shared::Message msg = messages->getRecord(id, txn);
bool hadStanzaId = !msg.getStanzaId().isEmpty();
QDateTime oTime = msg.getTime();
bool idChange = msg.change(data);
QString newId = msg.getId();
QDateTime nTime = msg.getTime();
bool orderChange = oTime != nTime;
if (idChange || orderChange) {
if (idChange)
messages->removeRecord(id, txn);
if (orderChange)
order->removeRecord(oTime.toMSecsSinceEpoch(), txn);
order->forceRecord(nTime.toMSecsSinceEpoch(), newId, txn);
}
QString sid = msg.getStanzaId();
if (!sid.isEmpty() && (idChange || !hadStanzaId))
stanzaIdToId->forceRecord(sid, newId, txn);
messages->forceRecord(newId, msg, txn);
txn.commit();
}
Shared::Message Core::Archive::newest() const {
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
try {
cursor.open(txn);
while (true) {
std::pair<uint64_t, QString> pair = cursor.prev();
Shared::Message msg = messages->getRecord(pair.second, txn);
if (msg.serverStored()) {
cursor.close();
return msg;
}
}
} catch (...) {
cursor.close();
throw;
}
}
QString Core::Archive::newestId() const {
Shared::Message msg = newest();
return msg.getId();
}
QString Core::Archive::oldestId() const {
Shared::Message msg = oldest();
return msg.getId();
}
Shared::Message Core::Archive::oldest() const {
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
try {
cursor.open(txn);
while (true) {
std::pair<uint64_t, QString> pair = cursor.next();
Shared::Message msg = messages->getRecord(pair.second, txn);
if (msg.serverStored()) {
cursor.close();
return msg;
}
}
} catch (...) {
cursor.close();
throw;
}
}
unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages) {
unsigned int success = 0;
LMDBAL::WriteTransaction txn = db.beginTransaction();
for (const Shared::Message& message : messages) {
QString id = message.getId();
bool added = false;
try {
Core::Archive::messages->addRecord(id, message, txn);
added = true;
} catch (const LMDBAL::Exist& e) {}
if (!added)
continue;
order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
QString sid = message.getStanzaId();
if (!sid.isEmpty())
stanzaIdToId->addRecord(sid, id, txn);
++success;
}
txn.commit();
return success;
}
long unsigned int Core::Archive::size() const {
return order->count();
}
std::list<Shared::Message> Core::Archive::getBefore(unsigned int count, const QString& id) {
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
std::list<Shared::Message> res;
try {
cursor.open(txn);
if (!id.isEmpty()) {
Shared::Message reference = messages->getRecord(id, txn);
uint64_t stamp = reference.getTime().toMSecsSinceEpoch();
cursor.set(stamp);
}
for (unsigned int i = 0; i < count; ++i) {
std::pair<uint64_t, QString> pair;
cursor.prev(pair.first, pair.second);
res.emplace_back();
Shared::Message& msg = res.back();
messages->getRecord(pair.second, msg, txn);
}
cursor.close();
return res;
} catch (const LMDBAL::NotFound& e) {
cursor.close();
if (res.empty())
throw e;
else
return res;
} catch (...) {
cursor.close();
throw;
}
}
bool Core::Archive::isFromTheBeginning() const {
try {
return stats->getRecord("fromTheBeginning").toBool();
} catch (const LMDBAL::NotFound& e) {
return false;
}
}
void Core::Archive::setFromTheBeginning(bool is) {
stats->forceRecord("fromTheBeginning", is);
}
Shared::EncryptionProtocol Core::Archive::encryption() const {
try {
return stats->getRecord("encryption").value<Shared::EncryptionProtocol>();
} catch (const LMDBAL::NotFound& e) {
return Shared::EncryptionProtocol::none;
}
}
bool Core::Archive::setEncryption(Shared::EncryptionProtocol is) {
LMDBAL::WriteTransaction txn = db.beginTransaction();
Shared::EncryptionProtocol current = Shared::EncryptionProtocol::none;
try {
current = stats->getRecord("encryption", txn).value<Shared::EncryptionProtocol>();
} catch (const LMDBAL::NotFound& e) {}
if (is != current) {
stats->forceRecord("encryption", static_cast<uint8_t>(is), txn);
txn.commit();
return true;
}
return false;
}
QString Core::Archive::idByStanzaId(const QString& stanzaId) const {
return stanzaIdToId->getRecord(stanzaId);
}
QString Core::Archive::stanzaIdById(const QString& id) const {
try {
Shared::Message msg = getElement(id);
return msg.getStanzaId();
} catch (const LMDBAL::NotFound& e) {
return QString();
}
}
bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource) {
LMDBAL::WriteTransaction txn = db.beginTransaction();
AvatarInfo oldInfo;
bool haveAvatar = false;
QString res = resource.isEmpty() ? jid : resource;
try {
avatars->getRecord(res, oldInfo, txn);
haveAvatar = true;
} catch (const LMDBAL::NotFound& e) {}
if (data.size() == 0) {
if (!haveAvatar)
return false;
avatars->removeRecord(res, txn);
txn.commit();
return true;
}
QString currentPath = db.getPath();
bool needToRemoveOld = false;
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(data);
QByteArray newHash(hash.result());
if (haveAvatar) {
if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash)
return false;
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type);
if (oldAvatar.exists()) {
if (oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type + ".bak")) {
needToRemoveOld = true;
} else {
qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
return false;
}
}
}
QMimeDatabase mimedb;
QMimeType type = mimedb.mimeTypeForData(data);
QString ext = type.preferredSuffix();
QFile newAvatar(currentPath + "/" + res + "." + ext);
if (!newAvatar.open(QFile::WriteOnly)) {
qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
}
return false;
}
newAvatar.write(data);
newAvatar.close();
newInfo.type = ext;
newInfo.hash = newHash;
newInfo.autogenerated = generated;
try {
avatars->forceRecord(res, newInfo, txn);
txn.commit();
} catch (...) {
qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
}
return false;
}
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
oldAvatar.remove();
}
return true;
}
bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const {
try {
avatars->getRecord(resource.isEmpty() ? jid : resource, target);
return true;
} catch (const LMDBAL::NotFound& e) {
return false;
}
}
void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const {
avatars->readAll(data);
}
Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const {
return avatars->getRecord(resource);
}
Core::Archive::AvatarInfo::AvatarInfo():
type(),
hash(),
autogenerated(false)
{}
Core::Archive::AvatarInfo::AvatarInfo(const QString& p_type, const QByteArray& p_hash, bool p_autogenerated):
type(p_type),
hash(p_hash),
autogenerated(p_autogenerated)
{}
QDataStream & operator<<(QDataStream& out, const Core::Archive::AvatarInfo& info) {
out << info.type;
out << info.hash;
out << info.autogenerated;
return out;
}
QDataStream & operator>>(QDataStream& in, Core::Archive::AvatarInfo& info) {
in >> info.type;
in >> info.hash;
in >> info.autogenerated;
return in;
}

101
core/components/archive.h Normal file
View File

@ -0,0 +1,101 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QCryptographicHash>
#include <QMimeDatabase>
#include <QMimeType>
#include <QDataStream>
#include "shared/enums.h"
#include "shared/message.h"
#include "shared/exception.h"
#include <list>
#include <base.h>
#include <storage.h>
#include <cursor.h>
namespace Core {
class Archive : public QObject {
Q_OBJECT
public:
class AvatarInfo;
Archive(const QString& account, const QString& jid, QObject* parent = 0);
~Archive();
void open();
void close();
bool addElement(const Shared::Message& message);
unsigned int addElements(const std::list<Shared::Message>& messages);
Shared::Message getElement(const QString& id) const;
bool hasElement(const QString& id) const;
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
Shared::Message oldest() const;
QString oldestId() const;
Shared::Message newest() const;
QString newestId() const;
void clear();
long unsigned int size() const;
std::list<Shared::Message> getBefore(unsigned int count, const QString& id);
bool isFromTheBeginning() const;
void setFromTheBeginning(bool is);
Shared::EncryptionProtocol encryption() const;
bool setEncryption(Shared::EncryptionProtocol value); //returns true if changed, false otherwise
bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
AvatarInfo getAvatarInfo(const QString& resource = "") const;
bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
QString idByStanzaId(const QString& stanzaId) const;
QString stanzaIdById(const QString& id) const;
public:
const QString jid;
const QString account;
public:
class AvatarInfo {
public:
AvatarInfo();
AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
QString type;
QByteArray hash;
bool autogenerated;
};
private:
bool opened;
LMDBAL::Base db;
LMDBAL::Storage<QString, Shared::Message>* messages;
LMDBAL::Storage<uint64_t, QString>* order;
LMDBAL::Storage<QString, QVariant>* stats;
LMDBAL::Storage<QString, AvatarInfo>* avatars;
LMDBAL::Storage<QString, QString>* stanzaIdToId;
mutable LMDBAL::Cursor<uint64_t, QString> cursor;
};
}
QDataStream& operator << (QDataStream &out, const Core::Archive::AvatarInfo& info);
QDataStream& operator >> (QDataStream &in, Core::Archive::AvatarInfo& info);

View File

@ -0,0 +1,77 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "clientcache.h"
#include <QDebug>
Core::ClientCache::ClientCache():
db("clients"),
cache(db.addCache<QString, Shared::ClientInfo>("info")),
requested(),
specific()
{
db.open();
}
Core::ClientCache::~ClientCache() {
db.close();
}
void Core::ClientCache::open() {
db.open();}
void Core::ClientCache::close() {
db.close();}
bool Core::ClientCache::checkClient(const Shared::ClientId& p_id) {
QString id = p_id.getId();
if (requested.count(id) == 0 && !cache->checkRecord(id)) {
requested.emplace(id, p_id);
emit requestClientInfo(id);
return false;
}
return true;
}
bool Core::ClientCache::registerClientInfo (
const QString& sourceFullJid,
const QString& id,
const std::set<Shared::Identity>& identities,
const std::set<QString>& features)
{
std::map<QString, Shared::ClientInfo>::iterator itr = requested.find(id);
if (itr != requested.end()) {
Shared::ClientInfo& info = itr->second;
info.identities = identities;
info.extensions = features;
bool valid = info.valid();
if (valid) {
cache->addRecord(id, info);
} else {
info.specificPresence = sourceFullJid;
specific.insert(std::make_pair(sourceFullJid, info));
}
requested.erase(id);
return valid;
} else {
return false;
}
}

View File

@ -0,0 +1,63 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <map>
#include <set>
#include <QObject>
#include <QString>
#include <cache.h>
#include <shared/clientid.h>
#include <shared/clientinfo.h>
#include <shared/identity.h>
namespace Core {
class ClientCache : public QObject {
Q_OBJECT
public:
ClientCache();
~ClientCache();
void open();
void close();
signals:
void requestClientInfo(const QString& id);
public slots:
bool checkClient(const Shared::ClientId& id);
bool registerClientInfo(
const QString& sourceFullJid,
const QString& id,
const std::set<Shared::Identity>& identities,
const std::set<QString>& features
);
private:
LMDBAL::Base db;
LMDBAL::Cache<QString, Shared::ClientInfo>* cache;
std::map<QString, Shared::ClientInfo> requested;
std::map<QString, Shared::ClientInfo> specific;
};
}

View File

@ -16,94 +16,69 @@
* 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(),
currentPath()
{ {
QSettings settings;
currentPath = settings.value("downloadsPath").toString();
} }
Core::NetworkAccess::~NetworkAccess() 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) {
if (info.exists() && info.isFile()) { QFileInfo info(p.first);
emit fileLocalPathResponse(messageId, path); if (info.exists() && info.isFile())
emit downloadFileComplete(p.second, p.first);
else
startDownload(p.second, url);
} else { } else {
files.removeRecord(url); startDownload(p.second, url);
emit fileLocalPathResponse(messageId, "");
} }
} catch (const Archive::NotFound& e) { } catch (const LMDBAL::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";
} catch (const Archive::Unknown& e) { storage.addFile(url);
startDownload(std::list<Shared::MessageInfo>(), url);
} catch (const LMDBAL::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) void Core::NetworkAccess::start() {
{
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());
}
}
}
void Core::NetworkAccess::start()
{
if (!running) { if (!running) {
manager = new QNetworkAccessManager(); manager = new QNetworkAccessManager();
files.open(); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
manager->setTransferTimeout();
#endif
storage.open();
running = true; running = true;
} }
} }
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;
@ -115,8 +90,7 @@ void Core::NetworkAccess::stop()
} }
} }
void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
{
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender()); 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);
@ -124,37 +98,54 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping"; qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
} else { } else {
Transfer* dwn = itr->second; Transfer* dwn = itr->second;
qreal received = bytesReceived; if (dwn->success) {
qreal total = bytesTotal; qreal received = bytesReceived;
qreal progress = received/total; qreal total = bytesTotal;
dwn->progress = progress; qreal progress = received/total;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { dwn->progress = progress;
emit downloadFileProgress(*mItr, progress); emit loadFileProgress(dwn->messages, progress, false);
} }
} }
} }
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) {
{ qDebug() << "DEBUG: DOWNLOAD ERROR";
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender()); QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
qDebug() << rpl->errorString();
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 reporting an error but seems like no one is waiting for it, skipping"; qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
} else { } 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* 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); //}
}
}
} }
} }
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors) {
{ qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
for (const QSslError& err : errors)
qDebug() << err.errorString();
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
if (itr == downloads.end()) {
qDebug() << "an SSL error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
} else {
//if (errorText.size() > 0) {
itr->second->success = false;
Transfer* dwn = itr->second;
emit loadFileError(dwn->messages, "SSL errors occured", false);
//}
}
}
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) {
QString errorText(""); QString errorText("");
switch (code) { switch (code) {
case QNetworkReply::NoError: case QNetworkReply::NoError:
@ -175,7 +166,11 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
errorText = "Connection was closed because it timed out"; errorText = "Connection was closed because it timed out";
break; break;
case QNetworkReply::OperationCanceledError: case QNetworkReply::OperationCanceledError:
//this means I closed it myself by abort() or close(), don't think I need to notify here //this means I closed it myself by abort() or close()
//I don't call them directory, but this is the error code
//Qt returns when it can not resume donwload after the network failure
//or when the download is canceled by the timout;
errorText = "Connection lost";
break; break;
case QNetworkReply::SslHandshakeFailedError: case QNetworkReply::SslHandshakeFailedError:
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
@ -274,52 +269,48 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
} }
void Core::NetworkAccess::onDownloadFinished() void Core::NetworkAccess::onDownloadFinished() {
{ qDebug() << "DEBUG: DOWNLOAD FINISHED";
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;
QString err;
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(); else
QString realName = *sItr; qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) { QString path = prepareDirectory(jid);
suffix += "." + (*sItr); if (path.size() > 0) {
} path = checkFileName(fileName, path);
QString postfix("");
QFileInfo proposedName(path + realName + postfix + suffix); QFile file(Shared::resolvePath(path));
int counter = 0; if (file.open(QIODevice::WriteOnly)) {
while (proposedName.exists()) { file.write(dwn->reply->readAll());
postfix = QString("(") + std::to_string(++counter).c_str() + ")"; file.close();
proposedName = QFileInfo(path + realName + postfix + suffix); storage.setPath(url, path);
qDebug() << "file" << path << "was successfully downloaded";
} else {
qDebug() << "couldn't save file" << path;
err = "Error opening file to write:" + file.errorString();
}
} else {
err = "Couldn't prepare a directory for file";
} }
path = proposedName.absoluteFilePath(); if (path.size() > 0)
QFile file(path); emit downloadFileComplete(dwn->messages, path);
if (file.open(QIODevice::WriteOnly)) { else
file.write(dwn->reply->readAll()); emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
file.close();
files.addRecord(url, path);
qDebug() << "file" << path << "was successfully downloaded";
} else {
qDebug() << "couldn't save file" << path;
path = "";
}
}
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit fileLocalPathResponse(*mItr, path);
} }
dwn->reply->deleteLater(); dwn->reply->deleteLater();
@ -328,12 +319,12 @@ 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({msgs, 0, 0, true, "", url, 0});
Transfer* dwn = new Transfer({{messageId}, 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);
connect(dwn->reply, &QNetworkReply::sslErrors, this, &NetworkAccess::onDownloadSSLError);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError); connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
#else #else
@ -341,45 +332,61 @@ 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) {
{
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 = 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?
} }
} }
void Core::NetworkAccess::onUploadFinished() void Core::NetworkAccess::onUploadFinished() {
{
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 = 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);
// Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { if (upl->path.startsWith(QDir::tempPath() + QDir::separator() + QStringLiteral("squawk_img_attach_")) && upl->path.endsWith(".png")) {
emit fileLocalPathResponse(*mItr, upl->path); QString err = "";
emit uploadFileComplete(*mItr, upl->url); QString downloadDirPath = prepareDirectory(upl->messages.front().jid);
if (downloadDirPath.size() > 0) {
QString newPath = downloadDirPath + QDir::separator() + upl->path.mid(QDir::tempPath().length() + 1);
// Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath));
if (copyResult)
upl->path = newPath; // Change storage
else
err = "copying to " + newPath + " failed";
} else {
err = "Couldn't prepare a directory for file";
}
if (err.size() != 0)
qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
} }
storage.addFile(upl->messages, upl->url, upl->path);
emit uploadFileComplete(upl->messages, upl->url, upl->path);
} }
upl->reply->deleteLater(); upl->reply->deleteLater();
@ -390,8 +397,7 @@ void Core::NetworkAccess::onUploadFinished()
} }
} }
void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal) void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal) {
{
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender()); QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
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);
@ -399,98 +405,39 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping"; qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
} else { } else {
Transfer* upl = itr->second; Transfer* upl = itr->second;
qreal received = bytesReceived; if (upl->success) {
qreal total = bytesTotal; qreal received = bytesReceived;
qreal progress = received/total; qreal total = bytesTotal;
upl->progress = progress; qreal progress = received/total;
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { upl->progress = progress;
emit uploadFileProgress(*mItr, progress); emit loadFileProgress(upl->messages, progress, true);
} }
} }
} }
void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path) QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) {
{ QString p = Shared::squawkifyPath(path);
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
QNetworkRequest req(url); try {
p = storage.getUrl(p);
} catch (const LMDBAL::NotFound& err) {
p = "";
} catch (...) {
throw;
}
return p;
}
void Core::NetworkAccess::uploadFile(
const Shared::MessageInfo& info,
const QString& path,
const QUrl& put,
const QUrl& get,
const QMap<QString, QString> headers
) {
QFile* file = new QFile(path); QFile* file = new QFile(path);
if (file->open(QIODevice::ReadOnly)) { Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
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)
{
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
}
bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId)
{
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);
Transfer* upl = new Transfer({{messageId}, 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 +453,108 @@ 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 = currentPath;
QString addition;
if (jid.size() > 0) {
addition = jid;
path += QDir::separator() + jid;
}
QDir location(path);
if (!location.exists()) {
bool res = location.mkpath(path);
if (!res)
return "";
else
return "squawk://" + addition;
}
return "squawk://" + addition;
}
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path) {
QStringList parts = name.split(".");
QString suffix("");
QStringList::const_iterator sItr = parts.begin();
QString realName = *sItr;
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr)
suffix += "." + (*sItr);
QString postfix("");
QString resolvedPath = Shared::resolvePath(path);
QString count("");
QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix);
int counter = 0;
while (proposedName.exists()) {
count = QString("(") + std::to_string(++counter).c_str() + ")";
proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix);
}
return path + QDir::separator() + realName + count + suffix;
}
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id) {
return storage.addMessageAndCheckForPath(url, account, jid, id);
}
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path) {
return storage.deletedFile(path);
}
void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) {
QDir dir(currentPath);
bool success = true;
qDebug() << "moving" << currentPath << "to" << newPath;
for (QString fileName : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
if (!success)
qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
currentPath = newPath;
}

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CORE_NETWORKACCESS_H #pragma once
#define CORE_NETWORKACCESS_H
#include <QObject> #include <QObject>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
@ -26,18 +25,17 @@
#include <QFileInfo> #include <QFileInfo>
#include <QFile> #include <QFile>
#include <QStandardPaths> #include <QStandardPaths>
#include <QSettings>
#include <set> #include <set>
#include "storage.h" #include <shared/pathcheck.h>
#include "urlstorage.h"
namespace Core { namespace Core {
/** //TODO Need to describe how to get rid of records when file is no longer reachable;
* @todo write docs class NetworkAccess : public QObject {
*/
class NetworkAccess : public QObject
{
Q_OBJECT Q_OBJECT
struct Transfer; struct Transfer;
public: public:
@ -48,30 +46,33 @@ 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, const QString& path);
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);
void moveFilesDirectory(const QString& newPath);
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);
void onDownloadError(QNetworkReply::NetworkError code); void onDownloadError(QNetworkReply::NetworkError code);
void onDownloadSSLError(const QList<QSslError> &errors);
void onDownloadFinished(); void onDownloadFinished();
void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal); void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onUploadError(QNetworkReply::NetworkError code); void onUploadError(QNetworkReply::NetworkError code);
@ -80,12 +81,13 @@ 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;
QString currentPath;
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;
@ -96,5 +98,3 @@ private:
}; };
} }
#endif // CORE_NETWORKACCESS_H

View File

@ -0,0 +1,252 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include "urlstorage.h"
Core::UrlStorage::UrlStorage(const QString& p_name):
base(p_name),
urlToInfo(base.addStorage<QString, UrlInfo>("urlToInfo")),
pathToUrl(base.addStorage<QString, QString>("pathToUrl"))
{}
Core::UrlStorage::~UrlStorage() {
close();
}
void Core::UrlStorage::open() {
base.open();
}
void Core::UrlStorage::close() {
base.close();
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite) {
LMDBAL::WriteTransaction txn = base.beginTransaction();
writeInfo(key, info, txn, overwrite);
txn.commit();
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, const LMDBAL::WriteTransaction& txn, bool overwrite) {
if (overwrite)
urlToInfo->forceRecord(key, info, txn);
else
urlToInfo->addRecord(key, info, txn);
if (info.hasPath())
pathToUrl->forceRecord(info.getPath(), key, txn);
}
void Core::UrlStorage::addFile(const QString& url) {
addToInfo(url, "", "", "");
}
void Core::UrlStorage::addFile(const QString& url, const QString& path) {
addToInfo(url, "", "", "", path);
}
void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
addToInfo(url, account, jid, id);
}
void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
addToInfo(url, account, jid, id, path);
}
void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
UrlInfo info (path, msgs);
writeInfo(url, info, true);
}
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id){
return addToInfo(url, account, jid, id).getPath();
}
Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
const QString& url,
const QString& account,
const QString& jid,
const QString& id,
const QString& path
) {
UrlInfo info;
LMDBAL::WriteTransaction txn = base.beginTransaction();
try {
urlToInfo->getRecord(url, info, txn);
} catch (const LMDBAL::NotFound& e) {}
bool pathChange = false;
bool listChange = false;
if (path != "-s") {
if (info.getPath() != path) {
info.setPath(path);
pathChange = true;
}
}
if (account.size() > 0 && jid.size() > 0 && id.size() > 0)
listChange = info.addMessage(account, jid, id);
if (pathChange || listChange) {
writeInfo(url, info, txn, true);
txn.commit();
}
return info;
}
std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path) {
std::list<Shared::MessageInfo> list;
LMDBAL::WriteTransaction txn = base.beginTransaction();
UrlInfo info;
try {
urlToInfo->getRecord(url, info, txn);
info.getMessages(list);
} catch (const LMDBAL::NotFound& e) {}
info.setPath(path);
writeInfo(url, info, txn, true);
txn.commit();
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url) {
std::list<Shared::MessageInfo> list;
LMDBAL::WriteTransaction txn = base.beginTransaction();
UrlInfo info;
urlToInfo->getRecord(url, info, txn);
urlToInfo->removeRecord(url, txn);
info.getMessages(list);
if (info.hasPath())
pathToUrl->removeRecord(info.getPath(), txn);
txn.commit();
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path) {
std::list<Shared::MessageInfo> list;
LMDBAL::WriteTransaction txn = base.beginTransaction();
QString url = pathToUrl->getRecord(path, txn);
pathToUrl->removeRecord(path, txn);
UrlInfo info = urlToInfo->getRecord(url, txn);
info.getMessages(list);
info.setPath(QString());
urlToInfo->changeRecord(url, info, txn);
txn.commit();
return list;
}
QString Core::UrlStorage::getUrl(const QString& path) {
return pathToUrl->getRecord(path);
}
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url) {
UrlInfo info = urlToInfo->getRecord(url);
std::list<Shared::MessageInfo> container;
info.getMessages(container);
return std::make_pair(info.getPath(), container);
}
Core::UrlStorage::UrlInfo::UrlInfo():
localPath(),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
localPath(path),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
localPath(path),
messages(msgs) {}
Core::UrlStorage::UrlInfo::~UrlInfo() {}
bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id) {
for (const Shared::MessageInfo& info : messages) {
if (info.account == acc && info.jid == jid && info.messageId == id)
return false;
}
messages.emplace_back(acc, jid, id);
return true;
}
void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const {
data << localPath;
std::list<Shared::MessageInfo>::size_type size = messages.size();
data << quint32(size);
for (const Shared::MessageInfo& info : messages) {
data << info.account;
data << info.jid;
data << info.messageId;
}
}
QDataStream & operator << (QDataStream& in, const Core::UrlStorage::UrlInfo& info) {
info.serialize(in);
return in;
}
QDataStream & operator >> (QDataStream& out, Core::UrlStorage::UrlInfo& info) {
info.deserialize(out);
return out;
}
void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data) {
data >> localPath;
quint32 size;
data >> size;
for (quint32 i = 0; i < size; ++i) {
messages.emplace_back();
Shared::MessageInfo& info = messages.back();
data >> info.account;
data >> info.jid;
data >> info.messageId;
}
}
void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const {
for (const Shared::MessageInfo& info : messages)
container.emplace_back(info);
}
QString Core::UrlStorage::UrlInfo::getPath() const {
return localPath;
}
bool Core::UrlStorage::UrlInfo::hasPath() const {
return localPath.size() > 0;
}
void Core::UrlStorage::UrlInfo::setPath(const QString& path) {
localPath = path;
}

View File

@ -0,0 +1,94 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <QDataStream>
#include <list>
#include <storage.h>
#include <shared/messageinfo.h>
namespace Core {
class UrlStorage {
public:
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:
LMDBAL::Base base;
LMDBAL::Storage<QString, UrlInfo>* urlToInfo;
LMDBAL::Storage<QString, QString>* pathToUrl;
private:
void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
void writeInfo(const QString& key, const UrlInfo& info, const LMDBAL::WriteTransaction& txn, bool overwrite = false);
UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
public:
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;
};
};
}
QDataStream& operator >> (QDataStream &in, Core::UrlStorage::UrlInfo& info);
QDataStream& operator << (QDataStream &out, const Core::UrlStorage::UrlInfo& info);

View File

@ -42,57 +42,48 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError); connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError);
room->setNickName(nick); room->setNickName(nick);
if (autoJoin) { if (autoJoin)
room->join(); room->join();
}
archive->readAllResourcesAvatars(exParticipants); archive->readAllResourcesAvatars(exParticipants);
} }
Core::Conference::~Conference() Core::Conference::~Conference(){
{ if (joined)
if (joined) {
room->leave(); room->leave();
}
room->deleteLater(); room->deleteLater();
} }
QString Core::Conference::getNick() const QString Core::Conference::getNick() const {
{
return nick; return nick;
} }
bool Core::Conference::getAutoJoin() bool Core::Conference::getAutoJoin() const {
{
return autoJoin; return autoJoin;
} }
bool Core::Conference::getJoined() const bool Core::Conference::getJoined() const {
{
return joined; return joined;
} }
void Core::Conference::setJoined(bool p_joined) void Core::Conference::setJoined(bool p_joined) {
{
if (joined != p_joined) { if (joined != p_joined) {
if (p_joined) { if (p_joined)
room->join(); room->join();
} else { else
room->leave(); room->leave();
}
} }
} }
void Core::Conference::setAutoJoin(bool p_autoJoin) void Core::Conference::setAutoJoin(bool p_autoJoin) {
{
if (autoJoin != p_autoJoin) { if (autoJoin != p_autoJoin) {
autoJoin = p_autoJoin; autoJoin = p_autoJoin;
emit autoJoinChanged(autoJoin); emit autoJoinChanged(autoJoin);
} }
} }
void Core::Conference::setNick(const QString& p_nick) void Core::Conference::setNick(const QString& p_nick) {
{
if (nick != p_nick) { if (nick != p_nick) {
if (joined) { if (joined) {
room->setNickName(p_nick); room->setNickName(p_nick);
@ -103,105 +94,99 @@ void Core::Conference::setNick(const QString& p_nick)
} }
} }
void Core::Conference::onRoomJoined() void Core::Conference::onRoomJoined() {
{
joined = true; joined = true;
emit joinedChanged(joined); emit joinedChanged(joined);
} }
void Core::Conference::onRoomLeft() void Core::Conference::onRoomLeft() {
{
joined = false; joined = false;
emit joinedChanged(joined); emit joinedChanged(joined);
} }
void Core::Conference::onRoomNameChanged(const QString& p_name) void Core::Conference::onRoomNameChanged(const QString& p_name) {
{
setName(p_name); setName(p_name);
} }
void Core::Conference::onRoomNickNameChanged(const QString& p_nick) void Core::Conference::onRoomNickNameChanged(const QString& p_nick) {
{
if (p_nick != nick) { if (p_nick != nick) {
nick = p_nick; nick = p_nick;
emit nickChanged(nick); emit nickChanged(nick);
} }
} }
void Core::Conference::onRoomError(const QXmppStanza::Error& err) void Core::Conference::onRoomError(const QXmppStanza::Error& err) {
{
qDebug() << "MUC" << jid << "error:" << err.text(); qDebug() << "MUC" << jid << "error:" << err.text();
} }
void Core::Conference::onRoomParticipantAdded(const QString& p_name) void Core::Conference::onRoomParticipantAdded(const QString& p_name) {
{
QStringList comps = p_name.split("/"); QStringList comps = p_name.split("/");
QString resource = comps.back(); QString resource = comps.back();
QXmppPresence pres = room->participantPresence(p_name); QXmppPresence pres = room->participantPresence(p_name);
QXmppMucItem mi = pres.mucItem(); QXmppMucItem mi = pres.mucItem();
if (resource == jid) { if (resource == jid)
resource = ""; resource = "";
}
std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource); std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource);
bool hasAvatar = itr != exParticipants.end(); bool hasAvatar = itr != exParticipants.end();
if (resource.size() > 0) { if (resource.size() > 0) {
QDateTime lastInteraction = pres.lastUserInteraction(); QDateTime lastInteraction = pres.lastUserInteraction();
if (!lastInteraction.isValid()) { if (!lastInteraction.isValid())
lastInteraction = QDateTime::currentDateTimeUtc(); lastInteraction = QDateTime::currentDateTimeUtc();
}
QMap<QString, QVariant> cData = { QMap<QString, QVariant> cData = {
{"lastActivity", lastInteraction}, {"lastActivity", lastInteraction},
{"availability", pres.availableStatusType()}, {"availability", pres.availableStatusType()},
{"status", pres.statusText()}, {"status", pres.statusText()},
{"affiliation", mi.affiliation()}, {"affiliation", mi.affiliation()},
{"role", mi.role()} {"role", mi.role()},
}; {"client", QVariant::fromValue(
Shared::ClientId(
if (hasAvatar) { pres.capabilityNode(),
if (itr->second.autogenerated) { pres.capabilityVer().toBase64(),
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid)); pres.capabilityHash())
} else { )
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
} }
cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type); };
} else { careAboutAvatar(hasAvatar, itr->second, cData, resource, p_name);
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
cData.insert("avatarPath", "");
requestVCard(p_name);
}
emit addParticipant(resource, cData); emit addParticipant(resource, cData);
if (!hasAvatar) // because this way vCard is already requested, no need to handle possible avatar update
return;
} }
handlePossibleAvatarUpdate(pres, resource, hasAvatar, itr->second);
}
void Core::Conference::handlePossibleAvatarUpdate (
const QXmppPresence& pres,
const QString& resource,
bool hasAvatar,
const Archive::AvatarInfo& info
) {
switch (pres.vCardUpdateType()) { switch (pres.vCardUpdateType()) {
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
break; break;
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
break; break;
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
if (!hasAvatar || !itr->second.autogenerated) { if (!hasAvatar || !info.autogenerated)
setAutoGeneratedAvatar(resource); setAutoGeneratedAvatar(resource);
}
} break;
break; case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
if (hasAvatar) { if (hasAvatar) {
if (itr->second.autogenerated || itr->second.hash != pres.photoHash()) { if (info.autogenerated || info.hash != pres.photoHash())
emit requestVCard(p_name); emit requestVCard(pres.from());
}
} else { } else {
emit requestVCard(p_name); emit requestVCard(pres.from());
} }
break; break;
}
} }
} }
void Core::Conference::onRoomParticipantChanged(const QString& p_name) void Core::Conference::onRoomParticipantChanged(const QString& p_name) {
{
QStringList comps = p_name.split("/"); QStringList comps = p_name.split("/");
QString resource = comps.back(); QString resource = comps.back();
QXmppPresence pres = room->participantPresence(p_name); QXmppPresence pres = room->participantPresence(p_name);
@ -209,22 +194,27 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
handlePresence(pres); handlePresence(pres);
if (resource != jid) { if (resource != jid) {
QDateTime lastInteraction = pres.lastUserInteraction(); QDateTime lastInteraction = pres.lastUserInteraction();
if (!lastInteraction.isValid()) { if (!lastInteraction.isValid())
lastInteraction = QDateTime::currentDateTimeUtc(); lastInteraction = QDateTime::currentDateTimeUtc();
}
emit changeParticipant(resource, { emit changeParticipant(resource, {
{"lastActivity", lastInteraction}, {"lastActivity", lastInteraction},
{"availability", pres.availableStatusType()}, {"availability", pres.availableStatusType()},
{"status", pres.statusText()}, {"status", pres.statusText()},
{"affiliation", mi.affiliation()}, {"affiliation", mi.affiliation()},
{"role", mi.role()} {"role", mi.role()},
{"client", QVariant::fromValue(
Shared::ClientId(
pres.capabilityNode(),
pres.capabilityVer().toBase64(),
pres.capabilityHash())
)
}
}); });
} }
} }
void Core::Conference::onRoomParticipantRemoved(const QString& p_name) void Core::Conference::onRoomParticipantRemoved(const QString& p_name) {
{
QStringList comps = p_name.split("/"); QStringList comps = p_name.split("/");
QString resource = comps.back(); QString resource = comps.back();
if (resource == jid) { if (resource == jid) {
@ -234,69 +224,40 @@ void Core::Conference::onRoomParticipantRemoved(const QString& p_name)
} }
} }
QString Core::Conference::getSubject() const QString Core::Conference::getSubject() const {
{ if (joined)
if (joined) {
return room->subject(); return room->subject();
} else { else
return ""; return "";
}
} }
void Core::Conference::onRoomSubjectChanged(const QString& p_name) void Core::Conference::onRoomSubjectChanged(const QString& p_name) {
{
emit subjectChanged(p_name); emit subjectChanged(p_name);
} }
void Core::Conference::handlePresence(const QXmppPresence& pres) void Core::Conference::handlePresence(const QXmppPresence& pres) {
{
QString id = pres.from(); QString id = pres.from();
QStringList comps = id.split("/"); QStringList comps = id.split("/");
QString jid = comps.front(); QString jid = comps.front();
QString resource(""); QString resource("");
if (comps.size() > 1) { if (comps.size() > 1)
resource = comps.back(); resource = comps.back();
}
Archive::AvatarInfo info;
switch (pres.vCardUpdateType()) { bool hasAvatar = readAvatarInfo(info, resource);
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo handlePossibleAvatarUpdate(pres, resource, hasAvatar, info);
break;
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
break;
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info, resource);
if (!hasAvatar || !info.autogenerated) {
setAutoGeneratedAvatar(resource);
}
}
break;
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info, resource);
if (hasAvatar) {
if (info.autogenerated || info.hash != pres.photoHash()) {
emit requestVCard(id);
}
} else {
emit requestVCard(id);
}
break;
}
}
} }
bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) {
{
Archive::AvatarInfo newInfo; Archive::AvatarInfo newInfo;
bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource); bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource);
if (result && resource.size() != 0) { if (result && resource.size() != 0) {
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource); std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
if (itr == exParticipants.end()) { if (itr == exParticipants.end())
exParticipants.insert(std::make_pair(resource, newInfo)); exParticipants.insert(std::make_pair(resource, newInfo));
} else { else
itr->second = newInfo; itr->second = newInfo;
}
emit changeParticipant(resource, { emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)}, {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
{"avatarPath", avatarPath(resource) + "." + newInfo.type} {"avatarPath", avatarPath(resource) + "." + newInfo.type}
@ -306,17 +267,15 @@ bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
return result; return result;
} }
bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) {
{
bool result = RosterItem::setAvatar(data, info, resource); bool result = RosterItem::setAvatar(data, info, resource);
if (result && resource.size() != 0) { if (result && resource.size() != 0) {
if (data.size() > 0) { if (data.size() > 0) {
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource); std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
if (itr == exParticipants.end()) { if (itr == exParticipants.end())
exParticipants.insert(std::make_pair(resource, info)); exParticipants.insert(std::make_pair(resource, info));
} else { else
itr->second = info; itr->second = info;
}
emit changeParticipant(resource, { emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)}, {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
@ -324,9 +283,8 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
}); });
} else { } else {
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource); std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
if (itr != exParticipants.end()) { if (itr != exParticipants.end())
exParticipants.erase(itr); exParticipants.erase(itr);
}
emit changeParticipant(resource, { emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(Shared::Avatar::empty)}, {"avatarState", static_cast<uint>(Shared::Avatar::empty)},
@ -339,25 +297,31 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
return result; return result;
} }
Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource) void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource, Shared::VCard& out) {
{ RosterItem::handleResponseVCard(card, resource, out);
Shared::VCard result = RosterItem::handleResponseVCard(card, resource); if (resource.size() > 0)
if (resource.size() > 0) {
emit changeParticipant(resource, { emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(result.getAvatarType())}, {"avatarState", static_cast<uint>(out.getAvatarType())},
{"avatarPath", result.getAvatarPath()} {"avatarPath", out.getAvatarPath()}
}); });
} }
QMap<QString, QVariant> Core::Conference::getAllAvatars() const {
QMap<QString, QVariant> result;
for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants)
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
return result; return result;
} }
QMap<QString, QVariant> Core::Conference::getAllAvatars() const QMap<QString, QVariant> Core::Conference::getInfo() const {
{ QMap<QString, QVariant> data = RosterItem::getInfo();
QMap<QString, QVariant> result;
for (const std::pair<QString, Archive::AvatarInfo>& pair : exParticipants) { data.insert("autoJoin", getAutoJoin());
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type); data.insert("joined", getJoined());
} data.insert("nick", getNick());
return result; data.insert("avatars", getAllAvatars());
return data;
} }

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CORE_CONFERENCE_H #pragma once
#define CORE_CONFERENCE_H
#include <QDir> #include <QDir>
@ -26,16 +25,11 @@
#include <set> #include <set>
#include "rosteritem.h" #include "rosteritem.h"
#include "shared/global.h" #include <shared/global.h>
#include <shared/clientid.h>
namespace Core namespace Core {
{ class Conference : public RosterItem {
/**
* @todo write docs
*/
class Conference : public RosterItem
{
Q_OBJECT Q_OBJECT
public: public:
Conference(const QString& p_jid, const QString& p_account, bool p_autoJoin, const QString& p_name, const QString& p_nick, QXmppMucRoom* p_room); Conference(const QString& p_jid, const QString& p_account, bool p_autoJoin, const QString& p_name, const QString& p_nick, QXmppMucRoom* p_room);
@ -48,12 +42,13 @@ public:
bool getJoined() const; bool getJoined() const;
void setJoined(bool p_joined); void setJoined(bool p_joined);
bool getAutoJoin(); bool getAutoJoin() const;
void setAutoJoin(bool p_autoJoin); void setAutoJoin(bool p_autoJoin);
void handlePresence(const QXmppPresence & pres) override; void handlePresence(const QXmppPresence & pres) override;
bool setAutoGeneratedAvatar(const QString& resource = "") override; bool setAutoGeneratedAvatar(const QString& resource = "") override;
Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override; void handleResponseVCard(const QXmppVCardIq & card, const QString &resource, Shared::VCard& out) override;
QMap<QString, QVariant> getAllAvatars() const; QMap<QString, QVariant> getAllAvatars() const;
QMap<QString, QVariant> getInfo() const override;
signals: signals:
void nickChanged(const QString& nick); void nickChanged(const QString& nick);
@ -67,6 +62,14 @@ signals:
protected: protected:
bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override; bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override;
private:
void handlePossibleAvatarUpdate(
const QXmppPresence& pres,
const QString& resource,
bool hasAvatar,
const Archive::AvatarInfo& info
);
private: private:
QString nick; QString nick;
QXmppMucRoom* room; QXmppMucRoom* room;
@ -89,5 +92,3 @@ private slots:
}; };
} }
#endif // CORE_CONFERENCE_H

View File

@ -22,55 +22,49 @@
Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent): Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent):
RosterItem(pJid, account, parent), RosterItem(pJid, account, parent),
groups(), groups(),
subscriptionState(Shared::SubscriptionState::unknown) subscriptionState(Shared::SubscriptionState::unknown),
{ pep(Shared::Support::unknown)
}
Core::Contact::~Contact() #ifdef WITH_OMEMO
{ ,omemoBundles(Shared::Possible::unknown)
} #endif
{}
QSet<QString> Core::Contact::getGroups() const Core::Contact::~Contact() {}
{
QSet<QString> Core::Contact::getGroups() const {
return groups; return groups;
} }
unsigned int Core::Contact::groupsCount() const unsigned int Core::Contact::groupsCount() const {
{
return groups.size(); return groups.size();
} }
void Core::Contact::setGroups(const QSet<QString>& set) void Core::Contact::setGroups(const QSet<QString>& set) {
{
QSet<QString> toRemove = groups - set; QSet<QString> toRemove = groups - set;
QSet<QString> toAdd = set - groups; QSet<QString> toAdd = set - groups;
groups = set; groups = set;
for (QSet<QString>::iterator itr = toRemove.begin(), end = toRemove.end(); itr != end; ++itr) { for (const QString& group : toRemove)
emit groupRemoved(*itr); emit groupRemoved(group);
}
for (QSet<QString>::iterator itr = toAdd.begin(), end = toAdd.end(); itr != end; ++itr) { for (const QString& group : toAdd)
emit groupAdded(*itr); emit groupAdded(group);
}
} }
Shared::SubscriptionState Core::Contact::getSubscriptionState() const Shared::SubscriptionState Core::Contact::getSubscriptionState() const {
{
return subscriptionState; return subscriptionState;
} }
void Core::Contact::setSubscriptionState(Shared::SubscriptionState state) void Core::Contact::setSubscriptionState(Shared::SubscriptionState state) {
{
if (subscriptionState != state) { if (subscriptionState != state) {
subscriptionState = state; subscriptionState = state;
emit subscriptionStateChanged(subscriptionState); emit subscriptionStateChanged(subscriptionState);
} }
} }
void Core::Contact::handlePresence(const QXmppPresence& pres) void Core::Contact::handlePresence(const QXmppPresence& pres) {
{
switch (pres.vCardUpdateType()) { switch (pres.vCardUpdateType()) {
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
break; break;
@ -79,18 +73,17 @@ void Core::Contact::handlePresence(const QXmppPresence& pres)
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
Archive::AvatarInfo info; Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info); bool hasAvatar = readAvatarInfo(info);
if (!hasAvatar || !info.autogenerated) { if (!hasAvatar || !info.autogenerated)
setAutoGeneratedAvatar(); setAutoGeneratedAvatar();
}
} }
break; break;
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
Archive::AvatarInfo info; Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info); bool hasAvatar = readAvatarInfo(info);
if (hasAvatar) { if (hasAvatar) {
if (info.autogenerated || info.hash != pres.photoHash()) { if (info.autogenerated || info.hash != pres.photoHash())
emit requestVCard(jid); emit requestVCard(jid);
}
} else { } else {
emit requestVCard(jid); emit requestVCard(jid);
} }
@ -98,3 +91,23 @@ void Core::Contact::handlePresence(const QXmppPresence& pres)
} }
} }
} }
void Core::Contact::setPepSupport(Shared::Support support) {
if (pep != support)
pep = support;
}
Shared::Support Core::Contact::getPepSupport() const {
return pep;
}
QMap<QString, QVariant> Core::Contact::getInfo() const {
QMap<QString, QVariant> data = RosterItem::getInfo();
data.insert("state", QVariant::fromValue(subscriptionState));
return data;
}

View File

@ -16,17 +16,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CORE_CONTACT_H #pragma once
#define CORE_CONTACT_H
#include <QObject> #include <QObject>
#include <QSet> #include <QSet>
#include "rosteritem.h" #include "rosteritem.h"
#include <shared/enums.h>
namespace Core { namespace Core {
class Contact : public RosterItem class Contact : public RosterItem {
{
Q_OBJECT Q_OBJECT
public: public:
Contact(const QString& pJid, const QString& account, QObject* parent = 0); Contact(const QString& pJid, const QString& account, QObject* parent = 0);
@ -38,7 +39,11 @@ public:
void setSubscriptionState(Shared::SubscriptionState state); void setSubscriptionState(Shared::SubscriptionState state);
Shared::SubscriptionState getSubscriptionState() const; Shared::SubscriptionState getSubscriptionState() const;
void setPepSupport(Shared::Support support);
Shared::Support getPepSupport() const;
void handlePresence(const QXmppPresence & pres) override; void handlePresence(const QXmppPresence & pres) override;
QMap<QString, QVariant> getInfo() const override;
signals: signals:
void groupAdded(const QString& name); void groupAdded(const QString& name);
@ -48,7 +53,11 @@ signals:
private: private:
QSet<QString> groups; QSet<QString> groups;
Shared::SubscriptionState subscriptionState; Shared::SubscriptionState subscriptionState;
Shared::Support pep;
#ifdef WITH_OMEMO
public:
Shared::Possible omemoBundles;
#endif
}; };
} }
#endif // CORE_CONTACT_H

View File

@ -0,0 +1,26 @@
set(SOURCE_FILES
manager.cpp
job.cpp
cardinternal.cpp
infoforuser.cpp
owncardinternal.cpp
owninfoforuser.cpp
contact.cpp
info.cpp
)
set(HEADER_FILES
manager.h
job.h
cardinternal.h
infoforuser.h
owncardinternal.h
owninfoforuser.h
contact.h
info.h
)
target_sources(squawk PRIVATE
${SOURCE_FILES}
${HEADER_FILES}
)

View File

@ -0,0 +1,30 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cardinternal.h"
Core::DelayManager::CardInternal::CardInternal(Id p_id, const QString& p_jid) :
Job(p_id, Type::cardInternal),
Contact(p_id, p_jid, Type::cardInternal)
{}
Core::DelayManager::CardInternal::CardInternal(const CardInternal& other) :
Job(other),
Contact(other)
{}

View File

@ -0,0 +1,35 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include "contact.h"
namespace Core {
namespace DelayManager {
class CardInternal : public Contact {
public:
CardInternal(Id id, const QString& jid);
CardInternal(const CardInternal& other);
};
}
}

View File

@ -0,0 +1,28 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "contact.h"
Core::DelayManager::Contact::Contact(const Contact& other):
Job(other),
jid(other.jid) {}
Core::DelayManager::Contact::Contact(Id p_id, const QString& p_jid, Type p_type):
Job(p_id, p_type),
jid(p_jid) {}

View File

@ -0,0 +1,38 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include "job.h"
namespace Core {
namespace DelayManager {
class Contact : public virtual Job {
protected:
Contact(Id id, const QString& jid, Type type);
Contact(const Contact& other);
public:
const QString jid;
};
}
}

View File

@ -0,0 +1,61 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "info.h"
Core::DelayManager::Info::Info(Id p_id, Type p_type) :
Job(p_id, p_type),
stage(Stage::waitingForVCard),
info(nullptr)
{}
Core::DelayManager::Info::Info(const Info& other) :
Job(other),
stage(other.stage),
info(nullptr)
{}
Core::DelayManager::Info::~Info() {
if (stage == Stage::waitingForBundles) {
delete info;
}
}
Core::DelayManager::Info::Stage Core::DelayManager::Info::getStage() const {
return stage;
}
void Core::DelayManager::Info::receivedVCard(const Shared::VCard& card) {
if (stage != Stage::waitingForVCard)
throw 245;
info = new Shared::VCard(card);
#ifdef WITH_OMEMO
stage = Stage::waitingForBundles;
#endif
}
Shared::VCard * Core::DelayManager::Info::claim() {
if (stage != Stage::waitingForBundles)
throw 246;
Shared::VCard* res = info;
info = nullptr;
return res;
}

55
core/delayManager/info.h Normal file
View File

@ -0,0 +1,55 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "job.h"
#include <shared/vcard.h>
#include <shared/info.h>
namespace Core {
namespace DelayManager {
class Info : public virtual Job {
public:
enum class Stage {
waitingForVCard,
waitingForBundles,
finished
};
protected:
Info(Id id, Type type);
Info(const Info& other);
public:
~Info();
void receivedVCard(const Shared::VCard& card);
Shared::VCard* claim();
Stage getStage() const;
private:
Stage stage;
Shared::VCard* info;
};
}
}

View File

@ -0,0 +1,31 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "infoforuser.h"
Core::DelayManager::InfoForUser::InfoForUser(Id p_id, const QString& p_jid) :
Job(p_id, Type::infoForUser),
Contact(p_id, p_jid, Type::infoForUser),
Info(p_id, Type::infoForUser)
{}
Core::DelayManager::InfoForUser::InfoForUser(const InfoForUser& other) :
Job(other),
Contact(other),
Info(other)
{}

View File

@ -0,0 +1,34 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "contact.h"
#include "info.h"
namespace Core {
namespace DelayManager {
class InfoForUser : public Contact, public Info {
public:
InfoForUser(Id id, const QString& jid);
InfoForUser(const InfoForUser& other);
};
}
}

30
core/delayManager/job.cpp Normal file
View File

@ -0,0 +1,30 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "job.h"
Core::DelayManager::Job::Job(Id p_id, Type p_type) :
id (p_id),
type (p_type) {}
Core::DelayManager::Job::Job(const Job& other) :
id(other.id),
type(other.type) {}
Core::DelayManager::Job::~Job() {}

54
core/delayManager/job.h Normal file
View File

@ -0,0 +1,54 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
namespace Core {
namespace DelayManager {
class Job {
public:
typedef uint16_t Id;
enum class Type {
cardInternal,
ownCardInternal,
infoForUser,
ownInfoForUser
};
inline static constexpr const char * const TypeString[] = {
"cardInternal",
"ownCardInternal",
"infoForUser",
"ownInfoForUser"
};
protected:
Job(Id id, Type type);
Job(const Job& other);
public:
virtual ~Job();
const Id id;
const Type type;
};
}
}

View File

@ -0,0 +1,439 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "manager.h"
#include <QDebug>
#include "cardinternal.h"
#include "infoforuser.h"
#include "owncardinternal.h"
#include "owninfoforuser.h"
Core::DelayManager::Manager::Manager(const QString& poj, Job::Id mpj, QObject* parent) :
QObject(parent),
maxParallelJobs(mpj),
nextJobId(1),
scheduledJobs(),
scheduledJobsById(scheduledJobs.get<id>()),
jobSequence(scheduledJobs.get<sequence>()),
runningJobs(),
ownVCardJobId(0),
ownInfoJobId(0),
scheduledVCards(),
requestedVCards(),
#ifdef WITH_OMEMO
requestedBundles(),
#endif
ownJid(poj)
{}
Core::DelayManager::Manager::~Manager() {
for (const std::pair<const Job::Id, Job*>& pair : runningJobs)
delete pair.second;
for (Job* job : jobSequence)
delete job;
}
Core::DelayManager::Job::Id Core::DelayManager::Manager::getNextJobId() {
Job::Id id = nextJobId++;
if (id == 0)
id = nextJobId++;
return id;
}
void Core::DelayManager::Manager::getInfo(const QString& jid) {
if (jid == ownJid)
return getOwnInfo();
Job* job = nullptr;
#ifdef WITH_OMEMO
std::map<QString, Job::Id>::const_iterator bitr = requestedBundles.find(jid);
if (bitr != requestedBundles.end()) {
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(bitr->second);
if (itr == runningJobs.end())
throw JobNotFound(bitr->second);
job = itr->second;
}
else
#endif
job = getVCardJob(jid);
if (job != nullptr) {
if (job->type == Job::Type::cardInternal)
replaceJob(new InfoForUser(job->id, jid));
} else
scheduleJob(new InfoForUser(getNextJobId(), jid));
}
void Core::DelayManager::Manager::getOwnInfo() {
if (ownInfoJobId == 0) {
if (ownVCardJobId != 0)
replaceJob(new OwnInfoForUser(ownVCardJobId));
else
scheduleJob(new OwnInfoForUser(getNextJobId()));
}
}
void Core::DelayManager::Manager::getVCard(const QString& jid) {
Job* job = getVCardJob(jid);
if (job == nullptr)
scheduleJob(new CardInternal(getNextJobId(), jid));
}
void Core::DelayManager::Manager::getOwnVCard() {
if (ownVCardJobId == 0)
scheduleJob(new OwnCardInternal(getNextJobId()));
}
bool Core::DelayManager::Manager::isOwnVCardPending() const {
return ownVCardJobId != 0;
}
Core::DelayManager::Job* Core::DelayManager::Manager::getVCardJob(const QString& jid) {
Job* job = nullptr;
std::map<QString, Job::Id>::const_iterator sitr = scheduledVCards.find(jid);
if (sitr == scheduledVCards.end()) {
std::map<QString, Job::Id>::const_iterator ritr = requestedVCards.find(jid);
if (ritr != requestedVCards.end()) {
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(ritr->second);
if (itr == runningJobs.end())
throw JobNotFound(ritr->second, "getVCardJob:1");
job = itr->second;
}
} else {
StorageById::const_iterator itr = scheduledJobsById.find(sitr->second);
if (itr == scheduledJobsById.end())
throw JobNotFound(sitr->second, "getVCardJob:2");
job = *itr;
}
return job;
}
void Core::DelayManager::Manager::preScheduleJob(Job* job) {
switch (job->type) {
case Job::Type::cardInternal:
scheduledVCards.emplace(dynamic_cast<CardInternal*>(job)->jid, job->id);
break;
case Job::Type::ownCardInternal:
ownVCardJobId = job->id;
break;
case Job::Type::infoForUser:
scheduledVCards.emplace(dynamic_cast<InfoForUser*>(job)->jid, job->id);
break;
case Job::Type::ownInfoForUser:
ownVCardJobId = job->id;
ownInfoJobId = job->id;
break;
}
}
void Core::DelayManager::Manager::scheduleJob(Job* job) {
preScheduleJob(job);
if (runningJobs.size() < maxParallelJobs)
executeJob(job);
else
scheduledJobs.push_back(job);
}
void Core::DelayManager::Manager::preExecuteJob(Job* job) {
switch (job->type) {
case Job::Type::cardInternal:
case Job::Type::infoForUser: {
Contact* cij = dynamic_cast<Contact*>(job);
requestedVCards.emplace(cij->jid, job->id);
scheduledVCards.erase(cij->jid);
}
break;
case Job::Type::ownInfoForUser:
case Job::Type::ownCardInternal:
break;
}
}
void Core::DelayManager::Manager::executeJob(Job* job) {
preExecuteJob(job);
runningJobs.emplace(job->id, job);
switch (job->type) {
case Job::Type::cardInternal:
case Job::Type::infoForUser:
emit requestVCard(dynamic_cast<Contact*>(job)->jid);
break;
case Job::Type::ownInfoForUser:
case Job::Type::ownCardInternal:
emit requestOwnVCard();
break;
}
}
void Core::DelayManager::Manager::jobIsDone(Job::Id jobId) {
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
if (itr == runningJobs.end())
throw JobNotFound(jobId, "jobIsDone");
Job* job = itr->second;
delete job;
runningJobs.erase(itr);
if (scheduledJobs.size() > 0) {
Job* job = scheduledJobs.front();
scheduledJobs.pop_front();
executeJob(job);
}
}
void Core::DelayManager::Manager::replaceJob(Job* job) {
preScheduleJob(job);
std::map<Job::Id, Job*>::iterator itr = runningJobs.find(job->id);
if (itr != runningJobs.end()) {
preExecuteJob(job);
delete itr->second;
itr->second = job;
} else {
StorageById::iterator sitr = scheduledJobsById.find(job->id);
if (sitr != scheduledJobsById.end()) {
delete *(sitr);
scheduledJobsById.replace(sitr, job);
} else
throw JobNotFound(job->id, "replaceJob");
}
}
void Core::DelayManager::Manager::jobIsCanceled(Job* job, bool wasRunning) {
switch (job->type) {
case Job::Type::cardInternal: {
CardInternal* jb = dynamic_cast<CardInternal*>(job);
if (wasRunning)
requestedVCards.erase(jb->jid);
else
scheduledVCards.erase(jb->jid);
emit gotVCard(jb->jid, Shared::VCard());
}
break;
case Job::Type::infoForUser: {
InfoForUser* jb = dynamic_cast<InfoForUser*>(job);
switch (jb->getStage()) {
case InfoForUser::Stage::waitingForVCard:
if (wasRunning)
requestedVCards.erase(jb->jid);
else
scheduledVCards.erase(jb->jid);
emit gotVCard(jb->jid, Shared::VCard());
break;
case InfoForUser::Stage::waitingForBundles:
#ifdef WITH_OMEMO
requestedBundles.erase(jb->jid);
#endif
break;
default:
break;
}
emit gotInfo(Shared::Info(jb->jid));
}
break;
case Job::Type::ownInfoForUser: {
OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
if (jb->getStage() == OwnInfoForUser::Stage::waitingForVCard) {
ownVCardJobId = 0;
emit gotOwnVCard(Shared::VCard());
}
ownInfoJobId = 0;
emit gotOwnInfo(Shared::Info (ownJid));
}
break;
case Job::Type::ownCardInternal:
ownVCardJobId = 0;
emit gotOwnVCard(Shared::VCard());
break;
}
delete job;
}
void Core::DelayManager::Manager::disconnected() {
for (const std::pair<const Job::Id, Job*> pair : runningJobs)
jobIsCanceled(pair.second, true);
for (Job* job : scheduledJobs)
jobIsCanceled(job, false);
runningJobs.clear();
scheduledJobs.clear();
}
void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared::VCard& card) {
std::map<QString, Job::Id>::const_iterator cardItr = requestedVCards.find(jid);
if (cardItr == requestedVCards.end()) {
qDebug() << "received VCard for" << jid << "but it was never requested through manager, ignoring";
return;
}
Job::Id jobId = cardItr->second;
requestedVCards.erase(cardItr);
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
if (itr == runningJobs.end())
throw JobNotFound(jobId, "receivedVCard");
Job* job = itr->second;
switch (job->type) {
case Job::Type::cardInternal:
jobIsDone(jobId);
emit gotVCard(jid, card);
break;
case Job::Type::infoForUser: {
#ifdef WITH_OMEMO
requestedBundles.emplace(jid, jobId);
InfoForUser* jb = dynamic_cast<InfoForUser*>(job);
jb->receivedVCard(card);
emit requestBundles(jid);
#else
Shared::Info info(jid);
info.turnIntoContact(card);
emit gotInfo(info);
jobIsDone(jobId);
#endif
emit gotVCard(jid, card);
}
break;
default:
throw UnexpectedJobType(job->type, "receivedVCard");
}
}
void Core::DelayManager::Manager::receivedOwnVCard(const Shared::VCard& card) {
if (ownVCardJobId == 0) {
qDebug() << "received own VCard for" << ownJid << "but it was never requested through manager, ignoring";
return;
}
Job::Id jobId = ownVCardJobId;
ownVCardJobId = 0;
std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
if (itr == runningJobs.end())
throw JobNotFound(jobId, "receivedOwnVCard");
Job* job = itr->second;
switch (job->type) {
case Job::Type::ownCardInternal:
jobIsDone(jobId);
emit gotOwnVCard(card);
break;
case Job::Type::ownInfoForUser: {
#ifdef WITH_OMEMO
OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
jb->receivedVCard(card);
emit requestOwnBundles();
#else
Shared::Info info(ownJid);
info.turnIntoOwnAccount(card);
emit gotOwnInfo(info);
jobIsDone(jobId);
#endif
emit gotOwnVCard(card);
}
break;
default:
throw UnexpectedJobType(job->type, "receivedVCard");
}
}
#ifdef WITH_OMEMO
void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys) {
std::map<QString, Job::Id>::const_iterator itr = requestedBundles.find(jid);
if (itr == requestedBundles.end()) {
qDebug() << "received bundles for" << jid << "but they were never requested through manager, ignoring";
return;
}
Job::Id jobId = itr->second;
requestedBundles.erase(itr);
std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
if (jitr == runningJobs.end())
throw JobNotFound(jobId, "receivedBundles");
Job* jb = jitr->second;
InfoForUser* job = dynamic_cast<InfoForUser*>(jb);
Shared::Info info(jid);
info.turnIntoContact(job->claim(), new std::list<Shared::KeyInfo>(keys));
emit gotInfo(info);
jobIsDone(jobId);
}
void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::KeyInfo>& keys) {
if (ownInfoJobId == 0) {
qDebug() << "received own bundles for" << ownJid << "but they were never requested through manager, ignoring";
return;
}
Job::Id jobId = ownInfoJobId;
ownInfoJobId = 0;
std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
if (jitr == runningJobs.end())
throw JobNotFound(jobId, "receivedOwnBundles");
Job* jb = jitr->second;
OwnInfoForUser* job = dynamic_cast<OwnInfoForUser*>(jb);
Shared::Info info(ownJid);
info.turnIntoOwnAccount(job->claim(), new std::list<Shared::KeyInfo>(keys));
emit gotOwnInfo(info);
jobIsDone(jobId);
}
#endif
void Core::DelayManager::Manager::setOwnJid(const QString& jid) {
ownJid = jid;
}
Core::DelayManager::Manager::UnexpectedJobType::UnexpectedJobType(Job::Type p_type, const std::string& p_method):
Exception(),
type(p_type),
method(p_method)
{}
std::string Core::DelayManager::Manager::UnexpectedJobType::getMessage() const{
std::string msg("Unexpected job type: ");
msg += Job::TypeString[static_cast<int>(type)];
if (method.size() > 0)
msg += " in method " + method;
return msg;
}
Core::DelayManager::Manager::JobNotFound::JobNotFound(Job::Id p_id, const std::string& p_method) :
Exception(),
id(p_id),
method(p_method)
{}
std::string Core::DelayManager::Manager::JobNotFound::getMessage() const {
std::string msg("Job with id ");
msg += std::to_string(id);
msg += " was not found";
if (method.size() > 0)
msg += " in method " + method;
return msg;
}

153
core/delayManager/manager.h Normal file
View File

@ -0,0 +1,153 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <list>
#include <set>
#include <string>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/member.hpp>
#include <QObject>
#include <QString>
#include <shared/vcard.h>
#include <shared/info.h>
#include <shared/exception.h>
#include "job.h"
namespace Core {
namespace DelayManager {
class Manager : public QObject {
Q_OBJECT
public:
Manager(const QString& ownJid, Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
~Manager();
void setOwnJid(const QString& jid);
bool isOwnVCardPending() const;
public slots:
void getOwnVCard();
void getOwnInfo();
void getVCard(const QString& jid);
void getInfo(const QString& jid);
signals:
void requestVCard(const QString& jid);
void requestOwnVCard();
#ifdef WITH_OMEMO
void requestBundles(const QString& jid);
void requestOwnBundles();
#endif
void gotVCard(const QString& jid, const Shared::VCard& info);
void gotOwnVCard(const Shared::VCard& info);
void gotInfo(const Shared::Info& info);
void gotOwnInfo(const Shared::Info& info);
public slots:
void disconnected();
void receivedOwnVCard(const Shared::VCard& card);
void receivedVCard(const QString& jid, const Shared::VCard& card);
#ifdef WITH_OMEMO
void receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys);
void receivedOwnBundles(const std::list<Shared::KeyInfo>& keys);
#endif
private:
void preScheduleJob(Job* job);
void scheduleJob(Job* job);
void preExecuteJob(Job* job);
void executeJob(Job* job);
void jobIsCanceled(Job* job, bool wasRunning);
void jobIsDone(Job::Id jobId);
Job::Id getNextJobId();
void replaceJob(Job* job);
Job* getVCardJob(const QString& jid);
private:
struct id {};
struct sequence {};
typedef boost::multi_index_container<
Job*,
boost::multi_index::indexed_by<
boost::multi_index::sequenced<
boost::multi_index::tag<sequence>
>,
boost::multi_index::ordered_unique<
boost::multi_index::tag<id>,
boost::multi_index::member<
Job,
const Job::Id,
&Job::id
>
>
>
> Storage;
typedef Storage::index<id>::type StorageById;
typedef Storage::index<sequence>::type StorageSequence;
Job::Id maxParallelJobs;
Job::Id nextJobId;
Storage scheduledJobs;
StorageById& scheduledJobsById;
StorageSequence& jobSequence;
std::map<Job::Id, Job*> runningJobs;
Job::Id ownVCardJobId;
Job::Id ownInfoJobId;
std::map<QString, Job::Id> scheduledVCards;
std::map<QString, Job::Id> requestedVCards;
#ifdef WITH_OMEMO
std::map<QString, Job::Id> requestedBundles;
#endif
QString ownJid;
public:
class UnexpectedJobType: public Utils::Exception {
public:
UnexpectedJobType(Job::Type p_type, const std::string& p_method = "");
std::string getMessage() const override;
private:
Job::Type type;
std::string method;
};
class JobNotFound: public Utils::Exception {
public:
JobNotFound(Job::Id p_id, const std::string& p_method = "");
std::string getMessage() const override;
private:
Job::Id id;
std::string method;
};
};
}
}

View File

@ -0,0 +1,32 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "owncardinternal.h"
Core::DelayManager::OwnCardInternal::OwnCardInternal(Id p_id) :
Job(p_id, Type::ownCardInternal)
{}
Core::DelayManager::OwnCardInternal::OwnCardInternal(Id p_id, Type p_type) :
Job(p_id, p_type)
{}
Core::DelayManager::OwnCardInternal::OwnCardInternal(const OwnCardInternal& other) :
Job(other)
{}

View File

@ -0,0 +1,36 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "job.h"
namespace Core {
namespace DelayManager {
class OwnCardInternal : public Job {
protected:
OwnCardInternal(Id id, Type type);
public:
OwnCardInternal(Id id);
OwnCardInternal(const OwnCardInternal& other);
};
}
}

View File

@ -0,0 +1,29 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "owninfoforuser.h"
Core::DelayManager::OwnInfoForUser::OwnInfoForUser(Id p_id) :
Job(p_id, Type::ownInfoForUser),
Info(p_id, Type::ownInfoForUser)
{}
Core::DelayManager::OwnInfoForUser::OwnInfoForUser(const OwnInfoForUser& other) :
Job(other),
Info(other)
{}

View File

@ -0,0 +1,34 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "contact.h"
#include "info.h"
namespace Core {
namespace DelayManager {
class OwnInfoForUser : public Info {
public:
OwnInfoForUser(Id id);
OwnInfoForUser(const OwnInfoForUser& other);
};
}
}

View File

@ -0,0 +1,22 @@
set(SOURCE_FILES
messagehandler.cpp
rosterhandler.cpp
vcardhandler.cpp
discoveryhandler.cpp
trusthandler.cpp
)
set(HEADER_FILES
messagehandler.h
rosterhandler.h
vcardhandler.h
discoveryhandler.h
trusthandler.h
)
if(WITH_OMEMO)
list(APPEND SOURCE_FILES omemohandler.cpp)
list(APPEND HEADER_FILES omemohandler.h)
endif()
target_sources(squawk PRIVATE ${SOURCE_FILES})

View File

@ -0,0 +1,155 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "discoveryhandler.h"
#include "core/account.h"
#include <QDebug>
Core::DiscoveryHandler::DiscoveryHandler(Core::Account* account):
QObject(),
acc(account),
omemoToCarbonsConnected (false) {}
Core::DiscoveryHandler::~DiscoveryHandler() {}
void Core::DiscoveryHandler::initialize()
{
QObject::connect(acc->dm, &QXmppDiscoveryManager::itemsReceived, this, &DiscoveryHandler::onItemsReceived);
QObject::connect(acc->dm, &QXmppDiscoveryManager::infoReceived, this, &DiscoveryHandler::onInfoReceived);
acc->dm->setClientType("pc");
acc->dm->setClientCategory("client");
acc->dm->setClientName(qApp->applicationDisplayName() + " " + qApp->applicationVersion());
acc->dm->setClientCapabilitiesNode("https://git.macaw.me/blue/squawk");
}
void Core::DiscoveryHandler::onItemsReceived(const QXmppDiscoveryIq& items)
{
QString server = acc->getServer();
if (items.from() == server) {
std::set<QString> needToRequest;
qDebug() << "Server items list received for account " << acc->getName() << ":";
for (QXmppDiscoveryIq::Item item : items.items()) {
QString jid = item.jid();
if (jid != server) {
qDebug() << " Node" << jid;
needToRequest.insert(jid);
} else {
qDebug() << " " << item.node().toStdString().c_str();
}
}
for (const QString& jid : needToRequest) {
acc->dm->requestInfo(jid);
}
}
}
void Core::DiscoveryHandler::onInfoReceived(const QXmppDiscoveryIq& info)
{
QString from = info.from();
QString server = acc->getServer();
QString accName = acc->getName();
QString bareJid = acc->getBareJid();
if (from == server) {
bool enableCC = false;
qDebug() << "Server info received for account" << accName;
QStringList features = info.features();
qDebug() << "List of supported features of the server " << server << ":";
for (const QString& feature : features) {
qDebug() << " " << feature.toStdString().c_str();
if (feature == "urn:xmpp:carbons:2") {
enableCC = true;
}
}
if (enableCC) {
qDebug() << "Enabling carbon copies for account" << accName;
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
acc->cm->setCarbonsEnabled(true);
#endif
#ifdef WITH_OMEMO
if (!omemoToCarbonsConnected && acc->oh->hasOwnDevice()) {
// connect(this, &QXmppCarbonManager::messageSent, acc->om, &QXmppOmemoManager::handleMessage);
// connect(this, &QXmppCarbonManager::messageReceived, acc->om, &QXmppOmemoManager::handleMessage);
omemoToCarbonsConnected = true;
}
} else {
if (omemoToCarbonsConnected) {
// disconnect(this, &QXmppCarbonManager::messageSent, acc->om, &QXmppOmemoManager::handleMessage);
// disconnect(this, &QXmppCarbonManager::messageReceived, acc->om, &QXmppOmemoManager::handleMessage);
omemoToCarbonsConnected = false;
}
#endif
}
qDebug() << "Requesting account" << accName << "capabilities";
acc->dm->requestInfo(bareJid);
} else if (from == bareJid) {
qDebug() << "Received capabilities for account" << accName << ":";
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
bool pepSupported = false;
for (const QXmppDiscoveryIq::Identity& identity : identities) {
QString type = identity.type();
QString category = identity.category();
qDebug() << " " << category << type;
if (type == "pep" && category == "pubsub") {
pepSupported = true;
}
}
acc->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
} else {
QString node = info.queryNode();
if (!node.isEmpty()) {
qDebug() << "Received features and identities for account" << accName << "about" << from;
QStringList feats = info.features();
std::set<Shared::Identity> identities;
std::set<QString> features(feats.begin(), feats.end());
QList<QXmppDiscoveryIq::Identity> idents = info.identities();
for (const QXmppDiscoveryIq::Identity& ident : idents) {
Shared::Identity identity;
identity.category = ident.category();
identity.language = ident.language();
identity.name = ident.name();
identity.type = ident.type();
identities.insert(identity);
qDebug() << " " << identity.name << identity.category << identity.type;
}
for (const QString& feat : features) {
qDebug() << " " << feat;
}
emit acc->infoDiscovered(from, node, identities, features);
} else {
Contact* cont = acc->rh->getContact(from);
if (cont != nullptr) {
qDebug() << "Received info for account" << accName << "about contact" << from;
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
bool pepSupported = false;
for (const QXmppDiscoveryIq::Identity& identity : identities) {
QString type = identity.type();
QString category = identity.category();
qDebug() << " " << category << type;
if (type == "pep" && category == "pubsub") {
pepSupported = true;
}
}
cont->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
}
}
}
}

View File

@ -0,0 +1,48 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef CORE_DISCOVERYHANDLER_H
#define CORE_DISCOVERYHANDLER_H
#include <QObject>
#include <QXmppDiscoveryManager.h>
#include <QXmppDiscoveryIq.h>
namespace Core {
class Account;
class DiscoveryHandler : public QObject
{
Q_OBJECT
public:
DiscoveryHandler(Account* account);
~DiscoveryHandler();
void initialize();
private slots:
void onItemsReceived (const QXmppDiscoveryIq& items);
void onInfoReceived (const QXmppDiscoveryIq& info);
private:
Account* acc;
bool omemoToCarbonsConnected;
};
}
#endif // CORE_DISCOVERYHANDLER_H

View File

@ -19,180 +19,198 @@
#include "messagehandler.h" #include "messagehandler.h"
#include "core/account.h" #include "core/account.h"
static const QMap<QString, QVariant> statePending({{"state", static_cast<uint8_t>(Shared::Message::State::pending)}});
static const QMap<QString, QVariant> stateDelivered({{"state", static_cast<uint8_t>(Shared::Message::State::delivered)}});
static const QMap<QString, QVariant> stateSent({{"state", static_cast<uint8_t>(Shared::Message::State::sent)}});
Core::MessageHandler::MessageHandler(Core::Account* account): Core::MessageHandler::MessageHandler(Core::Account* account):
QObject(), QObject(),
acc(account), acc(account),
pendingStateMessages(), pendingStateMessages(),
pendingMessages(),
uploadingSlotsQueue() uploadingSlotsQueue()
{ {}
}
void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) {
{ #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
#ifdef WITH_OMEMO
switch (msg.encryptionMethod()) {
case QXmpp::NoEncryption:
break; //just do nothing
case QXmpp::UnknownEncryption:
qDebug() << "Account" << acc->getName() << "received a message with unknown encryption type";
break; //let it go the way it is, there is nothing I can do here
case QXmpp::Otr:
qDebug() << "Account" << acc->getName() << "received an OTR encrypted message, not supported yet";
break; //let it go the way it is, there is nothing I can do yet
case QXmpp::LegacyOpenPgp:
qDebug() << "Account" << acc->getName() << "received an LegacyOpenPgp encrypted message, not supported yet";
break; //let it go the way it is, there is nothing I can do yet
case QXmpp::Ox:
qDebug() << "Account" << acc->getName() << "received an Ox encrypted message, not supported yet";
break; //let it go the way it is, there is nothing I can do yet
case QXmpp::Omemo0:
qDebug() << "Account" << acc->getName() << "received an Omemo0 encrypted message, not supported yet";
break; //let it go the way it is, there is nothing I can do yet
case QXmpp::Omemo1:
qDebug() << "Account" << acc->getName() << "received an Omemo1 encrypted message, not supported yet";
break; //let it go the way it is, there is nothing I can do yet
case QXmpp::Omemo2:
break;
}
#endif
#endif
bool handled = false; bool handled = false;
switch (msg.type()) { switch (msg.type()) {
case QXmppMessage::Normal: case QXmppMessage::Normal:
qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping"; qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping";
break; break;
case QXmppMessage::Chat: case QXmppMessage::Chat:
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
handled = handleChatMessage(msg, false, msg.isCarbonForwarded(), true);
#else
handled = handleChatMessage(msg); handled = handleChatMessage(msg);
#endif
break; break;
case QXmppMessage::GroupChat: case QXmppMessage::GroupChat:
handled = handleGroupMessage(msg); handled = handleGroupMessage(msg);
break; break;
case QXmppMessage::Error: { case QXmppMessage::Error:
QString id = msg.id(); handled = handlePendingMessageError(msg.id(), msg.error().text());
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id); if (!handled)
if (itr != pendingStateMessages.end()) {
QString jid = itr->second;
RosterItem* cnt = acc->rh->getRosterItem(jid);
QMap<QString, QVariant> cData = {
{"state", static_cast<uint>(Shared::Message::State::error)},
{"errorText", msg.error().text()}
};
if (cnt != 0) {
cnt->changeMessage(id, cData);
}
;
emit acc->changeMessage(jid, id, cData);
pendingStateMessages.erase(itr);
handled = true;
} else {
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping"; qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
}
} break;
break;
case QXmppMessage::Headline: case QXmppMessage::Headline:
qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping"; qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
break; break;
} }
if (!handled) { if (!handled)
logMessage(msg); logMessage(msg);
}
} }
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) bool Core::MessageHandler::handlePendingMessageError(const QString& id, const QString& errorText) {
{ return adjustPendingMessage(id, {
{"state", static_cast<uint8_t>(Shared::Message::State::error)},
{"errorText", errorText}
}, true);
}
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
if (msg.body().isEmpty() && msg.outOfBandUrl().isEmpty())
return false;
Shared::Message sMsg(Shared::Message::chat);
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
QString jid = sMsg.getPenPalJid();
Contact* cnt = acc->rh->getContact(jid);
if (cnt == 0) {
cnt = acc->rh->addOutOfRosterContact(jid);
qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
}
if (sMsg.getOutgoing()) {
if (sMsg.getForwarded())
sMsg.setState(Shared::Message::State::sent);
} else {
sMsg.setState(Shared::Message::State::delivered);
}
QString oId = msg.replaceId();
if (oId.size() > 0) {
QMap<QString, QVariant> cData = {
{"body", sMsg.getBody()},
{"stamp", sMsg.getTime()}
};
cnt->correctMessageInArchive(oId, sMsg);
emit acc->changeMessage(jid, oId, cData);
} else {
cnt->appendMessageToArchive(sMsg);
emit acc->message(sMsg);
}
return true;
}
bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
const QString& body(msg.body()); const QString& body(msg.body());
if (body.size() != 0) { if (body.isEmpty())
Shared::Message sMsg(Shared::Message::chat); return false;
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
QString jid = sMsg.getPenPalJid(); Shared::Message sMsg(Shared::Message::groupChat);
Contact* cnt = acc->rh->getContact(jid); initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
if (cnt == 0) { QString jid = sMsg.getPenPalJid();
cnt = acc->rh->addOutOfRosterContact(jid); Conference* cnt = acc->rh->getConference(jid);
} if (cnt == 0)
if (outgoing) { return false;
if (forwarded) {
sMsg.setState(Shared::Message::State::sent); bool result = adjustPendingMessage(msg.id(), stateDelivered, true);
} if (result) //then it was an echo of my own sent message, nothing else needs to be done
} else { return result;
sMsg.setState(Shared::Message::State::delivered);
} QString oId = msg.replaceId();
QString oId = msg.replaceId(); if (oId.size() > 0) {
if (oId.size() > 0) { QMap<QString, QVariant> cData = {
QMap<QString, QVariant> cData = { {"body", sMsg.getBody()},
{"body", sMsg.getBody()}, {"stamp", sMsg.getTime()}
{"stamp", sMsg.getTime()} };
}; cnt->correctMessageInArchive(oId, sMsg);
cnt->correctMessageInArchive(oId, sMsg); emit acc->changeMessage(jid, oId, cData);
emit acc->changeMessage(jid, oId, cData); } else {
} else { cnt->appendMessageToArchive(sMsg);
cnt->appendMessageToArchive(sMsg); QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
if (sMsg.getTime() > minAgo) //otherwise it's considered a delayed delivery, most probably MUC history initial fetch
emit acc->message(sMsg); emit acc->message(sMsg);
}
return true;
} }
return false;
return true;
} }
bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const {
{
const QString& body(msg.body());
if (body.size() != 0) {
QString id = msg.id();
Shared::Message sMsg(Shared::Message::groupChat);
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
QString jid = sMsg.getPenPalJid();
Conference* cnt = acc->rh->getConference(jid);
if (cnt == 0) {
return false;
}
std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
if (pItr != pendingStateMessages.end()) {
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
cnt->changeMessage(id, cData);
pendingStateMessages.erase(pItr);
emit acc->changeMessage(jid, id, cData);
} else {
QString oId = msg.replaceId();
if (oId.size() > 0) {
QMap<QString, QVariant> cData = {
{"body", sMsg.getBody()},
{"stamp", sMsg.getTime()}
};
cnt->correctMessageInArchive(oId, sMsg);
emit acc->changeMessage(jid, oId, cData);
} else {
cnt->appendMessageToArchive(sMsg);
QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
if (sMsg.getTime() > minAgo) { //otherwise it's considered a delayed delivery, most probably MUC history receipt
emit acc->message(sMsg);
} else {
//qDebug() << "Delayed delivery: ";
}
}
}
return true;
}
return false;
}
void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
{
const QDateTime& time(source.stamp()); const QDateTime& time(source.stamp());
QString id; QString id;
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0) #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
id = source.originId(); id = source.originId();
if (id.size() == 0) { if (id.size() == 0)
id = source.id(); id = source.id();
}
target.setStanzaId(source.stanzaId()); target.setStanzaId(source.stanzaId());
qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
#else #else
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();
qDebug() << "Had do initialize a message with no id, assigning autogenerated" << messageId;
} }
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()); #ifdef WITH_OMEMO
if (guessing) { #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) { if (source.encryptionMethod() == QXmpp::EncryptionMethod::Omemo2)
outgoing = true; target.setEncryption(Shared::EncryptionProtocol::omemo2);
} else { #endif
outgoing = false; #endif
}
} if (guessing)
outgoing = target.getFromJid() == acc->getBareJid();
target.setOutgoing(outgoing); target.setOutgoing(outgoing);
if (time.isValid()) { if (time.isValid())
target.setTime(time); target.setTime(time);
} 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) {
{
qDebug() << reason; qDebug() << reason;
qDebug() << "- from: " << msg.from(); qDebug() << "- from: " << msg.from();
qDebug() << "- to: " << msg.to(); qDebug() << "- to: " << msg.to();
@ -208,164 +226,422 @@ void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& re
qDebug() << "=============================="; qDebug() << "==============================";
} }
void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg) #if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
{ void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg) {
handleChatMessage(msg, false, true); handleChatMessage(msg, false, true);
} }
void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg) void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg) {
{
handleChatMessage(msg, true, true); handleChatMessage(msg, true, true);
} }
#endif
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id) std::optional<Shared::MessageInfo> Core::MessageHandler::getOriginalPendingMessageId(const QString& id, bool clear) {
{
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id); std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
if (itr != pendingStateMessages.end()) { if (itr != pendingStateMessages.end()) {
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}}; Shared::MessageInfo info(acc->name, itr->second, itr->first);
RosterItem* ri = acc->rh->getRosterItem(itr->second); std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
if (ri != 0) { if (itrC != pendingCorrectionMessages.end()) {
ri->changeMessage(id, cData); if (itrC->second.size() > 0)
info.jid = itrC->second;
if (clear)
pendingCorrectionMessages.erase(itrC);
} }
pendingStateMessages.erase(itr);
emit acc->changeMessage(itr->second, id, cData); if (clear)
pendingStateMessages.erase(itr);
return info;
}
return std::nullopt;
}
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id) {
SHARED_UNUSED(jid);
adjustPendingMessage(id, {{"state", static_cast<uint>(Shared::Message::State::delivered)}}, true);
}
void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId) {
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId));
prepareUpload(data, newMessage);
} else {
performSending(data, originalId, newMessage);
} }
} }
void Core::MessageHandler::sendMessage(Shared::Message data) void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage) {
{ QString jid = data.getPenPalJid();
QString id = data.getId();
qDebug() << "Sending message with id:" << id;
if (originalId.size() > 0)
qDebug() << "To replace the one with id:" << originalId;
RosterItem* ri = acc->rh->getRosterItem(jid);
if (newMessage && originalId.size() > 0)
newMessage = false;
QDateTime sendTime = QDateTime::currentDateTimeUtc();
std::pair<Shared::Message::State, QString> result = scheduleSending(data, sendTime, originalId);
data.setState(result.first);
data.setErrorText(result.second);
QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
if (ri != nullptr) {
if (newMessage)
ri->appendMessageToArchive(data);
else
ri->changeMessage(originalId.isEmpty() ? id : originalId, changes);
if (data.getState() != Shared::Message::State::error) {
pendingStateMessages.insert(std::make_pair(id, jid));
if (originalId.size() > 0)
pendingCorrectionMessages.insert(std::make_pair(id, originalId));
} else {
pendingStateMessages.erase(id);
pendingCorrectionMessages.erase(id);
}
}
emit acc->changeMessage(jid, originalId.isEmpty() ? id : originalId, changes);
}
std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending(
const Shared::Message& message,
const QDateTime& sendTime,
const QString& originalId
) {
if (acc->state != Shared::ConnectionState::connected)
return {Shared::Message::State::error, "You are is offline or reconnecting"};
QXmppMessage msg = createPacket(message, sendTime, originalId);
QString id = msg.id();
#ifdef WITH_OMEMO
if (message.getEncryption() == Shared::EncryptionProtocol::omemo2) {
QXmppTask<QXmppE2eeExtension::MessageEncryptResult> task = acc->om->encryptMessage(std::move(msg), std::nullopt);
if (task.isFinished()) {
const QXmppE2eeExtension::MessageEncryptResult& res = task.result();
if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(res)) {
qDebug() << "Successfully encrypted a message";
const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(res);
encrypted->setBody(QString());
encrypted->setOutOfBandUrl(QString());
bool success = acc->client.sendPacket(*encrypted.get());
if (success) {
qDebug() << "Successfully sent an encrypted message";
return {Shared::Message::State::sent, ""};
} else {
qDebug() << "Couldn't sent an encrypted message";
return {Shared::Message::State::error, "Error sending successfully encrypted message"};
}
} else if (std::holds_alternative<QXmppError>(res)) {
qDebug() << "Couldn't encrypt a message";
const QXmppError& err = std::get<QXmppError>(res);
return {Shared::Message::State::error, err.description};
} else {
qDebug() << "Couldn't encrypt a message";
return {Shared::Message::State::error, "Unexpected error ecryptng the message"};
}
} else {
task.then(this, [this, id] (QXmppE2eeExtension::MessageEncryptResult&& result) {
if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(result)) {
qDebug() << "Successfully encrypted a message";
const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(result);
encrypted->setBody(QString());
encrypted->setOutOfBandUrl(QString());
bool success = acc->client.sendPacket(*encrypted.get());
if (success) {
qDebug() << "Successfully sent an encrypted message";
if (!adjustPendingMessage(id, stateSent, false))
qDebug() << "Encrypted message has been successfully sent, but it couldn't be found to update the sate";
} else {
qDebug() << "Couldn't sent an encrypted message";
handlePendingMessageError(id, "Error sending successfully encrypted message");
}
} else if (std::holds_alternative<QXmppError>(result)) {
qDebug() << "Couldn't encrypt a message";
const QXmppError& err = std::get<QXmppError>(result);
handlePendingMessageError(id, err.description);
} else {
qDebug() << "Couldn't encrypt a message";
handlePendingMessageError(id, "Unexpected error ecryptng the message");
}
});
return {Shared::Message::State::pending, ""};
}
} else
#endif
{
bool success = acc->client.sendPacket(msg);
if (success)
return {Shared::Message::State::sent, ""};
else
return {Shared::Message::State::error, "Error sending message, internal QXMPP error"};
}
}
bool Core::MessageHandler::adjustPendingMessage(const QString& messageId, const QMap<QString, QVariant>& data, bool final) {
std::optional<Shared::MessageInfo> info = getOriginalPendingMessageId(messageId, final);
if (info) {
RosterItem* ri = acc->rh->getRosterItem(info->jid);
if (ri != nullptr)
ri->changeMessage(info->messageId, data);
emit acc->changeMessage(info->jid, info->messageId, data);
return true;
}
return false;
}
QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const {
QMap<QString, QVariant> changes;
QString oob = data.getOutOfBandUrl();
Shared::Message::State mstate = data.getState();
changes.insert("state", static_cast<uint>(mstate));
if (mstate == Shared::Message::State::error)
changes.insert("errorText", data.getErrorText());
if (oob.size() > 0)
changes.insert("outOfBandUrl", oob);
if (newMessage)
data.setTime(time);
if (originalId.size() > 0)
changes.insert("body", data.getBody());
changes.insert("stamp", time);
//sometimes (when the image is pasted with ctrl+v)
//I start sending message with one path, then copy it to downloads directory
//so, the final path changes. Let's assume it changes always since it costs me close to nothing
QString attachPath = data.getAttachPath();
if (attachPath.size() > 0) {
QString squawkified = Shared::squawkifyPath(attachPath);
changes.insert("attachPath", squawkified);
if (attachPath != squawkified)
data.setAttachPath(squawkified);
}
return changes;
}
QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const {
QXmppMessage msg(QString(), data.getTo(), data.getBody(), data.getThread());
QString id(data.getId());
if (originalId.size() > 0)
msg.setReplaceId(originalId);
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
msg.setOriginId(id);
#endif
msg.setId(id);
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
msg.setOutOfBandUrl(data.getOutOfBandUrl());
msg.setReceiptRequested(true);
msg.setStamp(time);
return msg;
}
void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage) {
if (acc->state != Shared::ConnectionState::connected) {
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
return;
}
QString jid = data.getPenPalJid(); QString jid = data.getPenPalJid();
QString id = data.getId(); QString id = data.getId();
RosterItem* ri = acc->rh->getRosterItem(jid); RosterItem* ri = acc->rh->getRosterItem(jid);
if (acc->state == Shared::ConnectionState::connected) { if (ri == nullptr) {
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread()); 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;
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
msg.setOriginId(id);
#endif
msg.setId(id);
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
msg.setOutOfBandUrl(data.getOutOfBandUrl());
msg.setReceiptRequested(true);
bool sent = acc->client.sendPacket(msg);
if (sent) {
data.setState(Shared::Message::State::sent);
} else {
data.setState(Shared::Message::State::error);
data.setErrorText("Couldn't send message via QXMPP library check out logs");
}
if (ri != 0) {
ri->appendMessageToArchive(data);
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
}
}
} else {
data.setState(Shared::Message::State::error);
data.setErrorText("You are is offline or reconnecting");
} }
emit acc->changeMessage(jid, id, {
{"state", static_cast<uint>(data.getState())},
{"errorText", data.getErrorText()}
});
}
void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path) QString path = data.getAttachPath();
{ QString url = acc->network->getFileRemoteUrl(path);
if (acc->state == Shared::ConnectionState::connected) { if (url.size() != 0)
QString url = acc->network->getFileRemoteUrl(path); return sendMessageWithLocalUploadedFile(data, url, newMessage);
if (url.size() != 0) {
sendMessageWithLocalUploadedFile(data, url); pendingStateMessages.insert(std::make_pair(id, jid));
} else { if (newMessage) {
if (acc->network->isUploading(path, data.getId())) { ri->appendMessageToArchive(data);
pendingMessages.emplace(data.getId(), data);
} else {
if (acc->um->serviceFound()) {
QFileInfo file(path);
if (file.exists() && file.isReadable()) {
uploadingSlotsQueue.emplace_back(path, data);
if (uploadingSlotsQueue.size() == 1) {
acc->um->requestUploadSlot(file);
}
} else {
onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
}
} else {
onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
}
}
}
} else { } else {
onFileUploadError(data.getId(), "Account is offline or reconnecting"); ri->changeMessage(id, statePending);
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping"; emit acc->changeMessage(jid, id, statePending);
}
//this checks if the file is already uploading, and if so it subscribes to it's success,
//So, I need to do stuff only if the network knows nothing of this file
if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path))
return;
if (!acc->um->serviceFound()) {
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
return;
}
QFileInfo file(path);
if (file.exists() && file.isReadable()) {
pendingStateMessages.insert(std::make_pair(id, jid));
uploadingSlotsQueue.emplace_back(file, id);
if (uploadingSlotsQueue.size() == 1)
acc->um->requestUploadSlot(file);
} else {
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
} }
} }
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot) {
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
{
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<QFileInfo, 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.path(), slot.putUrl(), slot.getUrl(), slot.putHeaders());
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) { uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0)
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first); acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
} }
} }
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) {
{ QString err(request.error().text());
if (uploadingSlotsQueue.size() == 0) { 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<QFileInfo, 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);
if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
uploadingSlotsQueue.pop_front(); uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0)
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
} }
} }
void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url) void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path) {
{ QMap<QString, QVariant> cData = {
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId); {"attachPath", path}
if (itr != pendingMessages.end()) { };
sendMessageWithLocalUploadedFile(itr->second, url); for (const Shared::MessageInfo& info : msgs) {
pendingMessages.erase(itr); if (info.account != acc->getName())
continue;
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
if (cnt != nullptr) {
bool changed = cnt->changeMessage(info.messageId, cData);
if (changed)
emit acc->changeMessage(info.jid, info.messageId, cData);
}
} }
} }
void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg) void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up) {
{ if (!up)
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId); return;
if (itr != pendingMessages.end()) {
pendingMessages.erase(itr); for (const Shared::MessageInfo& info : msgs)
if (info.account == acc->getName())
handleUploadError(info.jid, info.messageId, text);
}
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText) {
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
pendingStateMessages.erase(messageId);
pendingCorrectionMessages.erase(messageId);
requestChangeMessage(jid, messageId, {
{"state", static_cast<uint>(Shared::Message::State::error)},
{"errorText", errorText}
});
}
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
for (const Shared::MessageInfo& info : msgs) {
if (info.account != acc->getName())
continue;
RosterItem* ri = acc->rh->getRosterItem(info.jid);
if (ri != nullptr) {
Shared::Message msg = ri->getMessage(info.messageId);
msg.setAttachPath(path);
sendMessageWithLocalUploadedFile(msg, url, false);
} else {
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
}
} }
} }
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url) 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 messengers 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, pendingCorrectionMessages.at(msg.getId()), newMessage);
//TODO removal/progress update //TODO removal/progress update
} }
static const std::set<QString> allowedToChangeKeys({
"attachPath",
"outOfBandUrl",
"state",
"errorText"
});
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data) {
RosterItem* cnt = acc->rh->getRosterItem(jid);
if (cnt != nullptr) {
bool allSupported = true;
QString unsupportedString;
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
if (allowedToChangeKeys.count(itr.key()) != 1) { //to not allow this method
allSupported = false; //to make a message to look like if it was edited
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
break; //because the underlying tech assumes that
} //the change is initiated by user, not by system
}
if (allSupported) {
cnt->changeMessage(messageId, data);
emit acc->changeMessage(jid, messageId, data);
} else {
qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping";
}
}
}
void Core::MessageHandler::resendMessage(const QString& jid, const QString& id) {
RosterItem* cnt = acc->rh->getRosterItem(jid);
if (cnt != nullptr) {
try {
Shared::Message msg = cnt->getMessage(id);
if (msg.getState() == Shared::Message::State::error) {
if (msg.getEdited()) {
QString originalId = msg.getId();
msg.generateRandomId();
sendMessage(msg, false, originalId);
} else {
sendMessage(msg, false);
}
} else {
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping";
}
} catch (const LMDBAL::NotFound& err) {
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message wasn't found in history, skipping";
}
} else {
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this jid isn't present in account roster, skipping";
}
}

View File

@ -16,61 +16,73 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CORE_MESSAGEHANDLER_H #pragma once
#define CORE_MESSAGEHANDLER_H
#include <QObject> #include <QObject>
#include <QFileInfo>
#include <deque> #include <deque>
#include <map> #include <map>
#include <functional>
#include <optional>
#include <QXmppMessage.h> #include <QXmppMessage.h>
#include <QXmppHttpUploadIq.h> #include <QXmppHttpUploadIq.h>
#ifdef WITH_OMEMO
#include <QXmppE2eeExtension.h>
#endif
#include <shared/message.h> #include <shared/message.h>
#include <shared/messageinfo.h>
#include <shared/pathcheck.h>
namespace Core { namespace Core {
/**
* @todo write docs
*/
class Account; class Account;
class MessageHandler : public QObject class MessageHandler : public QObject {
{
Q_OBJECT Q_OBJECT
public: public:
MessageHandler(Account* account); MessageHandler(Account* account);
public: public:
void sendMessage(Shared::Message data); void sendMessage(const Shared::Message& data, bool newMessage = true, QString originalId = "");
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;
void resendMessage(const QString& jid, const QString& id);
public slots: public slots:
void onMessageReceived(const QXmppMessage& message); void onMessageReceived(const QXmppMessage& message);
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
void onCarbonMessageReceived(const QXmppMessage& message); void onCarbonMessageReceived(const QXmppMessage& message);
void onCarbonMessageSent(const QXmppMessage& message); void onCarbonMessageSent(const QXmppMessage& message);
#endif
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& url, const QString& path);
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
private: 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, const QString& originalId, bool newMessage = true);
void prepareUpload(const Shared::Message& data, bool newMessage = true);
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const;
QMap<QString, QVariant> getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const;
std::optional<Shared::MessageInfo> getOriginalPendingMessageId(const QString& id, bool clear = true);
bool handlePendingMessageError(const QString& id, const QString& errorText);
std::pair<Shared::Message::State, QString> scheduleSending(const Shared::Message& message, const QDateTime& sendTime, const QString& originalId);
bool adjustPendingMessage(const QString& messageId, const QMap<QString, QVariant>& data, bool final);
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::map<QString, QString> pendingCorrectionMessages; //key is new mesage, value is originalOne
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue; std::deque<std::pair<QFileInfo, QString>> uploadingSlotsQueue;
}; };
} }
#endif // CORE_MESSAGEHANDLER_H

View File

@ -0,0 +1,285 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDebug>
#include "omemohandler.h"
#include "core/account.h"
#include "core/adapterfunctions.h"
Core::OmemoHandler::OmemoHandler(Account* account) :
QObject(),
QXmppOmemoStorage(),
acc(account),
ownDevice(std::nullopt),
db(acc->getName() + "/omemo"),
meta(db.addCache<QString, QVariant>("meta")),
devices(db.addCache<QString, QHash<uint32_t, Device>>("devices")),
preKeyPairs(db.addCache<uint32_t, QByteArray>("preKeyPairs")),
signedPreKeyPairs(db.addCache<uint32_t, SignedPreKeyPair>("signedPreKeyPairs"))
{
db.open();
try {
QVariant own = meta->getRecord("ownDevice");
ownDevice = own.value<OwnDevice>();
qDebug() << "Successfully found own device omemo data for account" << acc->getName();
} catch (const LMDBAL::NotFound& e) {
qDebug() << "No device omemo data was found for account" << acc->getName();
}
}
Core::OmemoHandler::~OmemoHandler() {
db.close();
}
bool Core::OmemoHandler::hasOwnDevice() {
return ownDevice.has_value();
}
QXmppTask<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
OmemoData data;
data.ownDevice = ownDevice;
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
std::map<uint32_t, QByteArray> pkeys = preKeyPairs->readAll(txn);
for (const std::pair<const uint32_t, QByteArray>& pair : pkeys)
data.preKeyPairs.insert(pair.first, pair.second);
std::map<uint32_t, SignedPreKeyPair> spre = signedPreKeyPairs->readAll(txn);
for (const std::pair<const uint32_t, SignedPreKeyPair>& pair : spre) {
QXmppOmemoStorage::SignedPreKeyPair qxpair = {pair.second.first, pair.second.second};
data.signedPreKeyPairs.insert(pair.first, qxpair);
}
std::map<QString, QHash<uint32_t, Device>> devs = devices->readAll(txn);
for (const std::pair<const QString, QHash<uint32_t, Device>>& pair : devs)
data.devices.insert(pair.first, pair.second);
return Core::makeReadyTask(std::move(data));
}
QXmppTask<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t deviceId, const QXmppOmemoStorage::Device& device) {
QHash<uint32_t, Device> devs;
LMDBAL::WriteTransaction txn = db.beginTransaction();
bool had = true;
try {
devices->getRecord(jid, devs, txn);
} catch (const LMDBAL::NotFound& error) {
had = false;
}
devs.insert(deviceId, device); //overwrites
if (had)
devices->changeRecord(jid, devs, txn);
else
devices->addRecord(jid, devs, txn);
txn.commit();
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArray>& keyPairs) {
LMDBAL::WriteTransaction txn = db.beginTransaction();
for (QHash<uint32_t, QByteArray>::const_iterator itr = keyPairs.begin(), end = keyPairs.end(); itr != end; ++itr)
preKeyPairs->forceRecord(itr.key(), itr.value(), txn);
txn.commit();
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair& keyPair) {
signedPreKeyPairs->forceRecord(keyId, std::make_pair(keyPair.creationDate, keyPair.data));
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::removeDevice(const QString& jid, uint32_t deviceId) {
LMDBAL::WriteTransaction txn = db.beginTransaction();
QHash<uint32_t, Device> devs = devices->getRecord(jid, txn);
devs.remove(deviceId);
if (devs.isEmpty())
devices->removeRecord(jid, txn);
else
devices->changeRecord(jid, devs, txn);
txn.commit();
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::removeDevices(const QString& jid) {
devices->removeRecord(jid);
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::removePreKeyPair(uint32_t keyId) {
try {
preKeyPairs->removeRecord(keyId);
} catch (const LMDBAL::NotFound& e) {
qDebug() << "Couldn't remove preKeyPair " << e.what();
}
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::removeSignedPreKeyPair(uint32_t keyId) {
try {
signedPreKeyPairs->removeRecord(keyId);
} catch (const LMDBAL::NotFound& e) {}
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::setOwnDevice(const std::optional<OwnDevice>& device) {
bool had = ownDevice.has_value();
ownDevice = device;
if (ownDevice.has_value()) {
if (had)
meta->changeRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
else
meta->addRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
} else if (had) {
meta->removeRecord("ownDevice");
}
return Core::makeReadyTask();
}
QXmppTask<void> Core::OmemoHandler::resetAll() {
ownDevice = std::nullopt;
db.drop();
return Core::makeReadyTask();
}
void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const {
QHash<uint32_t, Device> devs;
try {
devices->getRecord(jid, devs);
} catch (const LMDBAL::NotFound& error) {}
for (QHash<uint32_t, Device>::const_iterator itr = devs.begin(), end = devs.end(); itr != end; ++itr) {
const Device& dev = itr.value();
out.emplace_back(
itr.key(),
dev.keyId,
dev.label,
dev.removalFromDeviceListDate,
Shared::TrustLevel::undecided,
Shared::EncryptionProtocol::omemo2,
false
);
}
}
void Core::OmemoHandler::requestBundles(const QString& jid) {
QXmppTask<void> task = acc->om->buildMissingSessions({jid});
Contact* cnt = acc->rh->getContact(jid);
if (cnt)
cnt->omemoBundles = Shared::Possible::discovering;
task.then(this, std::bind(&OmemoHandler::onBundlesReceived, this, jid));
}
void Core::OmemoHandler::requestOwnBundles() {
QXmppTask<void> task = acc->om->buildMissingSessions({acc->getBareJid()});
task.then(this, std::bind(&OmemoHandler::onOwnBundlesReceived, this));
}
void Core::OmemoHandler::onBundlesReceived(const QString& jid) {
std::list<Shared::KeyInfo> keys = readKeys(jid);
Contact* cnt = acc->rh->getContact(jid);
if (cnt)
cnt->omemoBundles = Shared::Possible::present;
acc->delay->receivedBundles(jid, keys);
}
void Core::OmemoHandler::onOwnBundlesReceived() {
std::list<Shared::KeyInfo> keys = readKeys(acc->getBareJid());
if (ownDevice)
keys.emplace_front(
ownDevice->id,
ownDevice->publicIdentityKey,
ownDevice->label,
QDateTime::currentDateTime(),
Shared::TrustLevel::authenticated,
Shared::EncryptionProtocol::omemo2,
true
);
acc->delay->receivedOwnBundles(keys);
}
std::list<Shared::KeyInfo> Core::OmemoHandler::readKeys(const QString& jid) {
std::list<Shared::KeyInfo> keys;
getDevices(jid, keys);
std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(Shared::EncryptionProtocol::omemo2, jid);
for (Shared::KeyInfo& key : keys) {
std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
if (itr != trustLevels.end())
key.trustLevel = itr->second;
}
return keys;
}
void Core::OmemoHandler::onOmemoDeviceAdded(const QString& jid, uint32_t id) {
SHARED_UNUSED(id);
qDebug() << "OMEMO device added for" << jid;
}
QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
in >> device.label;
in >> device.keyId;
in >> device.session;
in >> device.unrespondedSentStanzasCount;
in >> device.unrespondedReceivedStanzasCount;
in >> device.removalFromDeviceListDate;
return in;
}
QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::Device& device) {
out << device.label;
out << device.keyId;
out << device.session;
out << device.unrespondedSentStanzasCount;
out << device.unrespondedReceivedStanzasCount;
out << device.removalFromDeviceListDate;
return out;
}
QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::OwnDevice& device) {
in >> device.id;
in >> device.label;
in >> device.privateIdentityKey;
in >> device.publicIdentityKey;
in >> device.latestSignedPreKeyId;
in >> device.latestPreKeyId;
return in;
}
QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::OwnDevice& device) {
out << device.id;
out << device.label;
out << device.privateIdentityKey;
out << device.publicIdentityKey;
out << device.latestSignedPreKeyId;
out << device.latestPreKeyId;
return out;
}

View File

@ -0,0 +1,90 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <map>
#include <list>
#include <functional>
#include <QXmppOmemoStorage.h>
#include <cache.h>
#include <shared/keyinfo.h>
#include <shared/enums.h>
Q_DECLARE_METATYPE(QXmppOmemoStorage::OwnDevice);
Q_DECLARE_METATYPE(QXmppOmemoStorage::Device);
namespace Core {
class Account;
class OmemoHandler : public QObject, public QXmppOmemoStorage {
Q_OBJECT
public:
typedef std::pair<QDateTime, QByteArray> SignedPreKeyPair;
OmemoHandler(Account* account);
~OmemoHandler() override;
virtual QXmppTask<OmemoData> allData() override;
virtual QXmppTask<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
virtual QXmppTask<void> addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair &keyPair) override;
virtual QXmppTask<void> removeSignedPreKeyPair(uint32_t keyId) override;
virtual QXmppTask<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
virtual QXmppTask<void> removePreKeyPair(uint32_t keyId) override;
virtual QXmppTask<void> addDevice(const QString &jid, uint32_t deviceId, const Device &device) override;
virtual QXmppTask<void> removeDevice(const QString &jid, uint32_t deviceId) override;
virtual QXmppTask<void> removeDevices(const QString &jid) override;
virtual QXmppTask<void> resetAll() override;
bool hasOwnDevice();
void requestBundles(const QString& jid);
void requestOwnBundles();
void getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const;
public slots:
void onOmemoDeviceAdded(const QString& jid, uint32_t id);
private slots:
void onBundlesReceived(const QString& jid);
void onOwnBundlesReceived();
std::list<Shared::KeyInfo> readKeys(const QString& jid);
private:
Account* acc;
std::optional<OwnDevice> ownDevice;
LMDBAL::Base db;
LMDBAL::Cache<QString, QVariant>* meta;
LMDBAL::Cache<QString, QHash<uint32_t, Device>>* devices;
LMDBAL::Cache<uint32_t, QByteArray>* preKeyPairs;
LMDBAL::Cache<uint32_t, SignedPreKeyPair>* signedPreKeyPairs;
};
}
QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::Device& device);
QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::Device& device);
QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::OwnDevice& device);
QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::OwnDevice& device);

View File

@ -26,34 +26,41 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
conferences(), conferences(),
groups(), groups(),
queuedContacts(), queuedContacts(),
outOfRosterContacts() outOfRosterContacts() {}
{
void Core::RosterHandler::initialize() {
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived); connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded); connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
connect(acc->rm, &QXmppRosterManager::itemRemoved, this, &RosterHandler::onRosterItemRemoved); connect(acc->rm, &QXmppRosterManager::itemRemoved, this, &RosterHandler::onRosterItemRemoved);
connect(acc->rm, &QXmppRosterManager::itemChanged, this, &RosterHandler::onRosterItemChanged); connect(acc->rm, &QXmppRosterManager::itemChanged, this, &RosterHandler::onRosterItemChanged);
connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded); connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded);
connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived); connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
connect(acc, &Account::pepSupportChanged, this, &RosterHandler::onPepSupportedChanged);
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
connect(acc->th, &TrustHandler::trustLevelsChanged, this, &RosterHandler::onTrustChanged);
#endif
} }
Core::RosterHandler::~RosterHandler() Core::RosterHandler::~RosterHandler() {
{ clear();
for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
delete itr->second;
}
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
delete itr->second;
}
} }
void Core::RosterHandler::onRosterReceived() void Core::RosterHandler::clear() {
{ for (const std::pair<const QString, Contact*>& pair : contacts)
acc->vm->requestClientVCard(); //TODO need to make sure server actually supports vCards delete pair.second;
acc->ownVCardRequestInProgress = true;
for (const std::pair<const QString, Conference*>& pair : conferences)
delete pair.second;
contacts.clear();
conferences.clear();
}
void Core::RosterHandler::onRosterReceived() {
QStringList bj = acc->rm->getRosterBareJids(); QStringList bj = acc->rm->getRosterBareJids();
for (int i = 0; i < bj.size(); ++i) { for (int i = 0; i < bj.size(); ++i) {
const QString& jid = bj[i]; const QString& jid = bj[i];
@ -61,8 +68,7 @@ void Core::RosterHandler::onRosterReceived()
} }
} }
void Core::RosterHandler::onRosterItemAdded(const QString& bareJid) void Core::RosterHandler::onRosterItemAdded(const QString& bareJid) {
{
QString lcJid = bareJid.toLower(); QString lcJid = bareJid.toLower();
addedAccount(lcJid); addedAccount(lcJid);
std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid); std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid);
@ -72,8 +78,7 @@ void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
} }
} }
void Core::RosterHandler::addedAccount(const QString& jid) void Core::RosterHandler::addedAccount(const QString& jid) {
{
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid); std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
QXmppRosterIq::Item re = acc->rm->getRosterEntry(jid); QXmppRosterIq::Item re = acc->rm->getRosterEntry(jid);
Contact* contact; Contact* contact;
@ -82,7 +87,6 @@ void Core::RosterHandler::addedAccount(const QString& jid)
newContact = true; newContact = true;
contact = new Contact(jid, acc->name); contact = new Contact(jid, acc->name);
contacts.insert(std::make_pair(jid, contact)); contacts.insert(std::make_pair(jid, contact));
} else { } else {
contact = itr->second; contact = itr->second;
} }
@ -94,70 +98,44 @@ void Core::RosterHandler::addedAccount(const QString& jid)
contact->setName(re.name()); contact->setName(re.name());
if (newContact) { if (newContact) {
QMap<QString, QVariant> cData({ handleNewContact(contact);
{"name", re.name()}, QMap<QString, QVariant> cData = contact->getInfo();
{"state", QVariant::fromValue(state)} #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
}); cData.insert("trust", QVariant::fromValue(acc->th->getSummary(jid)));
#endif
careAboutAvatar(contact, cData);
int grCount = 0; int grCount = 0;
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) { for (const QString& groupName : gr) {
const QString& groupName = *itr;
addToGroup(jid, groupName); addToGroup(jid, groupName);
emit acc->addContact(jid, groupName, cData); emit acc->addContact(jid, groupName, cData);
grCount++; grCount++;
} }
if (grCount == 0) { if (grCount == 0)
emit acc->addContact(jid, "", cData); emit acc->addContact(jid, "", cData);
if (acc->pepSupport == Shared::Support::supported) {
acc->dm->requestInfo(jid);
//acc->dm->requestItems(jid);
} }
handleNewContact(contact);
} }
} }
void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin) void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin) {
{
QXmppMucRoom* room = acc->mm->addRoom(jid); QXmppMucRoom* room = acc->mm->addRoom(jid);
QString lNick = nick; QString lNick = nick;
if (lNick.size() == 0) { if (lNick.size() == 0)
lNick = acc->getName(); lNick = acc->getName();
}
Conference* conf = new Conference(jid, acc->getName(), autoJoin, roomName, lNick, room); Conference* conf = new Conference(jid, acc->getName(), autoJoin, roomName, lNick, room);
conferences.insert(std::make_pair(jid, conf)); conferences.insert(std::make_pair(jid, conf));
handleNewConference(conf); handleNewConference(conf);
QMap<QString, QVariant> cData = { QMap<QString, QVariant> cData = conf->getInfo();
{"autoJoin", conf->getAutoJoin()},
{"joined", conf->getJoined()},
{"nick", conf->getNick()},
{"name", conf->getName()},
{"avatars", conf->getAllAvatars()}
};
careAboutAvatar(conf, cData);
emit acc->addRoom(jid, cData); emit acc->addRoom(jid, cData);
} }
void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data) void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups) {
{
Archive::AvatarInfo info;
bool hasAvatar = item->readAvatarInfo(info);
if (hasAvatar) {
if (info.autogenerated) {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
} else {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
}
data.insert("avatarPath", item->avatarPath() + "." + info.type);
} else {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
data.insert("avatarPath", "");
acc->requestVCard(item->jid);
}
}
void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
{
if (acc->state == Shared::ConnectionState::connected) { if (acc->state == Shared::ConnectionState::connected) {
std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid); std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
if (itr != queuedContacts.end()) { if (itr != queuedContacts.end()) {
@ -171,8 +149,7 @@ void Core::RosterHandler::addContactRequest(const QString& jid, const QString& n
} }
} }
void Core::RosterHandler::removeContactRequest(const QString& jid) void Core::RosterHandler::removeContactRequest(const QString& jid) {
{
QString lcJid = jid.toLower(); QString lcJid = jid.toLower();
if (acc->state == Shared::ConnectionState::connected) { if (acc->state == Shared::ConnectionState::connected) {
std::set<QString>::const_iterator itr = outOfRosterContacts.find(lcJid); std::set<QString>::const_iterator itr = outOfRosterContacts.find(lcJid);
@ -187,25 +164,23 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
} }
} }
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact) void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact) {
{
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory); connect(contact, &RosterItem::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::encryptionChanged, this, &RosterHandler::onContactEncryptionChanged);
connect(contact, &RosterItem::requestVCard, acc->delay, &DelayManager::Manager::getVCard);
} }
void Core::RosterHandler::handleNewContact(Core::Contact* contact) void Core::RosterHandler::handleNewContact(Core::Contact* contact) {
{
handleNewRosterItem(contact); handleNewRosterItem(contact);
connect(contact, &Contact::groupAdded, this, &RosterHandler::onContactGroupAdded); connect(contact, &Contact::groupAdded, this, &RosterHandler::onContactGroupAdded);
connect(contact, &Contact::groupRemoved, this, &RosterHandler::onContactGroupRemoved); connect(contact, &Contact::groupRemoved, this, &RosterHandler::onContactGroupRemoved);
connect(contact, &Contact::subscriptionStateChanged, this, &RosterHandler::onContactSubscriptionStateChanged); connect(contact, &Contact::subscriptionStateChanged, this, &RosterHandler::onContactSubscriptionStateChanged);
} }
void Core::RosterHandler::handleNewConference(Core::Conference* contact) void Core::RosterHandler::handleNewConference(Core::Conference* contact) {
{
handleNewRosterItem(contact); handleNewRosterItem(contact);
connect(contact, &Conference::nickChanged, this, &RosterHandler::onMucNickNameChanged); connect(contact, &Conference::nickChanged, this, &RosterHandler::onMucNickNameChanged);
connect(contact, &Conference::subjectChanged, this, &RosterHandler::onMucSubjectChanged); connect(contact, &Conference::subjectChanged, this, &RosterHandler::onMucSubjectChanged);
@ -216,78 +191,74 @@ void Core::RosterHandler::handleNewConference(Core::Conference* contact)
connect(contact, &Conference::removeParticipant, this, &RosterHandler::onMucRemoveParticipant); connect(contact, &Conference::removeParticipant, this, &RosterHandler::onMucRemoveParticipant);
} }
void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data) void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data) {
{
Conference* room = static_cast<Conference*>(sender()); Conference* room = static_cast<Conference*>(sender());
emit acc->addRoomParticipant(room->jid, nickName, data); emit acc->addRoomParticipant(room->jid, nickName, data);
} }
void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data) void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data) {
{
Conference* room = static_cast<Conference*>(sender()); Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoomParticipant(room->jid, nickName, data); emit acc->changeRoomParticipant(room->jid, nickName, data);
} }
void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName) void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName) {
{
Conference* room = static_cast<Conference*>(sender()); Conference* room = static_cast<Conference*>(sender());
emit acc->removeRoomParticipant(room->jid, nickName); emit acc->removeRoomParticipant(room->jid, nickName);
} }
void Core::RosterHandler::onMucSubjectChanged(const QString& subject) void Core::RosterHandler::onMucSubjectChanged(const QString& subject) {
{
Conference* room = static_cast<Conference*>(sender()); Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, { emit acc->changeRoom(room->jid, {{"subject", subject}});
{"subject", subject}
});
} }
void Core::RosterHandler::onContactGroupAdded(const QString& group) void Core::RosterHandler::onContactGroupAdded(const QString& group) {
{
Contact* contact = static_cast<Contact*>(sender()); Contact* contact = static_cast<Contact*>(sender());
if (contact->groupsCount() == 1) { if (contact->groupsCount() == 1) {
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
} }
QMap<QString, QVariant> cData({ QMap<QString, QVariant> cData({
{"name", contact->getName()}, {"name", contact->getName()},
{"state", QVariant::fromValue(contact->getSubscriptionState())} {"state", QVariant::fromValue(contact->getSubscriptionState())},
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
{"trust", QVariant::fromValue(acc->th->getSummary(contact->jid))},
#endif
{"encryption", QVariant::fromValue(contact->encryption())}
}); });
addToGroup(contact->jid, group); addToGroup(contact->jid, group);
emit acc->addContact(contact->jid, group, cData); emit acc->addContact(contact->jid, group, cData);
} }
void Core::RosterHandler::onContactGroupRemoved(const QString& group) void Core::RosterHandler::onContactGroupRemoved(const QString& group) {
{
Contact* contact = static_cast<Contact*>(sender()); Contact* contact = static_cast<Contact*>(sender());
if (contact->groupsCount() == 0) { if (contact->groupsCount() == 0) {
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
} }
emit acc->removeContact(contact->jid, group); emit acc->removeContact(contact->jid, group);
removeFromGroup(contact->jid, group); removeFromGroup(contact->jid, group);
} }
void Core::RosterHandler::onContactNameChanged(const QString& cname) void Core::RosterHandler::onContactNameChanged(const QString& cname) {
{ RosterItem* contact = static_cast<RosterItem*>(sender());
Contact* contact = static_cast<Contact*>(sender()); emit acc->changeContact(contact->jid, {{"name", cname}});
QMap<QString, QVariant> cData({
{"name", cname},
});
emit acc->changeContact(contact->jid, cData);
} }
void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate) void Core::RosterHandler::onContactEncryptionChanged(Shared::EncryptionProtocol value) {
{ RosterItem* contact = static_cast<RosterItem*>(sender());
Contact* contact = static_cast<Contact*>(sender()); emit acc->changeContact(contact->jid, {{"encryption", QVariant::fromValue(value)}});
QMap<QString, QVariant> cData({
{"state", QVariant::fromValue(cstate)},
});
emit acc->changeContact(contact->jid, cData);
} }
void Core::RosterHandler::addToGroup(const QString& jid, const QString& group) void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate) {
{ Contact* contact = static_cast<Contact*>(sender());
emit acc->changeContact(contact->jid, {{"state", QVariant::fromValue(cstate)}});
}
void Core::RosterHandler::onTrustChanged(const QString& jid, const Shared::TrustSummary& trust) {
emit acc->changeContact(jid, {{"trust", QVariant::fromValue(trust)}});
}
void Core::RosterHandler::addToGroup(const QString& jid, const QString& group) {
std::map<QString, std::set<QString>>::iterator gItr = groups.find(group); std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
if (gItr == groups.end()) { if (gItr == groups.end()) {
gItr = groups.insert(std::make_pair(group, std::set<QString>())).first; gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
@ -296,8 +267,7 @@ void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
gItr->second.insert(jid.toLower()); gItr->second.insert(jid.toLower());
} }
void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group) void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group) {
{
QSet<QString> toRemove; QSet<QString> toRemove;
std::map<QString, std::set<QString>>::iterator itr = groups.find(group); std::map<QString, std::set<QString>>::iterator itr = groups.find(group);
if (itr == groups.end()) { if (itr == groups.end()) {
@ -315,66 +285,53 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
} }
} }
void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list) Core::RosterItem* Core::RosterHandler::getRosterItem(const QString& jid) {
{ RosterItem* item = nullptr;
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)
{
RosterItem* item = 0;
QString lcJid = jid.toLower(); QString lcJid = jid.toLower();
std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid); std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid);
if (citr != contacts.end()) { if (citr != contacts.end()) {
item = citr->second; item = citr->second;
} else { } else {
std::map<QString, Conference*>::const_iterator coitr = conferences.find(lcJid); std::map<QString, Conference*>::const_iterator coitr = conferences.find(lcJid);
if (coitr != conferences.end()) { if (coitr != conferences.end())
item = coitr->second; item = coitr->second;
}
} }
return item; return item;
} }
Core::Conference * Core::RosterHandler::getConference(const QString& jid) Core::Conference* Core::RosterHandler::getConference(const QString& jid) {
{
Conference* item = 0; Conference* item = 0;
std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower()); std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
if (coitr != conferences.end()) { if (coitr != conferences.end())
item = coitr->second; item = coitr->second;
}
return item; return item;
} }
Core::Contact * Core::RosterHandler::getContact(const QString& jid) Core::Contact* Core::RosterHandler::getContact(const QString& jid) {
{
Contact* item = 0; Contact* item = 0;
std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower()); std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
if (citr != contacts.end()) { if (citr != contacts.end())
item = citr->second; item = citr->second;
}
return item; return item;
} }
Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid) Core::Contact* Core::RosterHandler::addOutOfRosterContact(const QString& jid) {
{
QString lcJid = jid.toLower(); QString lcJid = jid.toLower();
Contact* cnt = new Contact(lcJid, acc->name); Contact* cnt = new Contact(lcJid, acc->name);
contacts.insert(std::make_pair(lcJid, cnt)); contacts.insert(std::make_pair(lcJid, cnt));
outOfRosterContacts.insert(lcJid); outOfRosterContacts.insert(lcJid);
cnt->setSubscriptionState(Shared::SubscriptionState::unknown); cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
emit acc->addContact(lcJid, "", QMap<QString, QVariant>({ emit acc->addContact(lcJid, "", QMap<QString, QVariant>({
{"state", QVariant::fromValue(Shared::SubscriptionState::unknown)} {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)},
{"encryption", QVariant::fromValue(Shared::EncryptionProtocol::none)}
})); }));
handleNewContact(cnt); handleNewContact(cnt);
return cnt; return cnt;
} }
void Core::RosterHandler::onRosterItemChanged(const QString& bareJid) void Core::RosterHandler::onRosterItemChanged(const QString& bareJid) {
{
QString lcJid = bareJid.toLower(); QString lcJid = bareJid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid); std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) { if (itr == contacts.end()) {
@ -391,8 +348,7 @@ void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
contact->setName(re.name()); contact->setName(re.name());
} }
void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid) void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid) {
{
QString lcJid = bareJid.toLower(); QString lcJid = bareJid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid); std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) { if (itr == contacts.end()) {
@ -402,22 +358,20 @@ void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid)
Contact* contact = itr->second; Contact* contact = itr->second;
contacts.erase(itr); contacts.erase(itr);
QSet<QString> cGroups = contact->getGroups(); QSet<QString> cGroups = contact->getGroups();
for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) { for (const QString& group : cGroups)
removeFromGroup(lcJid, *itr); removeFromGroup(lcJid, group);
}
emit acc->removeContact(lcJid); emit acc->removeContact(lcJid);
contact->deleteLater(); contact->deleteLater();
} }
void Core::RosterHandler::onMucRoomAdded(QXmppMucRoom* room) void Core::RosterHandler::onMucRoomAdded(QXmppMucRoom* room) {
{
qDebug() << "room" << room->jid() << "added with name" << room->name() qDebug() << "room" << room->jid() << "added with name" << room->name()
<< ", account" << acc->getName() << "joined:" << room->isJoined(); << ", account" << acc->getName() << "joined:" << room->isJoined();
} }
void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks) void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks) {
{
QList<QXmppBookmarkConference> confs = bookmarks.conferences(); QList<QXmppBookmarkConference> confs = bookmarks.conferences();
for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) { for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) {
const QXmppBookmarkConference& c = *itr; const QXmppBookmarkConference& c = *itr;
@ -427,54 +381,42 @@ void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
if (cItr == conferences.end()) { if (cItr == conferences.end()) {
addNewRoom(jid, c.nickName(), c.name(), c.autoJoin()); addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
} else { } else {
if (c.autoJoin()) { if (c.autoJoin())
cItr->second->setJoined(true); cItr->second->setJoined(true);
} else { else
cItr->second->setAutoJoin(false); cItr->second->setAutoJoin(false);
}
} }
} }
} }
void Core::RosterHandler::onMucJoinedChanged(bool joined) void Core::RosterHandler::onMucJoinedChanged(bool joined){
{
Conference* room = static_cast<Conference*>(sender()); Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, { emit acc->changeRoom(room->jid, {{"joined", joined}});
{"joined", joined}
});
} }
void Core::RosterHandler::onMucAutoJoinChanged(bool autoJoin) void Core::RosterHandler::onMucAutoJoinChanged(bool autoJoin) {
{
storeConferences(); storeConferences();
Conference* room = static_cast<Conference*>(sender()); Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, { emit acc->changeRoom(room->jid, {{"autoJoin", autoJoin}});
{"autoJoin", autoJoin}
});
} }
void Core::RosterHandler::onMucNickNameChanged(const QString& nickName) void Core::RosterHandler::onMucNickNameChanged(const QString& nickName){
{
storeConferences(); storeConferences();
Conference* room = static_cast<Conference*>(sender()); Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, { emit acc->changeRoom(room->jid, {{"nick", nickName}});
{"nick", nickName}
});
} }
Shared::SubscriptionState Core::RosterHandler::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) Shared::SubscriptionState Core::RosterHandler::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs){
{
Shared::SubscriptionState state; Shared::SubscriptionState state;
if (qs == QXmppRosterIq::Item::NotSet) { if (qs == QXmppRosterIq::Item::NotSet)
state = Shared::SubscriptionState::unknown; state = Shared::SubscriptionState::unknown;
} else { else
state = static_cast<Shared::SubscriptionState>(qs); state = static_cast<Shared::SubscriptionState>(qs);
}
return state; return state;
} }
void Core::RosterHandler::storeConferences() void Core::RosterHandler::storeConferences() {
{
QXmppBookmarkSet bms = acc->bm->bookmarks(); QXmppBookmarkSet bms = acc->bm->bookmarks();
QList<QXmppBookmarkConference> confs; QList<QXmppBookmarkConference> confs;
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) { for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
@ -490,8 +432,7 @@ void Core::RosterHandler::storeConferences()
acc->bm->setBookmarks(bms); acc->bm->setBookmarks(bms);
} }
void Core::RosterHandler::clearConferences() void Core::RosterHandler::clearConferences() {
{
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) { for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) {
itr->second->deleteLater(); itr->second->deleteLater();
emit acc->removeRoom(itr->first); emit acc->removeRoom(itr->first);
@ -499,21 +440,20 @@ void Core::RosterHandler::clearConferences()
conferences.clear(); conferences.clear();
} }
void Core::RosterHandler::removeRoomRequest(const QString& jid) void Core::RosterHandler::removeRoomRequest(const QString& jid) {
{
QString lcJid = jid.toLower(); QString lcJid = jid.toLower();
std::map<QString, Conference*>::const_iterator itr = conferences.find(lcJid); std::map<QString, Conference*>::const_iterator itr = conferences.find(lcJid);
if (itr == conferences.end()) { if (itr == conferences.end())
qDebug() << "An attempt to remove non existing room" << lcJid << "from account" << acc->name << ", skipping"; qDebug() << "An attempt to remove non existing room" << lcJid << "from account" << acc->name << ", skipping";
}
itr->second->deleteLater(); itr->second->deleteLater();
conferences.erase(itr); conferences.erase(itr);
emit acc->removeRoom(lcJid); emit acc->removeRoom(lcJid);
storeConferences(); storeConferences();
} }
void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin) void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin) {
{ SHARED_UNUSED(password);
QString lcJid = jid.toLower(); QString lcJid = jid.toLower();
std::map<QString, Conference*>::const_iterator cItr = conferences.find(lcJid); std::map<QString, Conference*>::const_iterator cItr = conferences.find(lcJid);
if (cItr == conferences.end()) { if (cItr == conferences.end()) {
@ -524,8 +464,7 @@ void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick
} }
} }
void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName) void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName) {
{
QString lcJid = jid.toLower(); QString lcJid = jid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid); std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) { if (itr == contacts.end()) {
@ -549,45 +488,43 @@ void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QSt
} }
} }
void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName) void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName) {
{
QString lcJid = jid.toLower(); QString lcJid = jid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid); std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) { if (itr == contacts.end()) {
qDebug() << "An attempt to remove non existing contact" << lcJid << "of account" qDebug() << "An attempt to remove non existing contact" << lcJid << "of account"
<< acc->name << "from the group" << groupName << ", skipping"; << acc->name << "from the group" << groupName << ", skipping";
return;
}
QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
QSet<QString> groups = item.groups();
QSet<QString>::const_iterator gItr = groups.find(groupName);
if (gItr != groups.end()) {
groups.erase(gItr);
item.setGroups(groups);
QXmppRosterIq iq;
iq.setType(QXmppIq::Set);
iq.addItem(item);
acc->client.sendPacket(iq);
} else { } else {
QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid); qDebug() << "An attempt to remove contact" << lcJid << "of account"
QSet<QString> groups = item.groups(); << acc->name << "from the group" << groupName << "but it's not in that group, skipping";
QSet<QString>::const_iterator gItr = groups.find(groupName);
if (gItr != groups.end()) {
groups.erase(gItr);
item.setGroups(groups);
QXmppRosterIq iq;
iq.setType(QXmppIq::Set);
iq.addItem(item);
acc->client.sendPacket(iq);
} else {
qDebug() << "An attempt to remove contact" << lcJid << "of account"
<< acc->name << "from the group" << groupName << "but it's not in that group, skipping";
}
} }
} }
void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QString& path) void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QString& path) {
{
RosterItem* item = static_cast<RosterItem*>(sender()); RosterItem* item = static_cast<RosterItem*>(sender());
QMap<QString, QVariant> cData({ QMap<QString, QVariant> cData({
{"avatarState", static_cast<uint>(type)}, {"avatarState", QVariant::fromValue(type)},
{"avatarPath", path} {"avatarPath", path}
}); });
emit acc->changeContact(item->jid, cData); emit acc->changeContact(item->jid, cData);
} }
void Core::RosterHandler::handleOffline() void Core::RosterHandler::handleOffline() {
{
for (const std::pair<const QString, Conference*>& pair : conferences) { for (const std::pair<const QString, Conference*>& pair : conferences) {
pair.second->clearArchiveRequests(); pair.second->clearArchiveRequests();
pair.second->downgradeDatabaseState(); pair.second->downgradeDatabaseState();
@ -597,3 +534,12 @@ void Core::RosterHandler::handleOffline()
pair.second->downgradeDatabaseState(); pair.second->downgradeDatabaseState();
} }
} }
void Core::RosterHandler::onPepSupportedChanged(Shared::Support support) {
if (support == Shared::Support::supported) {
for (const std::pair<const QString, Contact*>& pair : contacts) {
if (pair.second->getPepSupport() == Shared::Support::unknown)
acc->dm->requestInfo(pair.first);
}
}
}

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CORE_ROSTERHANDLER_H #pragma once
#define CORE_ROSTERHANDLER_H
#include <QObject> #include <QObject>
#include <QSet> #include <QSet>
@ -27,22 +26,24 @@
#include <list> #include <list>
#include <map> #include <map>
#include <set> #include <set>
#include <optional>
#include <QXmppBookmarkSet.h> #include <QXmppBookmarkSet.h>
#include <QXmppMucManager.h> #include <QXmppMucManager.h>
#include <QXmppRosterIq.h> #include <QXmppRosterIq.h>
#include <shared/enums.h>
#include <shared/message.h> #include <shared/message.h>
#include <shared/trustsummary.h>
#include <core/contact.h> #include <core/contact.h>
#include <core/conference.h> #include <core/conference.h>
#include <core/delayManager/manager.h>
namespace Core { namespace Core {
class Account; class Account;
class RosterHandler : public QObject class RosterHandler : public QObject {
{
Q_OBJECT Q_OBJECT
public: public:
RosterHandler(Account* account); RosterHandler(Account* account);
@ -64,12 +65,16 @@ public:
void storeConferences(); void storeConferences();
void clearConferences(); void clearConferences();
void initialize();
void clear();
private slots: private slots:
void onRosterReceived(); void onRosterReceived();
void onRosterItemAdded(const QString& bareJid); void onRosterItemAdded(const QString& bareJid);
void onRosterItemChanged(const QString& bareJid); void onRosterItemChanged(const QString& bareJid);
void onRosterItemRemoved(const QString& bareJid); void onRosterItemRemoved(const QString& bareJid);
void onTrustChanged(const QString& jid, const Shared::TrustSummary& trust);
void onMucRoomAdded(QXmppMucRoom* room); void onMucRoomAdded(QXmppMucRoom* room);
void onMucJoinedChanged(bool joined); void onMucJoinedChanged(bool joined);
@ -86,8 +91,9 @@ 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);
void onContactEncryptionChanged(Shared::EncryptionProtocol value);
void onPepSupportedChanged(Shared::Support support);
private: private:
void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin); void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
@ -97,7 +103,6 @@ private:
void handleNewRosterItem(Core::RosterItem* contact); void handleNewRosterItem(Core::RosterItem* contact);
void handleNewContact(Core::Contact* contact); void handleNewContact(Core::Contact* contact);
void handleNewConference(Core::Conference* contact); void handleNewConference(Core::Conference* contact);
void careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data);
static Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs); static Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs);
@ -111,5 +116,3 @@ private:
}; };
} }
#endif // CORE_ROSTERHANDLER_H

View File

@ -0,0 +1,463 @@
// 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 "trusthandler.h"
#include "core/account.h"
#include "core/adapterfunctions.h"
Core::TrustHandler::TrustHandler(Account* account):
QObject(),
QXmppTrustStorage(),
acc(account),
db(acc->getName() + "/trust"),
protocols(db.createDirectory() + "/protocols"),
securityPolicies(db.addCache<QString, uint8_t>("securityPolicies")),
ownKeys(db.addCache<QString, QByteArray>("ownKeys")),
keysByProtocol()
{
if (!protocols.open(QIODevice::ReadWrite | QIODevice::Text)) //never supposed to happen since I have just created a directory;
throw LMDBAL::Directory(protocols.fileName().toStdString());
QTextStream in(&protocols);
while(!in.atEnd()) {
QString protocol = in.readLine();
if (protocol.size() > 1) { //I'm afraid of reading some nonsence like separately standing \n or EF or BOM, so... let it be at least 2 chars long
KeyCache* cache = db.addCache<QString, Keys>(protocol.toStdString());
keysByProtocol.insert(std::make_pair(protocol, cache));
}
}
protocols.close();
db.open();
}
Core::TrustHandler::~TrustHandler() {
protocols.close();
db.close();
}
Core::TrustHandler::KeyCache * Core::TrustHandler::getCache(const QString& encryption) {
std::map<QString, KeyCache*>::iterator itr = keysByProtocol.find(encryption);
if (itr == keysByProtocol.end())
return createNewCache(encryption);
else
return itr->second;
}
Core::TrustHandler::KeyCache * Core::TrustHandler::createNewCache(const QString& encryption) {
db.close();
KeyCache* cache = db.addCache<QString, Keys>(encryption.toStdString());
keysByProtocol.insert(std::make_pair(encryption, cache));
if (!protocols.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
throw LMDBAL::Directory(protocols.fileName().toStdString());
QTextStream out(&protocols);
out << encryption + "\n";
protocols.close();
db.open();
return cache;
}
QXmppTask<void> Core::TrustHandler::resetAll(const QString& encryption) {
securityPolicies->removeRecord(encryption);
ownKeys->removeRecord(encryption);
std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(encryption);
if (itr == keysByProtocol.end())
return Core::makeReadyTask();
LMDBAL::WriteTransaction txn = db.beginTransaction();
KeyCache* cache = itr->second;
std::map<QString, Keys> keys = cache->readAll(txn);
cache->drop(txn);
txn.commit();
for (const std::pair<const QString, Keys>& pair : keys) {
bool empty = true;
for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : pair.second) {
if (trust.second != Shared::TrustLevel::undecided) {
empty = false;
break;
}
}
if (!empty)
emit trustLevelsChanged(pair.first, getSummary(pair.first));
}
return Core::makeReadyTask();
}
QXmppTask<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
const QString& encryption,
const QString& keyOwnerJid,
const QByteArray& keyId
) {
KeyCache* cache = getCache(encryption);
Shared::TrustLevel level = Shared::TrustLevel::undecided;
try {
Keys map = cache->getRecord(keyOwnerJid);
Keys::const_iterator itr = map.find(keyId);
if (itr != map.end())
level = itr->second;
} catch (const LMDBAL::NotFound& e) {}
return Core::makeReadyTask(std::move(convert(level)));
}
QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
const QString& encryption,
const QList<QString>& keyOwnerJids,
QXmpp::TrustLevel oldTrustLevel,
QXmpp::TrustLevel newTrustLevel
) {
QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
Shared::TrustLevel oldLevel = convert(oldTrustLevel);
Shared::TrustLevel newLevel = convert(newTrustLevel);
std::set<QString> modifiedJids;
KeyCache* cache = getCache(encryption);
LMDBAL::WriteTransaction txn = db.beginTransaction();
for (const QString& keyOwnerJid : keyOwnerJids) {
Keys map = cache->getRecord(keyOwnerJid, txn);
uint count = 0;
for (std::pair<const QByteArray, Shared::TrustLevel>& pair : map) {
Shared::TrustLevel& current = pair.second;
if (current == oldLevel) {
current = newLevel;
modifiedKeys[encryption].insert(keyOwnerJid, pair.first);
modifiedJids.insert(keyOwnerJid);
++count;
}
}
if (count > 0)
cache->changeRecord(keyOwnerJid, map, txn);
}
txn.commit();
for (const QString& jid : modifiedJids)
emit trustLevelsChanged(jid, getSummary(jid));
return Core::makeReadyTask(std::move(modifiedKeys));
}
QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
const QString& encryption,
const QMultiHash<QString, QByteArray>& keyIds,
QXmpp::TrustLevel trustLevel
) {
QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
Shared::TrustLevel level = convert(trustLevel);
std::set<QString> modifiedJids;
KeyCache* cache = getCache(encryption);
LMDBAL::WriteTransaction txn = db.beginTransaction();
for (MultySB::const_iterator itr = keyIds.begin(), end = keyIds.end(); itr != end; ++itr) {
const QString& keyOwnerJid = itr.key();
const QByteArray& keyId = itr.value();
try {
Keys map = cache->getRecord(keyOwnerJid, txn);
std::pair<Keys::iterator, bool> result = map.insert(std::make_pair(keyId, level));
bool changed = result.second;
if (!changed && result.first->second != level) {
result.first->second = level;
changed = true;
}
if (changed) {
modifiedKeys[encryption].insert(keyOwnerJid, keyId);
modifiedJids.insert(keyOwnerJid);
cache->changeRecord(keyOwnerJid, map, txn);
}
} catch (const LMDBAL::NotFound& e) {
Keys map({{keyId, level}});
modifiedKeys[encryption].insert(keyOwnerJid, keyId);
modifiedJids.insert(keyOwnerJid);
cache->addRecord(keyOwnerJid, map, txn);
}
}
txn.commit();
for (const QString& jid : modifiedJids)
emit trustLevelsChanged(jid, getSummary(jid));
return Core::makeReadyTask(std::move(modifiedKeys));
}
QXmppTask<bool> Core::TrustHandler::hasKey(
const QString& encryption,
const QString& keyOwnerJid,
QXmpp::TrustLevels trustLevels
) {
KeyCache* cache = getCache(encryption);
bool found = false;
try {
Keys map = cache->getRecord(keyOwnerJid);
for (const std::pair<const QByteArray, Shared::TrustLevel>& pair : map) {
if (trustLevels.testFlag(convert(pair.second))) {
found = true;
break;
}
}
} catch (const LMDBAL::NotFound& e) {}
return Core::makeReadyTask(std::move(found));
}
QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> Core::TrustHandler::keys(
const QString& encryption,
const QList<QString>& keyOwnerJids,
QXmpp::TrustLevels trustLevels
) {
HSHBTL res;
KeyCache* cache = getCache(encryption);
for (const QString& keyOwnerJid : keyOwnerJids) {
try {
Keys map = cache->getRecord(keyOwnerJid);
QHash<QByteArray, QXmpp::TrustLevel>& pRes = res[keyOwnerJid];
for (const std::pair<const QByteArray, Shared::TrustLevel>& pair : map) {
QXmpp::TrustLevel level = convert(pair.second);
if (!trustLevels || trustLevels.testFlag(level)) {
pRes.insert(pair.first, level);
}
}
} catch (const LMDBAL::NotFound& e) {}
}
return Core::makeReadyTask(std::move(res));
}
QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> Core::TrustHandler::keys(
const QString& encryption,
QXmpp::TrustLevels trustLevels
) {
QHash<TL, MultySB> res;
KeyCache* cache = getCache(encryption);
std::map<QString, Keys> storage = cache->readAll();
for (const std::pair<const QString, Keys>& value : storage) {
for (const std::pair<const QByteArray, Shared::TrustLevel>& pair : value.second) {
QXmpp::TrustLevel level = convert(pair.second);
if (!trustLevels || trustLevels.testFlag(level))
res[level].insert(value.first, pair.first);
}
}
return Core::makeReadyTask(std::move(res));
}
QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption) {
std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(encryption);
if (itr == keysByProtocol.end())
return Core::makeReadyTask();
LMDBAL::WriteTransaction txn = db.beginTransaction();
KeyCache* cache = itr->second;
std::map<QString, Keys> keys = cache->readAll(txn);
cache->drop(txn);
txn.commit();
for (const std::pair<const QString, Keys>& pair : keys) {
bool empty = true;
for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : pair.second) {
if (trust.second != Shared::TrustLevel::undecided) {
empty = false;
break;
}
}
if (!empty)
emit trustLevelsChanged(pair.first, getSummary(pair.first));
}
return Core::makeReadyTask();
}
QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const QString& keyOwnerJid) {
std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(encryption);
if (itr == keysByProtocol.end())
return Core::makeReadyTask();
KeyCache* cache = itr->second;
try {
cache->removeRecord(keyOwnerJid);
emit trustLevelsChanged(keyOwnerJid, getSummary(keyOwnerJid)); //TODO there is a probability of notification without the actial change
} catch (const LMDBAL::NotFound& e) {} //if the movin entry was empty or if it consisted of Undecided keys
return Core::makeReadyTask();
}
QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const QList<QByteArray>& keyIds) {
std::set<QByteArray> set;
for (const QByteArray& keyId : keyIds)
set.insert(keyId);
LMDBAL::WriteTransaction txn = db.beginTransaction();
KeyCache* cache = getCache(encryption);
std::map<QString, Keys> data = cache->readAll(txn);
std::set<QString> modifiedJids;
for (std::map<QString, Keys>::iterator cItr = data.begin(), cEnd = data.end(); cItr != cEnd; /*no increment*/) {
Keys& byOwner = cItr->second;
for (Keys::const_iterator itr = byOwner.begin(), end = byOwner.end(); itr != end; /*no increment*/) {
const QByteArray& keyId = itr->first;
if (set.erase(keyId)) {
byOwner.erase(itr++);
modifiedJids.insert(cItr->first);
} else
++itr;
}
if (byOwner.size() > 0)
data.erase(cItr++);
else
++cItr;
}
if (modifiedJids.size() > 0)
cache->replaceAll(data, txn);
txn.commit();
for (const QString& jid : modifiedJids)
emit trustLevelsChanged(jid, getSummary(jid));
return Core::makeReadyTask();
}
QXmppTask<void> Core::TrustHandler::addKeys(
const QString& encryption,
const QString& keyOwnerJid,
const QList<QByteArray>& keyIds,
QXmpp::TrustLevel trustLevel
) {
KeyCache* cache = getCache(encryption);
Shared::TrustLevel level = convert(trustLevel);
Keys data;
bool had = false;
LMDBAL::WriteTransaction txn = db.beginTransaction();
try {
data = cache->getRecord(keyOwnerJid, txn);
had = true;
} catch (const LMDBAL::NotFound& e) {}
for (const QByteArray& keyId : keyIds) {
std::pair<Keys::iterator, bool> result = data.insert(std::make_pair(keyId, level));
if (!result.second)
result.first->second = level;
}
if (had)
cache->changeRecord(keyOwnerJid, data, txn);
else
cache->addRecord(keyOwnerJid, data, txn);
txn.commit();
emit trustLevelsChanged(keyOwnerJid, getSummary(keyOwnerJid));
return Core::makeReadyTask();
}
QXmppTask<QByteArray> Core::TrustHandler::ownKey(const QString& encryption) {
QByteArray res;
try {
res = ownKeys->getRecord(encryption);
} catch (const LMDBAL::NotFound& e) {}
return Core::makeReadyTask(std::move(res));
}
QXmppTask<void> Core::TrustHandler::resetOwnKey(const QString& encryption) {
try {
ownKeys->removeRecord(encryption);
} catch (const LMDBAL::NotFound& e) {}
return Core::makeReadyTask();
}
QXmppTask<void> Core::TrustHandler::setOwnKey(const QString& encryption, const QByteArray& keyId) {
ownKeys->forceRecord(encryption, keyId);
return Core::makeReadyTask();
}
QXmppTask<QXmpp::TrustSecurityPolicy> Core::TrustHandler::securityPolicy(const QString& encryption) {
QXmpp::TrustSecurityPolicy res;
try {
res = static_cast<QXmpp::TrustSecurityPolicy>(securityPolicies->getRecord(encryption));
} catch (const LMDBAL::NotFound& e) {}
return Core::makeReadyTask(std::move(res));
}
QXmppTask<void> Core::TrustHandler::resetSecurityPolicy(const QString& encryption) {
try {
securityPolicies->removeRecord(encryption);
} catch (const LMDBAL::NotFound& e) {}
return Core::makeReadyTask();
}
QXmppTask<void> Core::TrustHandler::setSecurityPolicy(
const QString& encryption,
QXmpp::TrustSecurityPolicy securityPolicy)
{
uint8_t pol = securityPolicy;
securityPolicies->forceRecord(encryption, pol);
return Core::makeReadyTask();
}
Core::TrustHandler::Keys Core::TrustHandler::getKeys(Shared::EncryptionProtocol protocol, const QString& jid) const {
const QString& prt = Shared::TrustSummary::protocolKeys.at(protocol);
std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(prt);
if (itr != keysByProtocol.end()) {
try {
Keys map = itr->second->getRecord(jid);
return map;
} catch (const LMDBAL::NotFound& e) {
return Keys();
}
} else {
return Keys();
}
}
Shared::TrustSummary Core::TrustHandler::getSummary(const QString& jid) const {
Shared::TrustSummary result;
for (const std::pair<const QString, KeyCache*>& pair : keysByProtocol) {
try {
Keys keys = pair.second->getRecord(jid);
Shared::EncryptionProtocol protocol = Shared::TrustSummary::protocolValues.at(pair.first);
for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : keys)
result.increment(protocol, trust.second);
} catch (const LMDBAL::NotFound& e) {}
}
return result;
}
Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level) {
switch (level) {
case QXmpp::TrustLevel::Undecided: return Shared::TrustLevel::undecided;
case QXmpp::TrustLevel::AutomaticallyDistrusted: return Shared::TrustLevel::automaticallyDistrusted;
case QXmpp::TrustLevel::ManuallyDistrusted: return Shared::TrustLevel::manuallyDistrusted;
case QXmpp::TrustLevel::AutomaticallyTrusted: return Shared::TrustLevel::automaticallyTrusted;
case QXmpp::TrustLevel::ManuallyTrusted: return Shared::TrustLevel::manuallyTrusted;
case QXmpp::TrustLevel::Authenticated: return Shared::TrustLevel::authenticated;
default: throw 2413; //never supposed to get here, switch case if complete, this line is just to suppress a warning
}
}
Core::TrustHandler::TL Core::TrustHandler::convert(Shared::TrustLevel level) {
switch (level) {
case Shared::TrustLevel::undecided: return QXmpp::TrustLevel::Undecided;
case Shared::TrustLevel::automaticallyDistrusted: return QXmpp::TrustLevel::AutomaticallyDistrusted;
case Shared::TrustLevel::manuallyDistrusted: return QXmpp::TrustLevel::ManuallyDistrusted;
case Shared::TrustLevel::automaticallyTrusted: return QXmpp::TrustLevel::AutomaticallyTrusted;
case Shared::TrustLevel::manuallyTrusted: return QXmpp::TrustLevel::ManuallyTrusted;
case Shared::TrustLevel::authenticated: return QXmpp::TrustLevel::Authenticated;
default: throw 2413; //never supposed to get here, switch case if complete, this line is just to suppress a warning
}
}

View File

@ -0,0 +1,89 @@
// 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_TRUSTHANDLER_H
#define CORE_TRUSTHANDLER_H
#include <shared/enums.h>
#include <shared/trustsummary.h>
#include <QXmppTrustStorage.h>
#include <cache.h>
namespace Core {
class Account;
class TrustHandler : public QObject, public QXmppTrustStorage {
Q_OBJECT
public:
TrustHandler(Account* account);
~TrustHandler();
signals:
void trustLevelsChanged(const QString& jid, const Shared::TrustSummary& summary) const;
public:
typedef QMultiHash<QString, QByteArray> MultySB;
typedef QHash<QString, MultySB> HashSM;
typedef const QList<QString>& CLSR;
typedef const QList<QByteArray>& CLBAR;
typedef const QString& CSR;
typedef QXmpp::TrustLevel TL;
typedef QHash<QString, QHash<QByteArray, TL>> HSHBTL;
typedef std::map<QByteArray, Shared::TrustLevel> Keys;
typedef LMDBAL::Cache<QString, Keys> KeyCache;
virtual QXmppTask<void> resetAll(CSR encryption) override;
virtual QXmppTask<TL> trustLevel(CSR encryption, CSR keyOwnerJid, const QByteArray& keyId) override;
virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, CLSR keyOwnerJids, TL oldTrustLevel, TL newTrustLevel) override;
virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, const MultySB& keyIds, TL trustLevel) override;
virtual QXmppTask<bool> hasKey(CSR encryption, CSR keyOwnerJid, QXmpp::TrustLevels trustLevels) override;
virtual QXmppTask<HSHBTL> keys(CSR encryption, CLSR keyOwnerJids, QXmpp::TrustLevels trustLevels) override;
virtual QXmppTask<QHash<TL, MultySB>> keys(CSR encryption, QXmpp::TrustLevels trustLevels) override;
virtual QXmppTask<void> removeKeys(CSR encryption) override;
virtual QXmppTask<void> removeKeys(CSR encryption, CSR keyOwnerJid) override;
virtual QXmppTask<void> removeKeys(CSR encryption, CLBAR keyIds) override;
virtual QXmppTask<void> addKeys(CSR encryption, CSR keyOwnerJid, CLBAR keyIds, TL trustLevel) override;
virtual QXmppTask<QByteArray> ownKey(CSR encryption) override;
virtual QXmppTask<void> resetOwnKey(CSR encryption) override;
virtual QXmppTask<void> setOwnKey(CSR encryption, const QByteArray& keyId) override;
virtual QXmppTask<QXmpp::TrustSecurityPolicy> securityPolicy(CSR encryption) override;
virtual QXmppTask<void> resetSecurityPolicy(CSR encryption) override;
virtual QXmppTask<void> setSecurityPolicy(const QString& encryption, QXmpp::TrustSecurityPolicy securityPolicy) override;
static TL convert(Shared::TrustLevel level);
static Shared::TrustLevel convert(TL level);
Keys getKeys(Shared::EncryptionProtocol protocol, const QString& jid) const;
Shared::TrustSummary getSummary(const QString& jid) const;
private:
KeyCache* createNewCache(const QString& encryption);
KeyCache* getCache(const QString& encryption);
private:
Account* acc;
LMDBAL::Base db;
QFile protocols;
LMDBAL::Cache<QString, uint8_t>* securityPolicies;
LMDBAL::Cache<QString, QByteArray>* ownKeys;
std::map<QString, KeyCache*> keysByProtocol;
};
}
#endif // CORE_TRUSTHANDLER_H

View File

@ -0,0 +1,270 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "vcardhandler.h"
#include "core/account.h"
Core::VCardHandler::VCardHandler(Account* account):
QObject(),
acc(account),
avatarHash(),
avatarType()
{
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + acc->name;
QDir dir(path);
if (!dir.exists()) {
bool res = dir.mkpath(path);
if (!res) {
qDebug() << "Couldn't create a cache directory for account" << acc->name;
throw 22;
}
}
QFile* avatar = new QFile(path + "/avatar.png");
QString type = "png";
if (!avatar->exists()) {
delete avatar;
avatar = new QFile(path + "/avatar.jpg");
type = "jpg";
if (!avatar->exists()) {
delete avatar;
avatar = new QFile(path + "/avatar.jpeg");
type = "jpeg";
if (!avatar->exists()) {
delete avatar;
avatar = new QFile(path + "/avatar.gif");
type = "gif";
}
}
}
if (avatar->exists()) {
if (avatar->open(QFile::ReadOnly)) {
QCryptographicHash sha1(QCryptographicHash::Sha1);
sha1.addData(avatar);
avatarHash = sha1.result();
avatarType = type;
}
}
}
Core::VCardHandler::~VCardHandler() {}
void Core::VCardHandler::initialize() {
connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived);
//for some reason it doesn't work, launching from common handler
//connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived);
if (avatarType.size() != 0) {
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
acc->presence.setPhotoHash(avatarHash.toUtf8());
} else {
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
}
}
void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) {
QString id = card.from();
QStringList comps = id.split("/");
QString jid = comps.front().toLower();
QString resource("");
if (comps.size() > 1) {
resource = comps.back();
}
RosterItem* item = acc->rh->getRosterItem(jid);
if (item == nullptr) {
if (jid == acc->getBareJid())
onOwnVCardReceived(card);
else
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
return;
}
Shared::VCard vCard;
item->handleResponseVCard(card, resource, vCard);
acc->delay->receivedVCard(id, vCard);
}
void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
QByteArray ava = card.photo();
bool avaChanged = false;
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/";
if (ava.size() > 0) {
QCryptographicHash sha1(QCryptographicHash::Sha1);
sha1.addData(ava);
QString newHash(sha1.result());
QMimeDatabase db;
QMimeType newType = db.mimeTypeForData(ava);
if (avatarType.size() > 0) {
if (avatarHash != newHash) {
QString oldPath = path + "avatar." + avatarType;
QFile oldAvatar(oldPath);
bool oldToRemove = false;
if (oldAvatar.exists()) {
if (oldAvatar.rename(oldPath + ".bak")) {
oldToRemove = true;
} else {
qDebug() << "Received new avatar for account" << acc->name << "but can't get rid of the old one, doing nothing";
}
}
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
if (newAvatar.open(QFile::WriteOnly)) {
newAvatar.write(ava);
newAvatar.close();
avatarHash = newHash;
avatarType = newType.preferredSuffix();
avaChanged = true;
} else {
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
if (oldToRemove) {
qDebug() << "rolling back to the old avatar";
if (!oldAvatar.rename(oldPath)) {
qDebug() << "Couldn't roll back to the old avatar in account" << acc->name;
}
}
}
}
} else {
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
if (newAvatar.open(QFile::WriteOnly)) {
newAvatar.write(ava);
newAvatar.close();
avatarHash = newHash;
avatarType = newType.preferredSuffix();
avaChanged = true;
} else {
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
}
}
} else {
if (avatarType.size() > 0) {
QFile oldAvatar(path + "avatar." + avatarType);
if (!oldAvatar.remove()) {
qDebug() << "Received vCard for account" << acc->name << "without avatar, but can't get rid of the file, doing nothing";
} else {
avatarType = "";
avatarHash = "";
avaChanged = true;
}
}
}
if (avaChanged) {
QMap<QString, QVariant> change;
if (avatarType.size() > 0) {
acc->presence.setPhotoHash(avatarHash.toUtf8());
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
change.insert("avatarPath", path + "avatar." + avatarType);
} else {
acc->presence.setPhotoHash("");
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
change.insert("avatarPath", "");
}
acc->client.setClientPresence(acc->presence);
emit acc->changed(change);
}
Shared::VCard vCard;
initializeVCard(vCard, card);
if (avatarType.size() > 0) {
vCard.setAvatarType(Shared::Avatar::valid);
vCard.setAvatarPath(path + "avatar." + avatarType);
} else {
vCard.setAvatarType(Shared::Avatar::empty);
}
if (acc->delay->isOwnVCardPending())
acc->delay->receivedOwnVCard(vCard);
}
void Core::VCardHandler::handlePresenceOfMyAccountChange(const QXmppPresence& p_presence) {
if (!acc->delay->isOwnVCardPending()) {
switch (p_presence.vCardUpdateType()) {
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
break;
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
break;
case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
if (avatarType.size() > 0)
acc->delay->getOwnVCard();
break;
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
if (avatarHash != p_presence.photoHash())
acc->delay->getOwnVCard();
break;
}
}
}
void Core::VCardHandler::uploadVCard(const Shared::VCard& card) {
QXmppVCardIq iq;
initializeQXmppVCard(iq, card);
if (card.getAvatarType() != Shared::Avatar::empty) {
QString newPath = card.getAvatarPath();
QString oldPath = getAvatarPath();
QByteArray data;
QString type;
if (newPath != oldPath) {
QFile avatar(newPath);
if (!avatar.open(QFile::ReadOnly)) {
qDebug() << "An attempt to upload new vCard to account" << acc->name
<< "but it wasn't possible to read file" << newPath
<< "which was supposed to be new avatar, uploading old avatar";
if (avatarType.size() > 0) {
QFile oA(oldPath);
if (!oA.open(QFile::ReadOnly)) {
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
} else {
data = oA.readAll();
}
}
} else {
data = avatar.readAll();
}
} else {
if (avatarType.size() > 0) {
QFile oA(oldPath);
if (!oA.open(QFile::ReadOnly))
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
else
data = oA.readAll();
}
}
if (data.size() > 0) {
QMimeDatabase db;
type = db.mimeTypeForData(data).name();
iq.setPhoto(data);
iq.setPhotoType(type);
}
}
acc->vm->setClientVCard(iq);
onOwnVCardReceived(iq);
}
QString Core::VCardHandler::getAvatarPath() const {
if (avatarType.size() == 0)
return "";
else
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType;
}

View File

@ -0,0 +1,63 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef CORE_VCARDHANDLER_H
#define CORE_VCARDHANDLER_H
#include <set>
#include <QXmppVCardIq.h>
#include <QXmppPresence.h>
#include <QObject>
#include <shared/vcard.h>
#include <core/adapterfunctions.h>
/**
* @todo write docs
*/
namespace Core {
class Account;
class VCardHandler : public QObject
{
Q_OBJECT
public:
VCardHandler(Account* account);
~VCardHandler();
void handlePresenceOfMyAccountChange(const QXmppPresence& p_presence);
void uploadVCard(const Shared::VCard& card);
QString getAvatarPath() const;
void initialize();
private slots:
void onVCardReceived(const QXmppVCardIq& card);
void onOwnVCardReceived(const QXmppVCardIq& card);
private:
Account* acc;
QString avatarHash;
QString avatarType;
};
}
#endif // CORE_VCARDHANDLER_H

View File

@ -1,37 +1,6 @@
cmake_minimum_required(VERSION 3.0) if (WITH_KWALLET)
project(pse) target_sources(squawk PRIVATE kwallet.cpp)
if (WITH_KWALLET) add_subdirectory(wrappers)
set(CMAKE_AUTOMOC ON) target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF${QT_VERSION_MAJOR}::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
endif ()
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
)
add_library(kwalletPSE ${kwalletPSE_SRC})
target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
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

@ -28,7 +28,8 @@ Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0;
Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0; Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0;
Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial; Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial;
QLibrary Core::PSE::KWallet::lib("kwalletWrapper");
QLibrary Core::PSE::KWallet::lib(QString("%1/kwalletWrapper").arg(PLUGIN_PATH));
Core::PSE::KWallet::KWallet(): Core::PSE::KWallet::KWallet():
QObject(), QObject(),

View File

@ -27,7 +27,7 @@
#include <map> #include <map>
#include <set> #include <set>
#include <KF5/KWallet/KWallet> #include <KWallet>
namespace Core { namespace Core {
namespace PSE { namespace PSE {

View File

@ -0,0 +1,5 @@
add_library(kwalletWrapper SHARED kwallet.cpp)
target_link_libraries(kwalletWrapper PRIVATE KF${QT_VERSION_MAJOR}::Wallet)
install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)

View File

@ -1,4 +1,22 @@
#include <KF5/KWallet/KWallet> /*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <KWallet>
extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) { extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
return KWallet::Wallet::openWallet(name, w, ot); return KWallet::Wallet::openWallet(name, w, ot);

View File

@ -27,7 +27,7 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObje
account(pAccount), account(pAccount),
name(), name(),
archiveState(empty), archiveState(empty),
archive(new Archive(jid)), archive(new Archive(account, jid)),
syncronizing(false), syncronizing(false),
requestedCount(0), requestedCount(0),
requestedBefore(), requestedBefore(),
@ -38,42 +38,36 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObje
toCorrect(), toCorrect(),
muc(false) muc(false)
{ {
archive->open(account); archive->open();
if (archive->size() != 0) { if (archive->size() != 0) {
if (archive->isFromTheBeginning()) { if (archive->isFromTheBeginning())
archiveState = beginning; archiveState = beginning;
} else { else
archiveState = chunk; archiveState = chunk;
}
} }
} }
Core::RosterItem::~RosterItem() Core::RosterItem::~RosterItem() {
{
delete archive; delete archive;
} }
Core::RosterItem::ArchiveState Core::RosterItem::getArchiveState() const Core::RosterItem::ArchiveState Core::RosterItem::getArchiveState() const {
{
return archiveState; return archiveState;
} }
QString Core::RosterItem::getName() const QString Core::RosterItem::getName() const {
{
return name; return name;
} }
void Core::RosterItem::setName(const QString& n) void Core::RosterItem::setName(const QString& n) {
{
if (name != n) { if (name != n) {
name = n; name = n;
emit nameChanged(name); emit nameChanged(name);
} }
} }
void Core::RosterItem::addMessageToArchive(const Shared::Message& msg) void Core::RosterItem::addMessageToArchive(const Shared::Message& msg) {
{
if (msg.storable()) { if (msg.storable()) {
hisoryCache.push_back(msg); hisoryCache.push_back(msg);
std::map<QString, Shared::Message>::iterator itr = toCorrect.find(msg.getId()); std::map<QString, Shared::Message>::iterator itr = toCorrect.find(msg.getId());
@ -87,8 +81,7 @@ void Core::RosterItem::addMessageToArchive(const Shared::Message& msg)
} }
} }
void Core::RosterItem::correctMessageInArchive(const QString& originalId, const Shared::Message& msg) void Core::RosterItem::correctMessageInArchive(const QString& originalId, const Shared::Message& msg) {
{
if (msg.storable()) { if (msg.storable()) {
QDateTime thisTime = msg.getTime(); QDateTime thisTime = msg.getTime();
std::map<QString, Shared::Message>::iterator itr = toCorrect.find(originalId); std::map<QString, Shared::Message>::iterator itr = toCorrect.find(originalId);
@ -109,8 +102,7 @@ void Core::RosterItem::correctMessageInArchive(const QString& originalId, const
} }
} }
void Core::RosterItem::requestHistory(int count, const QString& before) void Core::RosterItem::requestHistory(int count, const QString& before) {
{
if (syncronizing) { if (syncronizing) {
requestCache.emplace_back(count, before); requestCache.emplace_back(count, before);
} else { } else {
@ -118,11 +110,30 @@ void Core::RosterItem::requestHistory(int count, const QString& before)
} }
} }
void Core::RosterItem::nextRequest() 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) {
try {
QString firstId = archive->oldestId();
if (responseCache.size() == 0) {
if (requestedBefore == firstId) {
last = true;
}
} else {
if (responseCache.front().getId() == firstId) {
last = true;
}
}
//} catch (const Archive::Empty& e) {
} catch (const LMDBAL::NotFound& e) {
last = true;
}
} else if (archiveState == empty && responseCache.size() == 0) {
last = true;
}
emit historyResponse(responseCache, last);
} }
} }
if (requestCache.size() > 0) { if (requestCache.size() > 0) {
@ -138,8 +149,7 @@ void Core::RosterItem::nextRequest()
} }
} }
void Core::RosterItem::performRequest(int count, const QString& before) void Core::RosterItem::performRequest(int count, const QString& before) {
{
syncronizing = true; syncronizing = true;
requestedCount = count; requestedCount = count;
requestedBefore = before; requestedBefore = before;
@ -156,8 +166,13 @@ void Core::RosterItem::performRequest(int count, const QString& before)
requestCache.emplace_back(requestedCount, before); requestCache.emplace_back(requestedCount, before);
requestedCount = -1; requestedCount = -1;
} }
Shared::Message msg = archive->newest(); try {
emit needHistory("", getId(msg), msg.getTime()); Shared::Message msg = archive->newest();
emit needHistory("", getId(msg), msg.getTime());
//} catch (const Archive::Empty& e) {
} catch (const LMDBAL::NotFound& e) { //this can happen when the only message in archive is not server stored (error, for example)
emit needHistory(before, "");
}
} }
break; break;
case end: case end:
@ -173,14 +188,14 @@ void Core::RosterItem::performRequest(int count, const QString& before)
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), lBefore); std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), lBefore);
responseCache.insert(responseCache.begin(), arc.begin(), arc.end()); responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
found = true; found = true;
} catch (const Archive::NotFound& e) { } catch (const LMDBAL::NotFound& e) {
requestCache.emplace_back(requestedCount, before);
requestedCount = -1;
emit needHistory(getId(archive->oldest()), "");
} catch (const Archive::Empty& e) {
requestCache.emplace_back(requestedCount, before); requestCache.emplace_back(requestedCount, before);
requestedCount = -1; requestedCount = -1;
emit needHistory(getId(archive->oldest()), ""); emit needHistory(getId(archive->oldest()), "");
// } catch (const Archive::Empty& e) {
// requestCache.emplace_back(requestedCount, before);
// requestedCount = -1;
// emit needHistory(getId(archive->oldest()), "");
} }
if (found) { if (found) {
@ -213,18 +228,17 @@ void Core::RosterItem::performRequest(int count, const QString& before)
try { try {
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before); std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
responseCache.insert(responseCache.begin(), arc.begin(), arc.end()); responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
} catch (const Archive::NotFound& e) { } catch (const LMDBAL::NotFound& e) {
qDebug("requesting id hasn't been found in archive, skipping");
} catch (const Archive::Empty& e) {
qDebug("requesting id hasn't been found in archive, skipping"); qDebug("requesting id hasn't been found in archive, skipping");
// } catch (const Archive::Empty& e) {
// qDebug("requesting id hasn't been found in archive, skipping");
} }
nextRequest(); nextRequest();
break; break;
} }
} }
QString Core::RosterItem::getId(const Shared::Message& msg) QString Core::RosterItem::getId(const Shared::Message& msg) {
{
QString id; QString id;
if (muc) { if (muc) {
id = msg.getStanzaId(); id = msg.getStanzaId();
@ -234,8 +248,7 @@ QString Core::RosterItem::getId(const Shared::Message& msg)
return id; return id;
} }
void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg) void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg) {
{
if (msg.getId().size() > 0) { if (msg.getId().size() > 0) {
if (msg.storable()) { if (msg.storable()) {
switch (archiveState) { switch (archiveState) {
@ -276,8 +289,7 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
} }
} }
bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data) bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data) {
{
bool found = false; bool found = false;
for (Shared::Message& msg : appendCache) { for (Shared::Message& msg : appendCache) {
if (msg.getId() == id) { if (msg.getId() == id) {
@ -301,7 +313,7 @@ bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVar
try { try {
archive->changeMessage(id, data); archive->changeMessage(id, data);
found = true; found = true;
} catch (const Archive::NotFound& e) { } catch (const LMDBAL::NotFound& e) {
qDebug() << "An attempt to change state to the message" << id << "but it couldn't be found"; qDebug() << "An attempt to change state to the message" << id << "but it couldn't be found";
} }
} }
@ -318,8 +330,7 @@ bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVar
return found; return found;
} }
void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId) void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId) {
{
unsigned int added(0); unsigned int added(0);
if (hisoryCache.size() > 0) { if (hisoryCache.size() > 0) {
added = archive->addElements(hisoryCache); added = archive->addElements(hisoryCache);
@ -360,6 +371,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) {
@ -373,12 +389,10 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before); std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
responseCache.insert(responseCache.begin(), arc.begin(), arc.end()); responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
found = true; found = true;
} catch (const Archive::NotFound& e) { } catch (const LMDBAL::NotFound& e) {
// } 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 {
@ -401,51 +415,43 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
} }
} }
QString Core::RosterItem::getServer() const QString Core::RosterItem::getServer() const {
{
QStringList lst = jid.split("@"); QStringList lst = jid.split("@");
return lst.back(); return lst.back();
} }
bool Core::RosterItem::isMuc() const bool Core::RosterItem::isMuc() const {
{
return muc; return muc;
} }
QString Core::RosterItem::avatarPath(const QString& resource) const QString Core::RosterItem::avatarPath(const QString& resource) const {
{
QString path = folderPath() + "/" + (resource.size() == 0 ? jid : resource); QString path = folderPath() + "/" + (resource.size() == 0 ? jid : resource);
return path; return path;
} }
QString Core::RosterItem::folderPath() const QString Core::RosterItem::folderPath() const {
{
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + account + "/" + jid; path += "/" + account + "/" + jid;
return path; return path;
} }
bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) {
{
bool result = archive->setAvatar(data, info, false, resource); bool result = archive->setAvatar(data, info, false, resource);
if (resource.size() == 0 && result) { if (resource.size() == 0 && result) {
if (data.size() == 0) { if (data.size() == 0)
emit avatarChanged(Shared::Avatar::empty, ""); emit avatarChanged(Shared::Avatar::empty, "");
} else { else
emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + info.type); emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + info.type);
}
} }
return result; return result;
} }
bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource) bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource) {
{
Archive::AvatarInfo info; Archive::AvatarInfo info;
return setAutoGeneratedAvatar(info, resource); return setAutoGeneratedAvatar(info, resource);
} }
bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource) bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource) {
{
QImage image(96, 96, QImage::Format_ARGB32_Premultiplied); QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
QPainter painter(&image); QPainter painter(&image);
quint8 colorIndex = rand() % Shared::colorPalette.size(); quint8 colorIndex = rand() % Shared::colorPalette.size();
@ -455,11 +461,11 @@ bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const Q
f.setBold(true); f.setBold(true);
f.setPixelSize(72); f.setPixelSize(72);
painter.setFont(f); painter.setFont(f);
if (bg.lightnessF() > 0.5) { if (bg.lightnessF() > 0.5)
painter.setPen(Qt::black); painter.setPen(Qt::black);
} else { else
painter.setPen(Qt::white); painter.setPen(Qt::white);
}
painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, resource.size() == 0 ? jid.at(0).toUpper() : resource.at(0).toUpper()); painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, resource.size() == 0 ? jid.at(0).toUpper() : resource.at(0).toUpper());
QByteArray arr; QByteArray arr;
QBuffer stream(&arr); QBuffer stream(&arr);
@ -467,25 +473,22 @@ bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const Q
image.save(&stream, "PNG"); image.save(&stream, "PNG");
stream.close(); stream.close();
bool result = archive->setAvatar(arr, info, true, resource); bool result = archive->setAvatar(arr, info, true, resource);
if (resource.size() == 0 && result) { if (resource.size() == 0 && result)
emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png"); emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png");
}
return result; return result;
} }
bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const {
{
return archive->readAvatarInfo(target, resource); return archive->readAvatarInfo(target, resource);
} }
Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource) void Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource, Shared::VCard& vCard) {
{
Archive::AvatarInfo info; Archive::AvatarInfo info;
Archive::AvatarInfo newInfo; Archive::AvatarInfo newInfo;
bool hasAvatar = readAvatarInfo(info, resource); bool hasAvatar = readAvatarInfo(info, resource);
QByteArray ava = card.photo(); QByteArray ava = card.photo();
Shared::VCard vCard;
initializeVCard(vCard, card); initializeVCard(vCard, card);
Shared::Avatar type = Shared::Avatar::empty; Shared::Avatar type = Shared::Avatar::empty;
QString path = ""; QString path = "";
@ -505,9 +508,9 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
} }
} }
} else { } else {
if (!hasAvatar || !info.autogenerated) { if (!hasAvatar || !info.autogenerated)
setAutoGeneratedAvatar(resource); setAutoGeneratedAvatar(resource);
}
type = Shared::Avatar::autocreated; type = Shared::Avatar::autocreated;
path = avatarPath(resource) + ".png"; path = avatarPath(resource) + ".png";
} }
@ -515,21 +518,17 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
vCard.setAvatarType(type); vCard.setAvatarType(type);
vCard.setAvatarPath(path); vCard.setAvatarPath(path);
if (resource.size() == 0) { if (resource.size() == 0)
emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath()); emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath());
}
return vCard;
} }
void Core::RosterItem::clearArchiveRequests() void Core::RosterItem::clearArchiveRequests() {
{
syncronizing = false; syncronizing = false;
requestedCount = 0; requestedCount = 0;
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();
} }
@ -539,13 +538,72 @@ void Core::RosterItem::clearArchiveRequests()
requestCache.clear(); requestCache.clear();
} }
void Core::RosterItem::downgradeDatabaseState() void Core::RosterItem::downgradeDatabaseState() {
{ if (archiveState == ArchiveState::complete)
if (archiveState == ArchiveState::complete) {
archiveState = ArchiveState::beginning; archiveState = ArchiveState::beginning;
if (archiveState == ArchiveState::end)
archiveState = ArchiveState::chunk;
}
Shared::Message Core::RosterItem::getMessage(const QString& id) {
for (const Shared::Message& msg : appendCache) {
if (msg.getId() == id)
return msg;
} }
if (archiveState == ArchiveState::end) { for (Shared::Message& msg : hisoryCache) {
archiveState = ArchiveState::chunk; if (msg.getId() == id)
return msg;
}
return archive->getElement(id);
}
Shared::EncryptionProtocol Core::RosterItem::encryption() const {
return archive->encryption();
}
void Core::RosterItem::setEncryption(Shared::EncryptionProtocol value) {
bool changed = archive->setEncryption(value);
if (changed)
emit encryptionChanged(value);
}
QMap<QString, QVariant> Core::RosterItem::getInfo() const {
QMap<QString, QVariant> result({
{"name", name},
{"encryption", QVariant::fromValue(encryption())},
});
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info);
careAboutAvatar(hasAvatar, info, result);
return result;
}
void Core::RosterItem::careAboutAvatar (
bool hasAvatar,
const Archive::AvatarInfo& info,
QMap<QString, QVariant>& output,
const QString& resource,
const QString& subject
) const {
if (hasAvatar) {
if (info.autogenerated)
output.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
else
output.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
output.insert("avatarPath", avatarPath(resource) + "." + info.type);
} else {
output.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
output.insert("avatarPath", "");
if (subject.size() == 0)
emit requestVCard(jid);
else
emit requestVCard(subject);
} }
} }

View File

@ -16,8 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CORE_ROSTERITEM_H #pragma once
#define CORE_ROSTERITEM_H
#include <QObject> #include <QObject>
#include <QString> #include <QString>
@ -34,7 +33,8 @@
#include "shared/enums.h" #include "shared/enums.h"
#include "shared/message.h" #include "shared/message.h"
#include "shared/vcard.h" #include "shared/vcard.h"
#include "archive.h" #include "components/archive.h"
#include "adapterfunctions.h"
namespace Core { namespace Core {
@ -61,6 +61,8 @@ public:
void setName(const QString& n); void setName(const QString& n);
QString getServer() const; QString getServer() const;
bool isMuc() const; bool isMuc() const;
Shared::EncryptionProtocol encryption() const;
void setEncryption(Shared::EncryptionProtocol value);
void addMessageToArchive(const Shared::Message& msg); void addMessageToArchive(const Shared::Message& msg);
void correctMessageInArchive(const QString& originalId, const Shared::Message& msg); void correctMessageInArchive(const QString& originalId, const Shared::Message& msg);
@ -71,20 +73,24 @@ public:
QString folderPath() const; QString folderPath() const;
bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const; bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
virtual bool setAutoGeneratedAvatar(const QString& resource = ""); virtual bool setAutoGeneratedAvatar(const QString& resource = "");
virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource); virtual void handleResponseVCard(const QXmppVCardIq& card, const QString& resource, Shared::VCard& out);
virtual void handlePresence(const QXmppPresence& pres) = 0; virtual void handlePresence(const QXmppPresence& pres) = 0;
bool changeMessage(const QString& id, const QMap<QString, QVariant>& data); bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
void clearArchiveRequests(); void clearArchiveRequests();
void downgradeDatabaseState(); void downgradeDatabaseState();
virtual QMap<QString, QVariant> getInfo() const;
Shared::Message getMessage(const QString& id);
signals: signals:
void nameChanged(const QString& name); void nameChanged(const QString& name) const;
void subscriptionStateChanged(Shared::SubscriptionState state); void subscriptionStateChanged(Shared::SubscriptionState state) const;
void historyResponse(const std::list<Shared::Message>& messages); void historyResponse(const std::list<Shared::Message>& messages, bool last) const;
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()); void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()) const;
void avatarChanged(Shared::Avatar, const QString& path); void avatarChanged(Shared::Avatar, const QString& path) const;
void requestVCard(const QString& jid); void requestVCard(const QString& jid) const;
void encryptionChanged(Shared::EncryptionProtocol value) const;
public: public:
const QString jid; const QString jid;
@ -93,6 +99,13 @@ public:
protected: protected:
virtual bool setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource = ""); virtual bool setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource = "");
virtual bool setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource = ""); virtual bool setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource = "");
void careAboutAvatar(
bool hasAvatar,
const Archive::AvatarInfo& info,
QMap<QString, QVariant>& output,
const QString& resource = "",
const QString& subject = ""
) const;
protected: protected:
QString name; QString name;
@ -116,5 +129,3 @@ private:
}; };
} }
#endif // CORE_ROSTERITEM_H

View File

@ -21,6 +21,8 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include "shared/defines.h"
int SignalCatcher::sigintFd[2] = {0,0}; int SignalCatcher::sigintFd[2] = {0,0};
SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent): SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
@ -28,14 +30,10 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
app(p_app) app(p_app)
{ {
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd)) if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd))
{
qFatal("Couldn't create INT socketpair"); qFatal("Couldn't create INT socketpair");
}
if (setup_unix_signal_handlers() != 0) if (setup_unix_signal_handlers() != 0)
{
qFatal("Couldn't install unix handlers"); qFatal("Couldn't install unix handlers");
}
snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this); snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
connect(snInt, &QSocketNotifier::activated, this, &SignalCatcher::handleSigInt); connect(snInt, &QSocketNotifier::activated, this, &SignalCatcher::handleSigInt);
@ -44,25 +42,25 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
SignalCatcher::~SignalCatcher() SignalCatcher::~SignalCatcher()
{} {}
void SignalCatcher::handleSigInt() void SignalCatcher::handleSigInt() {
{
snInt->setEnabled(false); snInt->setEnabled(false);
char tmp; char tmp;
ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp)); ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
SHARED_UNUSED(s);
app->quit(); emit interrupt();
snInt->setEnabled(true); snInt->setEnabled(true);
} }
void SignalCatcher::intSignalHandler(int unused) void SignalCatcher::intSignalHandler(int unused) {
{
char a = 1; char a = 1;
ssize_t s = ::write(sigintFd[0], &a, sizeof(a)); ssize_t s = ::write(sigintFd[0], &a, sizeof(a));
SHARED_UNUSED(s);
SHARED_UNUSED(unused);
} }
int SignalCatcher::setup_unix_signal_handlers() int SignalCatcher::setup_unix_signal_handlers() {
{
struct sigaction s_int; struct sigaction s_int;
s_int.sa_handler = SignalCatcher::intSignalHandler; s_int.sa_handler = SignalCatcher::intSignalHandler;

View File

@ -33,6 +33,9 @@ public:
static void intSignalHandler(int unused); static void intSignalHandler(int unused);
signals:
void interrupt();
public slots: public slots:
void handleSigInt(); void handleSigInt();

View File

@ -1,6 +1,6 @@
/* /*
* Squawk messenger. * Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me> * Copyright (C) 2021 Shunf4 <shun1048576@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -22,35 +22,37 @@
#include <QDir> #include <QDir>
#include <QStandardPaths> #include <QStandardPaths>
#include "utils/jammer.h"
Core::Squawk::Squawk(QObject* parent): Core::Squawk::Squawk(QObject* parent):
QObject(parent), QObject(parent),
accounts(), accounts(),
amap(), amap(),
state(Shared::Availability::offline),
network(), network(),
waitingForAccounts(0) isInitialized(false),
#ifdef WITH_KWALLET #ifdef WITH_KWALLET
,kwallet() kwallet(),
#endif #endif
clientCache()
{ {
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) {
connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened); connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword); connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword); connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword);
Shared::Global::setSupported("KWallet", true); Shared::Global::setSupported("KWallet", true);
} }
#endif #endif
} }
Core::Squawk::~Squawk() Core::Squawk::~Squawk() {
{
Accounts::const_iterator itr = accounts.begin(); Accounts::const_iterator itr = accounts.begin();
Accounts::const_iterator end = accounts.end(); Accounts::const_iterator end = accounts.end();
for (; itr != end; ++itr) { for (; itr != end; ++itr) {
@ -58,70 +60,73 @@ Core::Squawk::~Squawk()
} }
} }
void Core::Squawk::onWalletOpened(bool success) void Core::Squawk::onWalletOpened(bool success) {
{
qDebug() << "KWallet opened: " << success; qDebug() << "KWallet opened: " << success;
} }
void Core::Squawk::stop() void Core::Squawk::stop() {
{
qDebug("Stopping squawk core.."); qDebug("Stopping squawk core..");
network.stop(); network.stop();
QSettings settings; clientCache.close();
settings.beginGroup("core");
settings.beginWriteArray("accounts"); if (isInitialized) {
SimpleCrypt crypto(passwordHash); QSettings settings;
for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) { settings.beginGroup("core");
settings.setArrayIndex(i); settings.beginWriteArray("accounts");
Account* acc = accounts[i]; for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
settings.setArrayIndex(i);
Shared::AccountPassword ap = acc->getPasswordType(); Account* acc = accounts[i];
QString password;
Shared::AccountPassword ap = acc->getPasswordType();
switch (ap) { QString password;
case Shared::AccountPassword::plain:
password = acc->getPassword(); switch (ap) {
break; case Shared::AccountPassword::plain:
case Shared::AccountPassword::jammed: password = acc->getPassword();
password = crypto.encryptToString(acc->getPassword()); break;
break; case Shared::AccountPassword::jammed:
default: password = Jammer::encrypt(acc->getPassword(), passwordHash);
break; break;
default:
break;
}
settings.setValue("name", acc->getName());
settings.setValue("server", acc->getServer());
settings.setValue("login", acc->getLogin());
settings.setValue("password", password);
settings.setValue("resource", acc->getResource());
settings.setValue("passwordType", static_cast<int>(ap));
settings.setValue("active", acc->getActive());
} }
settings.endArray();
settings.setValue("name", acc->getName()); settings.endGroup();
settings.setValue("server", acc->getServer());
settings.setValue("login", acc->getLogin()); settings.sync();
settings.setValue("password", password);
settings.setValue("resource", acc->getResource());
settings.setValue("passwordType", static_cast<int>(ap));
} }
settings.endArray();
settings.endGroup();
settings.sync();
emit quit(); emit quit();
} }
void Core::Squawk::start() void Core::Squawk::start() {
{
qDebug("Starting squawk core.."); qDebug("Starting squawk core..");
readSettings(); readSettings();
isInitialized = true;
network.start(); network.start();
clientCache.open();
} }
void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map) void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map) {
{
QString name = map.value("name").toString(); QString name = map.value("name").toString();
QString login = map.value("login").toString(); QString login = map.value("login").toString();
QString server = map.value("server").toString(); QString server = map.value("server").toString();
QString password = map.value("password").toString(); QString password = map.value("password").toString();
QString resource = map.value("resource").toString(); QString resource = map.value("resource").toString();
int passwordType = map.value("passwordType").toInt(); int passwordType = map.value("passwordType").toInt();
bool active = map.value("active").toBool();
addAccount(login, server, password, name, resource, Shared::Global::fromInt<Shared::AccountPassword>(passwordType)); addAccount(login, server, password, name, resource, active, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
} }
void Core::Squawk::addAccount( void Core::Squawk::addAccount(
@ -129,13 +134,15 @@ void Core::Squawk::addAccount(
const QString& server, const QString& server,
const QString& password, const QString& password,
const QString& name, const QString& name,
const QString& resource, const QString& resource,
Shared::AccountPassword passwordType bool active,
) Shared::AccountPassword passwordType)
{ {
QSettings settings; if (amap.count(name) > 0) {
qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring";
Account* acc = new Account(login, server, password, name, &network); return;
}
Account* acc = new Account(login, server, password, name, active, &network);
acc->setResource(resource); acc->setResource(resource);
acc->setPasswordType(passwordType); acc->setPasswordType(passwordType);
accounts.push_back(acc); accounts.push_back(acc);
@ -144,6 +151,8 @@ void Core::Squawk::addAccount(
connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged); connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
connect(acc, &Account::changed, this, &Squawk::onAccountChanged); connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
connect(acc, &Account::error, this, &Squawk::onAccountError); connect(acc, &Account::error, this, &Squawk::onAccountError);
connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword);
connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged); connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact); connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup); connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
@ -167,9 +176,11 @@ void Core::Squawk::addAccount(
connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence); connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence);
connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence); connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence);
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard); connect(acc, &Account::infoReady, this, &Squawk::responseInfo);
connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError); connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
connect(acc, &Account::infoDiscovered, this, &Squawk::onAccountInfoDiscovered);
QMap<QString, QVariant> map = { QMap<QString, QVariant> map = {
{"login", login}, {"login", login},
@ -181,49 +192,76 @@ void Core::Squawk::addAccount(
{"offline", QVariant::fromValue(Shared::Availability::offline)}, {"offline", QVariant::fromValue(Shared::Availability::offline)},
{"error", ""}, {"error", ""},
{"avatarPath", acc->getAvatarPath()}, {"avatarPath", acc->getAvatarPath()},
{"passwordType", QVariant::fromValue(passwordType)} {"passwordType", QVariant::fromValue(passwordType)},
{"active", active}
}; };
emit newAccount(map); emit newAccount(map);
switch (passwordType) {
case Shared::AccountPassword::alwaysAsk:
case Shared::AccountPassword::kwallet:
if (password == "")
acc->invalidatePassword();
break;
default:
break;
}
if (state != Shared::Availability::offline) {
acc->setAvailability(state);
if (acc->getActive())
acc->connect();
}
} }
void Core::Squawk::changeState(Shared::Availability p_state) void Core::Squawk::changeState(Shared::Availability p_state) {
{
if (state != p_state) { if (state != p_state) {
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
Account* acc = *itr;
acc->setAvailability(p_state);
if (state == Shared::Availability::offline && acc->getActive())
acc->connect();
}
state = p_state; state = p_state;
}
emit stateChanged(p_state);
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
(*itr)->setAvailability(state);
} }
} }
void Core::Squawk::connectAccount(const QString& account) void Core::Squawk::connectAccount(const QString& account) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to connect non existing account, skipping"); qDebug("An attempt to connect non existing account, skipping");
return; return;
} }
itr->second->connect(); itr->second->setActive(true);
if (state != Shared::Availability::offline)
itr->second->connect();
} }
void Core::Squawk::disconnectAccount(const QString& account) void Core::Squawk::disconnectAccount(const QString& account) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to connect non existing account, skipping"); qDebug("An attempt to connect non existing account, skipping");
return; return;
} }
itr->second->setActive(false);
itr->second->disconnect(); itr->second->disconnect();
} }
void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state) void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}}); QMap<QString, QVariant> changes = {
{"state", QVariant::fromValue(p_state)}
};
if (acc->getLastError() == Account::Error::none)
changes.insert("error", "");
emit changeAccount(acc->getName(), changes);
#ifdef WITH_KWALLET #ifdef WITH_KWALLET
if (p_state == Shared::ConnectionState::connected) { if (p_state == Shared::ConnectionState::connected) {
if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) { if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
@ -231,125 +269,117 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
} }
} }
#endif #endif
Accounts::const_iterator itr = accounts.begin();
bool es = true;
bool ea = true;
Shared::ConnectionState cs = (*itr)->getState();
Shared::Availability av = (*itr)->getAvailability();
itr++;
for (Accounts::const_iterator end = accounts.end(); itr != end; itr++) {
Account* item = *itr;
if (item->getState() != cs) {
es = false;
}
if (item->getAvailability() != av) {
ea = false;
}
}
if (es) {
if (cs == Shared::ConnectionState::disconnected) {
state = Shared::Availability::offline;
emit stateChanged(state);
} else if (ea) {
state = av;
emit stateChanged(state);
}
}
} }
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data) void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit addContact(acc->getName(), jid, group, data); emit addContact(acc->getName(), jid, group, data);
} }
void Core::Squawk::onAccountAddGroup(const QString& name) void Core::Squawk::onAccountAddGroup(const QString& name) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit addGroup(acc->getName(), name); emit addGroup(acc->getName(), name);
} }
void Core::Squawk::onAccountRemoveGroup(const QString& name) void Core::Squawk::onAccountRemoveGroup(const QString& name) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit removeGroup(acc->getName(), name); emit removeGroup(acc->getName(), name);
} }
void Core::Squawk::onAccountChangeContact(const QString& jid, const QMap<QString, QVariant>& data) void Core::Squawk::onAccountChangeContact(const QString& jid, const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeContact(acc->getName(), jid, data); emit changeContact(acc->getName(), jid, data);
} }
void Core::Squawk::onAccountRemoveContact(const QString& jid) void Core::Squawk::onAccountRemoveContact(const QString& jid) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit removeContact(acc->getName(), jid); emit removeContact(acc->getName(), jid);
} }
void Core::Squawk::onAccountRemoveContact(const QString& jid, const QString& group) void Core::Squawk::onAccountRemoveContact(const QString& jid, const QString& group) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit removeContact(acc->getName(), jid, group); emit removeContact(acc->getName(), jid, group);
} }
void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data) void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit addPresence(acc->getName(), jid, name, data); emit addPresence(acc->getName(), jid, name, data);
//it's equal if a MUC sends its status with presence of the same jid (ex: muc@srv.im/muc@srv.im), it's not a client, so, no need to request
if (jid != name) {
const Shared::ClientId& id = data["client"].value<Shared::ClientId>();
if (!id.valid())
return;
if (!clientCache.checkClient(id))
acc->discoverInfo(jid + "/" + name, id.getId());
}
} }
void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& name) void Core::Squawk::onAccountInfoDiscovered(
const QString& address,
const QString& node,
const std::set<Shared::Identity>& identities,
const std::set<QString>& features)
{ {
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
if (!clientCache.registerClientInfo(address, node, identities, features)) {
qDebug() << "Account" << acc->getName() << "received an ill-formed client discovery response from" << address << "about" << node;
}
}
void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& name) {
Account* acc = static_cast<Account*>(sender());
emit removePresence(acc->getName(), jid, name); emit removePresence(acc->getName(), jid, name);
} }
void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state) void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), {{"availability", QVariant::fromValue(state)}}); emit changeAccount(acc->getName(), {{"availability", QVariant::fromValue(state)}});
} }
void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data) void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), data); emit changeAccount(acc->getName(), data);
} }
void Core::Squawk::onAccountMessage(const Shared::Message& data) void Core::Squawk::onAccountMessage(const Shared::Message& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit accountMessage(acc->getName(), data); emit accountMessage(acc->getName(), data);
} }
void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data) void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to send a message with non existing account, skipping"); qDebug() << "An attempt to send a message with non existing account" << account << ", skipping";
return; return;
} }
itr->second->sendMessage(data); itr->second->sendMessage(data);
} }
void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path) void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to send a message with non existing account, skipping"); qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping";
return;
}
itr->second->replaceMessage(originalId, data);
}
void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id) {
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping";
return; return;
} }
itr->second->sendMessage(data, path); itr->second->resendMessage(jid, id);
} }
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);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to request an archive of non existing account, skipping"); qDebug("An attempt to request an archive of non existing account, skipping");
@ -358,14 +388,12 @@ 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) {
{
AccountsMap::const_iterator itr = amap.find(name); AccountsMap::const_iterator itr = amap.find(name);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to modify non existing account, skipping"); qDebug("An attempt to modify non existing account, skipping");
@ -376,6 +404,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
Shared::ConnectionState st = acc->getState(); Shared::ConnectionState st = acc->getState();
QMap<QString, QVariant>::const_iterator mItr; QMap<QString, QVariant>::const_iterator mItr;
bool needToReconnect = false; bool needToReconnect = false;
bool wentReconnecting = false;
mItr = map.find("login"); mItr = map.find("login");
if (mItr != map.end()) { if (mItr != map.end()) {
@ -401,34 +430,37 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
} }
} }
if (needToReconnect && st != Shared::ConnectionState::disconnected) { bool activeChanged = false;
acc->reconnect(); mItr = map.find("active");
if (mItr == map.end() || mItr->toBool() == acc->getActive()) {
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
acc->reconnect();
wentReconnecting = true;
}
} else {
acc->setActive(mItr->toBool());
activeChanged = true;
} }
mItr = map.find("login"); mItr = map.find("login");
if (mItr != map.end()) { if (mItr != map.end())
acc->setLogin(mItr->toString()); acc->setLogin(mItr->toString());
}
mItr = map.find("password"); mItr = map.find("password");
if (mItr != map.end()) { if (mItr != map.end())
acc->setPassword(mItr->toString()); acc->setPassword(mItr->toString());
}
mItr = map.find("resource"); mItr = map.find("resource");
if (mItr != map.end()) { if (mItr != map.end())
acc->setResource(mItr->toString()); acc->setResource(mItr->toString());
}
mItr = map.find("server"); mItr = map.find("server");
if (mItr != map.end()) { if (mItr != map.end())
acc->setServer(mItr->toString()); acc->setServer(mItr->toString());
}
mItr = map.find("passwordType"); mItr = map.find("passwordType");
if (mItr != map.end()) { if (mItr != map.end())
acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt())); acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt()));
}
#ifdef WITH_KWALLET #ifdef WITH_KWALLET
if (acc->getPasswordType() == Shared::AccountPassword::kwallet if (acc->getPasswordType() == Shared::AccountPassword::kwallet
@ -439,17 +471,25 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
} }
#endif #endif
if (state != Shared::Availability::offline) {
if (activeChanged && acc->getActive())
acc->connect();
else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication)
acc->connect();
}
emit changeAccount(name, map); emit changeAccount(name, map);
} }
void Core::Squawk::onAccountError(const QString& text) void Core::Squawk::onAccountError(const QString& text) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), {{"error", text}}); emit changeAccount(acc->getName(), {{"error", text}});
if (acc->getLastError() == Account::Error::authentication)
emit requestPassword(acc->getName(), true);
} }
void Core::Squawk::removeAccountRequest(const QString& name) void Core::Squawk::removeAccountRequest(const QString& name) {
{
AccountsMap::const_iterator itr = amap.find(name); AccountsMap::const_iterator itr = amap.find(name);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to remove non existing account " << name << " from core, skipping"; qDebug() << "An attempt to remove non existing account " << name << " from core, skipping";
@ -457,9 +497,8 @@ void Core::Squawk::removeAccountRequest(const QString& name)
} }
Account* acc = itr->second; Account* acc = itr->second;
if (acc->getState() != Shared::ConnectionState::disconnected) { if (acc->getState() != Shared::ConnectionState::disconnected)
acc->disconnect(); acc->disconnect();
}
for (Accounts::const_iterator aItr = accounts.begin(); aItr != accounts.end(); ++aItr) { for (Accounts::const_iterator aItr = accounts.begin(); aItr != accounts.end(); ++aItr) {
if (*aItr == acc) { if (*aItr == acc) {
@ -479,8 +518,7 @@ void Core::Squawk::removeAccountRequest(const QString& name)
acc->deleteLater(); acc->deleteLater();
} }
void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason) void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to subscribe to the contact with non existing account, skipping"); qDebug("An attempt to subscribe to the contact with non existing account, skipping");
@ -490,8 +528,7 @@ void Core::Squawk::subscribeContact(const QString& account, const QString& jid,
itr->second->subscribeToContact(jid, reason); itr->second->subscribeToContact(jid, reason);
} }
void Core::Squawk::unsubscribeContact(const QString& account, const QString& jid, const QString& reason) void Core::Squawk::unsubscribeContact(const QString& account, const QString& jid, const QString& reason) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to subscribe to the contact with non existing account, skipping"); qDebug("An attempt to subscribe to the contact with non existing account, skipping");
@ -501,8 +538,7 @@ void Core::Squawk::unsubscribeContact(const QString& account, const QString& jid
itr->second->unsubscribeFromContact(jid, reason); itr->second->unsubscribeFromContact(jid, reason);
} }
void Core::Squawk::removeContactRequest(const QString& account, const QString& jid) void Core::Squawk::removeContactRequest(const QString& account, const QString& jid) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to remove contact from non existing account, skipping"); qDebug("An attempt to remove contact from non existing account, skipping");
@ -512,8 +548,7 @@ void Core::Squawk::removeContactRequest(const QString& account, const QString& j
itr->second->removeContactRequest(jid); itr->second->removeContactRequest(jid);
} }
void Core::Squawk::addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups) void Core::Squawk::addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug("An attempt to add contact to a non existing account, skipping"); qDebug("An attempt to add contact to a non existing account, skipping");
@ -523,26 +558,22 @@ void Core::Squawk::addContactRequest(const QString& account, const QString& jid,
itr->second->addContactRequest(jid, name, groups); itr->second->addContactRequest(jid, name, groups);
} }
void Core::Squawk::onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data) void Core::Squawk::onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit addRoom(acc->getName(), jid, data); emit addRoom(acc->getName(), jid, data);
} }
void Core::Squawk::onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data) void Core::Squawk::onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeRoom(acc->getName(), jid, data); emit changeRoom(acc->getName(), jid, data);
} }
void Core::Squawk::onAccountRemoveRoom(const QString jid) void Core::Squawk::onAccountRemoveRoom(const QString jid) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit removeRoom(acc->getName(), jid); emit removeRoom(acc->getName(), jid);
} }
void Core::Squawk::setRoomJoined(const QString& account, const QString& jid, bool joined) void Core::Squawk::setRoomJoined(const QString& account, const QString& jid, bool joined) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to set jouned to the room" << jid << "of non existing account" << account << ", skipping"; qDebug() << "An attempt to set jouned to the room" << jid << "of non existing account" << account << ", skipping";
@ -551,8 +582,7 @@ void Core::Squawk::setRoomJoined(const QString& account, const QString& jid, boo
itr->second->setRoomJoined(jid, joined); itr->second->setRoomJoined(jid, joined);
} }
void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, bool joined) void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, bool joined) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to set autoJoin to the room" << jid << "of non existing account" << account << ", skipping"; qDebug() << "An attempt to set autoJoin to the room" << jid << "of non existing account" << account << ", skipping";
@ -561,32 +591,36 @@ void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, b
itr->second->setRoomAutoJoin(jid, joined); itr->second->setRoomAutoJoin(jid, joined);
} }
void Core::Squawk::onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data) void Core::Squawk::setContactEncryption(const QString& account, const QString& jid, Shared::EncryptionProtocol value) {
{ AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to set encryption to the contact" << jid << "of non existing account" << account << ", skipping";
return;
}
itr->second->setContactEncryption(jid, value);
}
void Core::Squawk::onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data) {
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit addRoomParticipant(acc->getName(), jid, nick, data); emit addRoomParticipant(acc->getName(), jid, nick, data);
} }
void Core::Squawk::onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data) void Core::Squawk::onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeRoomParticipant(acc->getName(), jid, nick, data); emit changeRoomParticipant(acc->getName(), jid, nick, data);
} }
void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString& nick) void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString& nick) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit removeRoomParticipant(acc->getName(), jid, nick); emit removeRoomParticipant(acc->getName(), jid, nick);
} }
void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data) void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data) {
{
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
emit changeMessage(acc->getName(), jid, id, data); emit changeMessage(acc->getName(), jid, id, data);
} }
void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid) void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to remove the room" << jid << "of non existing account" << account << ", skipping"; qDebug() << "An attempt to remove the room" << jid << "of non existing account" << account << ", skipping";
@ -595,8 +629,7 @@ void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid)
itr->second->removeRoomRequest(jid); itr->second->removeRoomRequest(jid);
} }
void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin) void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to add the room" << jid << "to non existing account" << account << ", skipping"; qDebug() << "An attempt to add the room" << jid << "to non existing account" << account << ", skipping";
@ -605,18 +638,11 @@ 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.downladFile(url);
network.fileLocalPathRequest(messageId, url);
} }
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url) void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName) {
{
network.downladFileRequest(messageId, url);
}
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping"; qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
@ -625,8 +651,7 @@ void Core::Squawk::addContactToGroupRequest(const QString& account, const QStrin
itr->second->addContactToGroupRequest(jid, groupName); itr->second->addContactToGroupRequest(jid, groupName);
} }
void Core::Squawk::removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName) void Core::Squawk::removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping"; qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
@ -635,8 +660,7 @@ void Core::Squawk::removeContactFromGroupRequest(const QString& account, const Q
itr->second->removeContactFromGroupRequest(jid, groupName); itr->second->removeContactFromGroupRequest(jid, groupName);
} }
void Core::Squawk::renameContactRequest(const QString& account, const QString& jid, const QString& newName) void Core::Squawk::renameContactRequest(const QString& account, const QString& jid, const QString& newName) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping"; qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping";
@ -645,121 +669,117 @@ void Core::Squawk::renameContactRequest(const QString& account, const QString& j
itr->second->renameContactRequest(jid, newName); itr->second->renameContactRequest(jid, newName);
} }
void Core::Squawk::requestVCard(const QString& account, const QString& jid) void Core::Squawk::requestInfo(const QString& account, const QString& jid) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to request" << jid << "vcard of non existing account" << account << ", skipping"; qDebug() << "An attempt to request info about" << jid << "of non existing account" << account << ", skipping";
return; return;
} }
itr->second->requestVCard(jid); itr->second->requestInfo(jid);
} }
void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card) void Core::Squawk::updateInfo(const QString& account, const Shared::Info& info) {
{
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to upload vcard to non existing account" << account << ", skipping"; qDebug() << "An attempt to update info to non existing account" << account << ", skipping";
return; return;
} }
itr->second->uploadVCard(card); itr->second->updateInfo(info);
} }
void Core::Squawk::responsePassword(const QString& account, const QString& password) void Core::Squawk::readSettings() {
{ QSettings settings;
settings.beginGroup("core");
int size = settings.beginReadArray("accounts");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
Shared::AccountPassword passwordType =
Shared::Global::fromInt<Shared::AccountPassword>(
settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt()
);
QString name = settings.value("name").toString();
QString password = settings.value("password", "").toString();
if (passwordType == Shared::AccountPassword::jammed)
password = Jammer::decrypt(password, passwordHash);
addAccount(
settings.value("login").toString(),
settings.value("server").toString(),
password,
name,
settings.value("resource").toString(),
settings.value("active").toBool(),
passwordType
);
}
settings.endArray();
settings.endGroup();
qDebug() << "Squawk core is ready";
emit ready();
}
void Core::Squawk::onAccountNeedPassword() {
Account* acc = static_cast<Account*>(sender());
switch (acc->getPasswordType()) {
case Shared::AccountPassword::alwaysAsk:
emit requestPassword(acc->getName(), false);
break;
case Shared::AccountPassword::kwallet: {
#ifdef WITH_KWALLET
if (kwallet.supportState() == PSE::KWallet::success) {
kwallet.requestReadPassword(acc->getName());
} else {
#endif
emit requestPassword(acc->getName(), false);
#ifdef WITH_KWALLET
}
#endif
break;
}
default:
break; //should never happen;
}
}
void Core::Squawk::onWalletRejectPassword(const QString& login) {
emit requestPassword(login, false);
}
void Core::Squawk::responsePassword(const QString& account, const QString& password) {
AccountsMap::const_iterator itr = amap.find(account); AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) { if (itr == amap.end()) {
qDebug() << "An attempt to set password to non existing account" << account << ", skipping"; qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
return; return;
} }
itr->second->setPassword(password); Account* acc = itr->second;
acc->setPassword(password);
emit changeAccount(account, {{"password", password}}); emit changeAccount(account, {{"password", password}});
accountReady(); if (state != Shared::Availability::offline && acc->getActive())
acc->connect();
} }
void Core::Squawk::readSettings() void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText) {
{ Account* acc = static_cast<Account*>(sender());
QSettings settings; emit fileError({{acc->getName(), jid, id}}, errorText, true);
settings.beginGroup("core");
int size = settings.beginReadArray("accounts");
waitingForAccounts = size;
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
parseAccount(
settings.value("login").toString(),
settings.value("server").toString(),
settings.value("password", "").toString(),
settings.value("name").toString(),
settings.value("resource").toString(),
Shared::Global::fromInt<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt())
);
}
settings.endArray();
settings.endGroup();
} }
void Core::Squawk::accountReady() void Core::Squawk::onLocalPathInvalid(const QString& path) {
{ std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
--waitingForAccounts;
if (waitingForAccounts == 0) { QMap<QString, QVariant> data({{"attachPath", ""}});
emit ready(); 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);
void Core::Squawk::parseAccount( } else {
const QString& login, qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
const QString& server,
const QString& password,
const QString& name,
const QString& resource,
Shared::AccountPassword passwordType
)
{
switch (passwordType) {
case Shared::AccountPassword::plain:
addAccount(login, server, password, name, resource, passwordType);
accountReady();
break;
case Shared::AccountPassword::jammed: {
SimpleCrypt crypto(passwordHash);
QString decrypted = crypto.decryptToString(password);
addAccount(login, server, decrypted, name, resource, passwordType);
accountReady();
}
break;
case Shared::AccountPassword::alwaysAsk:
addAccount(login, server, QString(), name, resource, passwordType);
emit requestPassword(name);
break;
case Shared::AccountPassword::kwallet: {
addAccount(login, server, QString(), name, resource, passwordType);
#ifdef WITH_KWALLET
if (kwallet.supportState() == PSE::KWallet::success) {
kwallet.requestReadPassword(name);
} else {
#endif
emit requestPassword(name);
#ifdef WITH_KWALLET
}
#endif
} }
} }
} }
void Core::Squawk::onWalletRejectPassword(const QString& login) void Core::Squawk::changeDownloadsPath(const QString& path) {
{ network.moveFilesDirectory(path);
emit requestPassword(login);
} }
void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password)
{
AccountsMap::const_iterator itr = amap.find(login);
if (itr == amap.end()) {
qDebug() << "An attempt to set password to non existing account" << login << ", skipping";
return;
}
itr->second->setPassword(password);
emit changeAccount(login, {{"password", password}});
accountReady();
}

View File

@ -31,17 +31,18 @@
#include "shared/enums.h" #include "shared/enums.h"
#include "shared/message.h" #include "shared/message.h"
#include "shared/global.h" #include "shared/global.h"
#include "networkaccess.h" #include "shared/info.h"
#include "external/simpleCrypt/simplecrypt.h" #include "shared/clientinfo.h"
#include <core/components/clientcache.h>
#include <core/components/networkaccess.h>
#ifdef WITH_KWALLET #ifdef WITH_KWALLET
#include "passwordStorageEngines/kwallet.h" #include "passwordStorageEngines/kwallet.h"
#endif #endif
namespace Core namespace Core {
{ class Squawk : public QObject {
class Squawk : public QObject
{
Q_OBJECT Q_OBJECT
public: public:
@ -51,47 +52,60 @@ 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& url, const QString& path);
void responseVCard(const QString& jid, const Shared::VCard& card);
void responseInfo(const Shared::Info& info);
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, bool authernticationError);
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 replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
void resendMessage(const QString& account, const QString& jid, const QString& id);
void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void 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 +113,20 @@ 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 setContactEncryption(const QString& account, const QString& jid, Shared::EncryptionProtocol value);
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 uploadVCard(const QString& account, const Shared::VCard& card); void requestInfo(const QString& account, const QString& jid);
void updateInfo(const QString& account, const Shared::Info& info);
void responsePassword(const QString& account, const QString& password); void responsePassword(const QString& account, const QString& password);
void onLocalPathInvalid(const QString& path);
void changeDownloadsPath(const QString& path);
private: private:
typedef std::deque<Account*> Accounts; typedef std::deque<Account*> Accounts;
@ -117,11 +136,13 @@ private:
AccountsMap amap; AccountsMap amap;
Shared::Availability state; Shared::Availability state;
NetworkAccess network; NetworkAccess network;
uint8_t waitingForAccounts; bool isInitialized;
#ifdef WITH_KWALLET #ifdef WITH_KWALLET
PSE::KWallet kwallet; PSE::KWallet kwallet;
#endif #endif
ClientCache clientCache;
private slots: private slots:
void addAccount( void addAccount(
@ -130,6 +151,7 @@ private slots:
const QString& password, const QString& password,
const QString& name, const QString& name,
const QString& resource, const QString& resource,
bool active,
Shared::AccountPassword passwordType Shared::AccountPassword passwordType
); );
@ -146,7 +168,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);
@ -154,24 +176,19 @@ private slots:
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data); void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick); void 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 onAccountNeedPassword();
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 onWalletRejectPassword(const QString& login); void onWalletRejectPassword(const QString& login);
void onAccountInfoDiscovered(const QString& address, const QString& node, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
private: private:
void readSettings(); void readSettings();
void accountReady();
void parseAccount(
const QString& login,
const QString& server,
const QString& password,
const QString& name,
const QString& resource,
Shared::AccountPassword passwordType
);
static const quint64 passwordHash = 0x3c5bb27570f50e11; static const quint64 passwordHash = 0x08d054225ac4871d;
}; };
} }

View File

@ -1,184 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QStandardPaths>
#include <QDir>
#include "storage.h"
Core::Storage::Storage(const QString& p_name):
name(p_name),
opened(false),
environment(),
base()
{
}
Core::Storage::~Storage()
{
close();
}
void Core::Storage::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, 1);
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_txn_commit(txn);
opened = true;
}
}
void Core::Storage::close()
{
if (opened) {
mdb_dbi_close(environment, base);
mdb_env_close(environment);
opened = false;
}
}
void Core::Storage::addRecord(const QString& key, const QString& value)
{
if (!opened) {
throw Archive::Closed("addRecord", name.toStdString());
}
const std::string& id = key.toStdString();
const std::string& val = value.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = val.size();
lmdbData.mv_data = (char*)val.c_str();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc != 0) {
mdb_txn_abort(txn);
if (rc == MDB_KEYEXIST) {
throw Archive::Exist(name.toStdString(), id);
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
} else {
mdb_txn_commit(txn);
}
}
void Core::Storage::changeRecord(const QString& key, const QString& value)
{
if (!opened) {
throw Archive::Closed("changeRecord", name.toStdString());
}
const std::string& id = key.toStdString();
const std::string& val = value.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = val.size();
lmdbData.mv_data = (char*)val.c_str();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
mdb_txn_abort(txn);
if (rc) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
} else {
mdb_txn_commit(txn);
}
}
QString Core::Storage::getRecord(const QString& key) const
{
if (!opened) {
throw Archive::Closed("addElement", name.toStdString());
}
const std::string& id = key.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
MDB_txn *txn;
int rc;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
if (rc) {
mdb_txn_abort(txn);
if (rc == MDB_NOTFOUND) {
throw Archive::NotFound(id, name.toStdString());
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
} else {
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
QString value(sId.c_str());
mdb_txn_abort(txn);
return value;
}
}
void Core::Storage::removeRecord(const QString& key)
{
if (!opened) {
throw Archive::Closed("addElement", name.toStdString());
}
const std::string& id = key.toStdString();
MDB_val lmdbKey;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
MDB_txn *txn;
int rc;
mdb_txn_begin(environment, NULL, 0, &txn);
rc = mdb_del(txn, base, &lmdbKey, NULL);
if (rc) {
mdb_txn_abort(txn);
if (rc == MDB_NOTFOUND) {
throw Archive::NotFound(id, name.toStdString());
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
} else {
mdb_txn_commit(txn);
}
}

View File

@ -0,0 +1,9 @@
set(SOURCE_FILES
jammer.cpp
)
set(HEADER_FILES
jammer.h
)
target_sources(squawk PRIVATE ${SOURCE_FILES})

94
core/utils/jammer.cpp Normal file
View File

@ -0,0 +1,94 @@
/*
* 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 "jammer.h"
#include <memory>
#include <openssl/evp.h>
#include <openssl/err.h>
struct CipherCtxDeleter {
void operator()(EVP_CIPHER_CTX* ctx) const {
EVP_CIPHER_CTX_free(ctx);
}
};
typedef std::unique_ptr<EVP_CIPHER_CTX, CipherCtxDeleter> CipherCtx;
QString Core::Jammer::encrypt(const QString& plaintext, qint64 key) {
QByteArray encryptedData = process(plaintext.toUtf8(), intToKey(key), true);
return QString::fromUtf8(encryptedData.toHex());
}
QString Core::Jammer::decrypt(const QString& ciphertext, qint64 key) {
QByteArray encryptedData = QByteArray::fromHex(ciphertext.toUtf8());
QByteArray decryptedData = process(encryptedData, intToKey(key), false);
return QString::fromUtf8(decryptedData);
}
std::string Core::Jammer::getOpenSSLErrorString() {
unsigned long errCode = ERR_get_error();
if (errCode == 0) {
return "No OpenSSL error";
}
const char *errMsg = ERR_reason_error_string(errCode);
return errMsg ? std::string(errMsg) : "Unknown OpenSSL error";
}
QByteArray Core::Jammer::process(const QByteArray& input, const QByteArray& key, bool encrypt) {
CipherCtx ctx(EVP_CIPHER_CTX_new());
if (!ctx)
throw std::runtime_error("Failed to create password jammer context");
QByteArray output(input.size() + 16, 0);
int outputLength = 0;
int finalLength = 0;
if (!ctx)
throw std::runtime_error("Failed to create EVP_CIPHER_CTX: " + getOpenSSLErrorString());
if (EVP_CipherInit_ex(ctx.get(), EVP_chacha20(), nullptr, toUChar(key), nullptr, encrypt) != 1)
throw std::runtime_error("EVP_CipherInit_ex failed. " + getOpenSSLErrorString());
if (EVP_CipherUpdate(ctx.get(), toUChar(output), &outputLength, toUChar(input), input.size()) != 1)
throw std::runtime_error("EVP_CipherUpdate failed. " + getOpenSSLErrorString());
if (EVP_CipherFinal_ex(ctx.get(), toUChar(output) + outputLength, &finalLength) != 1)
throw std::runtime_error("EVP_CipherFinal_ex failed. " + getOpenSSLErrorString());
output.resize(outputLength + finalLength);
return output;
}
QByteArray Core::Jammer::intToKey(qint64 key, int keySize) {
QByteArray keyBytes(reinterpret_cast<const char *>(&key), sizeof(key));
while (keyBytes.size() < keySize)
keyBytes.append(keyBytes);
keyBytes.truncate(keySize);
return keyBytes;
}
unsigned char* Core::Jammer::toUChar(QByteArray& data) {
return reinterpret_cast<unsigned char *>(data.data());}
const unsigned char* Core::Jammer::toUChar(const QByteArray& data) {
return reinterpret_cast<const unsigned char *>(data.constData());}

44
core/utils/jammer.h Normal file
View File

@ -0,0 +1,44 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdexcept>
#include <QByteArray>
#include <QString>
namespace Core {
class Jammer {
public:
static QString encrypt(const QString& plaintext, qint64 key);
static QString decrypt(const QString& ciphertext, qint64 key);
private:
Jammer() = delete;
static QByteArray process(const QByteArray& input, const QByteArray& key, bool encrypt);
static QByteArray intToKey(qint64 key, int keySize = 32);
static unsigned char* toUChar(QByteArray& data);
static const unsigned char* toUChar(const QByteArray& data);
static std::string getOpenSSLErrorString();
};
}

1
external/lmdbal vendored Submodule

@ -0,0 +1 @@
Subproject commit d62eddc47edbec9f8c071459e045578f61ab58df

2
external/qxmpp vendored

@ -1 +1 @@
Subproject commit fe83e9c3d42c3becf682e2b5ecfc9d77b24c614f Subproject commit 0cd7379bd78aa01af7e84f2fad6269ef0c0ba49c

View File

@ -1,16 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(simplecrypt)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
set(simplecrypt_SRC
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)

View File

@ -1,248 +0,0 @@
/*
Copyright (c) 2011, Andre Somers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Rathenau Instituut, Andre Somers nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "simplecrypt.h"
#include <QByteArray>
#include <QtDebug>
#include <QtGlobal>
#include <QDateTime>
#include <QCryptographicHash>
#include <QDataStream>
SimpleCrypt::SimpleCrypt():
m_key(0),
m_compressionMode(CompressionAuto),
m_protectionMode(ProtectionChecksum),
m_lastError(ErrorNoError) {}
SimpleCrypt::SimpleCrypt(quint64 key):
m_key(key),
m_compressionMode(CompressionAuto),
m_protectionMode(ProtectionChecksum),
m_lastError(ErrorNoError)
{
splitKey();
}
void SimpleCrypt::setKey(quint64 key)
{
m_key = key;
splitKey();
}
void SimpleCrypt::splitKey()
{
m_keyParts.clear();
m_keyParts.resize(8);
for (int i=0;i<8;i++) {
quint64 part = m_key;
for (int j=i; j>0; j--)
part = part >> 8;
part = part & 0xff;
m_keyParts[i] = static_cast<char>(part);
}
}
QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext)
{
QByteArray plaintextArray = plaintext.toUtf8();
return encryptToByteArray(plaintextArray);
}
QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
{
if (m_keyParts.isEmpty()) {
qWarning() << "No key set.";
m_lastError = ErrorNoKeySet;
return QByteArray();
}
QByteArray ba = plaintext;
CryptoFlags flags = CryptoFlagNone;
if (m_compressionMode == CompressionAlways) {
ba = qCompress(ba, 9); //maximum compression
flags |= CryptoFlagCompression;
} else if (m_compressionMode == CompressionAuto) {
QByteArray compressed = qCompress(ba, 9);
if (compressed.count() < ba.count()) {
ba = compressed;
flags |= CryptoFlagCompression;
}
}
QByteArray integrityProtection;
if (m_protectionMode == ProtectionChecksum) {
flags |= CryptoFlagChecksum;
QDataStream s(&integrityProtection, QIODevice::WriteOnly);
s << qChecksum(ba.constData(), ba.length());
} else if (m_protectionMode == ProtectionHash) {
flags |= CryptoFlagHash;
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(ba);
integrityProtection += hash.result();
}
//prepend a random char to the string
char randomChar = char(QRandomGenerator::global()->generate() & 0xFF);
ba = randomChar + integrityProtection + ba;
int pos(0);
char lastChar(0);
int cnt = ba.count();
while (pos < cnt) {
ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
lastChar = ba.at(pos);
++pos;
}
QByteArray resultArray;
resultArray.append(char(0x03)); //version for future updates to algorithm
resultArray.append(char(flags)); //encryption flags
resultArray.append(ba);
m_lastError = ErrorNoError;
return resultArray;
}
QString SimpleCrypt::encryptToString(const QString& plaintext)
{
QByteArray plaintextArray = plaintext.toUtf8();
QByteArray cypher = encryptToByteArray(plaintextArray);
QString cypherString = QString::fromLatin1(cypher.toBase64());
return cypherString;
}
QString SimpleCrypt::encryptToString(QByteArray plaintext)
{
QByteArray cypher = encryptToByteArray(plaintext);
QString cypherString = QString::fromLatin1(cypher.toBase64());
return cypherString;
}
QString SimpleCrypt::decryptToString(const QString &cyphertext)
{
QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());
return plaintext;
}
QString SimpleCrypt::decryptToString(QByteArray cypher)
{
QByteArray ba = decryptToByteArray(cypher);
QString plaintext = QString::fromUtf8(ba, ba.size());
return plaintext;
}
QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext)
{
QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
QByteArray ba = decryptToByteArray(cyphertextArray);
return ba;
}
QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher)
{
if (m_keyParts.isEmpty()) {
qWarning() << "No key set.";
m_lastError = ErrorNoKeySet;
return QByteArray();
}
QByteArray ba = cypher;
if( cypher.count() < 3 )
return QByteArray();
char version = ba.at(0);
if (version !=3) { //we only work with version 3
m_lastError = ErrorUnknownVersion;
qWarning() << "Invalid version or not a cyphertext.";
return QByteArray();
}
CryptoFlags flags = CryptoFlags(ba.at(1));
ba = ba.mid(2);
int pos(0);
int cnt(ba.count());
char lastChar = 0;
while (pos < cnt) {
char currentChar = ba[pos];
ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
lastChar = currentChar;
++pos;
}
ba = ba.mid(1); //chop off the random number at the start
bool integrityOk(true);
if (flags.testFlag(CryptoFlagChecksum)) {
if (ba.length() < 2) {
m_lastError = ErrorIntegrityFailed;
return QByteArray();
}
quint16 storedChecksum;
{
QDataStream s(&ba, QIODevice::ReadOnly);
s >> storedChecksum;
}
ba = ba.mid(2);
quint16 checksum = qChecksum(ba.constData(), ba.length());
integrityOk = (checksum == storedChecksum);
} else if (flags.testFlag(CryptoFlagHash)) {
if (ba.length() < 20) {
m_lastError = ErrorIntegrityFailed;
return QByteArray();
}
QByteArray storedHash = ba.left(20);
ba = ba.mid(20);
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(ba);
integrityOk = (hash.result() == storedHash);
}
if (!integrityOk) {
m_lastError = ErrorIntegrityFailed;
return QByteArray();
}
if (flags.testFlag(CryptoFlagCompression))
ba = qUncompress(ba);
m_lastError = ErrorNoError;
return ba;
}

View File

@ -1,226 +0,0 @@
/*
Copyright (c) 2011, Andre Somers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Rathenau Instituut, Andre Somers nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SIMPLECRYPT_H
#define SIMPLECRYPT_H
#include <QString>
#include <QVector>
#include <QFlags>
#include <QRandomGenerator>
/**
@ short Simple encrypt*ion and decryption of strings and byte arrays
This class provides a simple implementation of encryption and decryption
of strings and byte arrays.
@warning The encryption provided by this class is NOT strong encryption. It may
help to shield things from curious eyes, but it will NOT stand up to someone
determined to break the encryption. Don't say you were not warned.
The class uses a 64 bit key. Simply create an instance of the class, set the key,
and use the encryptToString() method to calculate an encrypted version of the input string.
To decrypt that string again, use an instance of SimpleCrypt initialized with
the same key, and call the decryptToString() method with the encrypted string. If the key
matches, the decrypted version of the string will be returned again.
If you do not provide a key, or if something else is wrong, the encryption and
decryption function will return an empty string or will return a string containing nonsense.
lastError() will return a value indicating if the method was succesful, and if not, why not.
SimpleCrypt is prepared for the case that the encryption and decryption
algorithm is changed in a later version, by prepending a version identifier to the cypertext.
*/
class SimpleCrypt
{
public:
/**
CompressionMode describes if compression will be applied to the data to be
encrypted.
*/
enum CompressionMode {
CompressionAuto, /*!< Only apply compression if that results in a shorter plaintext. */
CompressionAlways, /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */
CompressionNever /*!< Never apply compression. */
};
/**
IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
or wrong decryption keys.
Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
increases the length of the resulting cypertext, but makes it possible to check if the plaintext
appears to be valid after decryption.
*/
enum IntegrityProtectionMode {
ProtectionNone, /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
};
/**
Error describes t*he type of error that occured.
*/
enum Error {
ErrorNoError, /*!< No error occurred. */
ErrorNoKeySet, /*!< No key was set. You can not encrypt or decrypt without a valid key. */
ErrorUnknownVersion, /*!< The version of this data is unknown, or the data is otherwise not valid. */
ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
};
/**
Constructor. *
Constructs a SimpleCrypt instance without a valid key set on it.
*/
SimpleCrypt();
/**
Constructor. *
Constructs a SimpleCrypt instance and initializes it with the given @arg key.
*/
explicit SimpleCrypt(quint64 key);
/**
( Re-) initializes* the key with the given @arg key.
*/
void setKey(quint64 key);
/**
Returns true if SimpleCrypt has been initialized with a key.
*/
bool hasKey() const {return !m_keyParts.isEmpty();}
/**
Sets the compress*ion mode to use when encrypting data. The default mode is Auto.
Note that decryption is not influenced by this mode, as the decryption recognizes
what mode was used when encrypting.
*/
void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
/**
Returns the CompressionMode that is currently in use.
*/
CompressionMode compressionMode() const {return m_compressionMode;}
/**
Sets the integrity mode to use when encrypting data. The default mode is Checksum.
Note that decryption is not influenced by this mode, as the decryption recognizes
what mode was used when encrypting.
*/
void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
/**
Returns the IntegrityProtectionMode that is currently in use.
*/
IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}
/**
Returns the last *error that occurred.
*/
Error lastError() const {return m_lastError;}
/**
Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
actual result of the string, so it can be stored easily in a text format.
*/
QString encryptToString(const QString& plaintext) ;
/**
Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
actual result of the encryption, so it can be stored easily in a text format.
*/
QString encryptToString(QByteArray plaintext) ;
/**
Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
a binary cyphertext in a QByteArray the result.
This method returns a byte array, that is useable for storing a binary format. If you need
a string you can store in a text file, use encryptToString() instead.
*/
QByteArray encryptToByteArray(const QString& plaintext) ;
/**
Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
a binary cyphertext in a QByteArray the result.
This method returns a byte array, that is useable for storing a binary format. If you need
a string you can store in a text file, use encryptToString() instead.
*/
QByteArray encryptToByteArray(QByteArray plaintext) ;
/**
Decrypts a cypher*text string encrypted with this class with the set key back to the
plain text version.
If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QString decryptToString(const QString& cyphertext) ;
/**
Decrypts a cypher*text string encrypted with this class with the set key back to the
plain text version.
If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QByteArray decryptToByteArray(const QString& cyphertext) ;
/**
Decrypts a cypher*text binary encrypted with this class with the set key back to the
plain text version.
If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QString decryptToString(QByteArray cypher) ;
/**
Decrypts a cypher*text binary encrypted with this class with the set key back to the
plain text version.
If an error occured, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QByteArray decryptToByteArray(QByteArray cypher) ;
//enum to describe options that have been used for the encryption. Currently only one, but
//that only leaves room for future extensions like adding a cryptographic hash...
enum CryptoFlag{CryptoFlagNone = 0,
CryptoFlagCompression = 0x01,
CryptoFlagChecksum = 0x02,
CryptoFlagHash = 0x04
};
Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag);
private:
void splitKey();
quint64 m_key;
QVector<char> m_keyParts;
CompressionMode m_compressionMode;
IntegrityProtectionMode m_protectionMode;
Error m_lastError;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)
#endif // SimpleCrypt_H

164
main.cpp
View File

@ -1,164 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ui/squawk.h"
#include "core/squawk.h"
#include "signalcatcher.h"
#include "shared/global.h"
#include <QtWidgets/QApplication>
#include <QtCore/QThread>
#include <QtCore/QObject>
#include <QSettings>
#include <QTranslator>
#include <QLibraryInfo>
#include <QStandardPaths>
int main(int argc, char *argv[])
{
qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<Shared::VCard>("Shared::VCard");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<QSet<QString>>("QSet<QString>");
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
qRegisterMetaType<Shared::Availability>("Shared::Availability");
QApplication app(argc, argv);
SignalCatcher sc(&app);
QApplication::setOrganizationName("squawk");
QApplication::setApplicationName("squawk");
QApplication::setApplicationDisplayName("Squawk");
QApplication::setApplicationVersion("0.1.5");
QTranslator qtTranslator;
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&qtTranslator);
QTranslator myappTranslator;
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
bool found = false;
for (QString share : shares) {
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
if (found) {
break;
}
}
if (!found) {
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
}
app.installTranslator(&myappTranslator);
QIcon icon;
icon.addFile(":images/logo.svg", QSize(16, 16));
icon.addFile(":images/logo.svg", QSize(24, 24));
icon.addFile(":images/logo.svg", QSize(32, 32));
icon.addFile(":images/logo.svg", QSize(48, 48));
icon.addFile(":images/logo.svg", QSize(64, 64));
icon.addFile(":images/logo.svg", QSize(96, 96));
icon.addFile(":images/logo.svg", QSize(128, 128));
icon.addFile(":images/logo.svg", QSize(256, 256));
icon.addFile(":images/logo.svg", QSize(512, 512));
QApplication::setWindowIcon(icon);
new Shared::Global(); //translates enums
Squawk w;
w.show();
Core::Squawk* squawk = new Core::Squawk();
QThread* coreThread = new QThread();
squawk->moveToThread(coreThread);
QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop);
QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit);
QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater);
QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest);
QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest);
QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest);
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
QObject::connect(&w, qOverload<const QString&, const Shared::Message&>(&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::subscribeContact, squawk, &Core::Squawk::subscribeContact);
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest);
QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest);
QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined);
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest);
QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount);
QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount);
QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup);
QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup);
QObject::connect(squawk, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
&w, qOverload<const QString&, const QString&>(&Squawk::removeContact));
QObject::connect(squawk, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
&w, qOverload<const QString&, const QString&, const QString&>(&Squawk::removeContact));
QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact);
QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence);
QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence);
QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged);
QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage);
QObject::connect(squawk, &Core::Squawk::changeMessage, &w, &Squawk::changeMessage);
QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive);
QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom);
QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom);
QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom);
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress);
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
coreThread->start();
int result = app.exec();
w.writeSettings();
coreThread->wait(500); //TODO hate doing that but settings for some reason don't get saved to the disk
return result;
}

14
main/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
set(SOURCE_FILES
main.cpp
application.cpp
dialogqueue.cpp
root.cpp
)
set(HEADER_FILES
application.h
dialogqueue.h
root.h
)
target_sources(squawk PRIVATE ${SOURCE_FILES})

666
main/application.cpp Normal file
View File

@ -0,0 +1,666 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "application.h"
Application::Application(Core::Squawk* p_core):
QObject(),
availability(Shared::Availability::offline),
core(p_core),
squawk(nullptr),
notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"),
roster(),
conversations(),
dialogueQueue(roster),
nowQuitting(false),
destroyingSquawk(false),
storage(),
trayIcon(nullptr),
actionQuit(Shared::icon("application-exit"), tr("Quit")),
actionToggle(QApplication::windowIcon(), tr("Minimize to tray")),
expandedPaths()
{
connect(&actionQuit, &QAction::triggered, this, &Application::quit);
connect(&actionToggle, &QAction::triggered, this, &Application::toggleSquawk);
connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
connect(&roster, &Models::Roster::addedElement, this, &Application::onAddedElement);
//connecting myself to the backend
connect(this, &Application::changeState, core, &Core::Squawk::changeState);
connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined);
connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin);
connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact);
connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact);
connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage);
connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage);
connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage);
connect(this, &Application::setEncryption, core, &Core::Squawk::setContactEncryption);
connect(&roster, &Models::Roster::requestArchive,
std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3));
connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword);
connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount);
connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest);
connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid);
//coonecting backend to myself
connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged);
connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage);
connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive);
connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage);
connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount);
connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount);
connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount);
connect(core, &Core::Squawk::addContact, &roster, &Models::Roster::addContact);
connect(core, &Core::Squawk::addGroup, this, &Application::addGroup);
connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup);
connect(core, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
&roster, qOverload<const QString&, const QString&>(&Models::Roster::removeContact));
connect(core, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
&roster, qOverload<const QString&, const QString&, const QString&>(&Models::Roster::removeContact));
connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact);
connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence);
connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence);
connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom);
connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom);
connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom);
connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant);
connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant);
connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant);
connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false));
connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true));
connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress);
connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError);
connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword);
connect(core, &Core::Squawk::ready, this, &Application::readSettings);
QDBusConnection sys = QDBusConnection::sessionBus();
sys.connect(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationClosed",
this,
SLOT(onNotificationClosed(quint32, quint32))
);
sys.connect(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"ActionInvoked",
this,
SLOT(onNotificationInvoked(quint32, const QString&))
);
}
Application::~Application() {}
void Application::quit() {
if (!nowQuitting) {
nowQuitting = true;
emit quitting();
writeSettings();
unreadMessagesCountChanged(0); //this notification persist in the desktop, for now I'll zero it on quit not to confuse people
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed);
itr->second->close();
}
conversations.clear();
dialogueQueue.quit();
if (squawk != nullptr)
squawk->close();
if (trayIcon != nullptr) {
trayIcon->deleteLater();
trayIcon = nullptr;
}
if (!destroyingSquawk)
checkForTheLastWindow();
}
}
void Application::checkForTheLastWindow() {
if (QApplication::topLevelWidgets().size() > 0)
emit readyToQuit();
else
connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit);
}
void Application::createMainWindow() {
if (squawk == nullptr) {
squawk = new Squawk(roster);
connect(squawk, &Squawk::notify, this, &Application::notify);
connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription);
connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation);
connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
connect(squawk, &Squawk::changeState, this, &Application::setState);
connect(squawk, &Squawk::changeTray, this, &Application::onChangeTray);
connect(squawk, &Squawk::itemExpanded, this, &Application::onItemExpanded);
connect(squawk, &Squawk::itemCollapsed, this, &Application::onItemCollapsed);
connect(squawk, &Squawk::quit, this, &Application::quit);
connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest);
connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest);
connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount);
connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount);
connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest);
connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest);
connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest);
connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest);
connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest);
connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest);
connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest);
connect(squawk, &Squawk::requestInfo, core, &Core::Squawk::requestInfo);
connect(squawk, &Squawk::updateInfo, core, &Core::Squawk::updateInfo);
connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath);
connect(core, &Core::Squawk::responseInfo, squawk, &Squawk::responseInfo);
dialogueQueue.setParentWidnow(squawk);
squawk->stateChanged(availability);
squawk->raise();
squawk->show();
squawk->activateWindow();
for (const std::list<QString>& entry : expandedPaths) {
QModelIndex ind = roster.getIndexByPath(entry);
if (ind.isValid())
squawk->expand(ind);
}
connect(squawk, &Squawk::itemExpanded, this, &Application::onItemExpanded);
connect(squawk, &Squawk::itemCollapsed, this, &Application::onItemCollapsed);
}
}
void Application::onSquawkClosing() {
dialogueQueue.setParentWidnow(nullptr);
if (!nowQuitting) {
disconnect(core, &Core::Squawk::responseInfo, squawk, &Squawk::responseInfo);
}
destroyingSquawk = true;
squawk->deleteLater();
squawk = nullptr;
QSettings settings;
if (!nowQuitting && QSystemTrayIcon::isSystemTrayAvailable() && settings.value("tray", false).toBool()) {
if (settings.value("hideTray", false).toBool()) {
createTrayIcon();
}
actionToggle.setText(tr("Show Squawk"));
} else {
quit();
}
}
void Application::onSquawkDestroyed() {
destroyingSquawk = false;
if (nowQuitting)
checkForTheLastWindow();
}
void Application::onChangeTray(bool enabled, bool hide) {
if (enabled) {
if (trayIcon == nullptr) {
if (!hide || squawk == nullptr)
createTrayIcon();
} else {
if (hide && squawk != nullptr) {
trayIcon->deleteLater();
trayIcon = nullptr;
}
}
} else if (trayIcon != nullptr) {
trayIcon->deleteLater();
trayIcon = nullptr;
}
}
void Application::createTrayIcon() {
trayIcon = new QSystemTrayIcon();
QMenu* trayIconMenu = new QMenu();
trayIconMenu->addAction(&actionToggle);
trayIconMenu->addAction(&actionQuit);
trayIcon->setContextMenu(trayIconMenu);
trayIcon->setIcon(QApplication::windowIcon().pixmap(32, 32));
trayIcon->setToolTip(QApplication::applicationDisplayName());
connect(trayIcon, &QSystemTrayIcon::activated, this, &Application::trayClicked);
connect(trayIcon, &QSystemTrayIcon::destroyed, trayIconMenu, &QMenu::deleteLater);
trayIcon->show();
}
void Application::trayClicked(QSystemTrayIcon::ActivationReason reason) {
switch (reason) {
case QSystemTrayIcon::Trigger:
case QSystemTrayIcon::DoubleClick:
case QSystemTrayIcon::MiddleClick:
toggleSquawk();
break;
default:
break;
}
}
void Application::toggleSquawk() {
QSettings settings;
if (squawk == nullptr) {
createMainWindow();
if (settings.value("hideTray", false).toBool()) {
trayIcon->deleteLater();
trayIcon = nullptr;
}
actionToggle.setText(tr("Minimize to tray"));
} else {
squawk->close();
}
}
void Application::onItemCollapsed(const QModelIndex& index) {
std::list<QString> address = roster.getItemPath(index);
if (address.size() > 0)
expandedPaths.erase(address);
}
void Application::onItemExpanded(const QModelIndex& index) {
std::list<QString> address = roster.getItemPath(index);
if (address.size() > 0)
expandedPaths.insert(address);
}
void Application::onAddedElement(const std::list<QString>& path) {
if (squawk != nullptr && expandedPaths.count(path) > 0) {
QModelIndex index = roster.getIndexByPath(path);
if (index.isValid())
squawk->expand(index);
}
}
void Application::notify(const QString& account, const Shared::Message& msg) {
QString jid = msg.getPenPalJid();
QString name = QString(roster.getContactName(account, jid));
QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource()));
QVariantList args;
args << QString();
uint32_t notificationId = qHash(msg.getId());
args << notificationId;
if (path.size() > 0)
args << path;
else
args << QString("mail-message"); //TODO should here better be unknown user icon?
if (msg.getType() == Shared::Message::groupChat)
args << msg.getFromResource() + tr(" from ") + name;
else
args << name;
QString body(msg.getBody());
QString oob(msg.getOutOfBandUrl());
if (body == oob) {
body = tr("Attached file");
}
args << body;
args << QStringList({
"markAsRead", tr("Mark as Read"),
"openConversation", tr("Open conversation")
});
args << QVariantMap({
{"desktop-entry", qApp->desktopFileName()},
{"category", QString("message")},
{"urgency", 1},
// {"sound-file", "/path/to/macaw/squawk"},
{"sound-name", QString("message-new-instant")}
});
args << -1;
notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId())));
if (squawk != nullptr)
QApplication::alert(squawk);
}
void Application::onNotificationClosed(quint32 id, quint32 reason) {
Notifications::const_iterator itr = storage.find(id);
if (itr != storage.end()) {
if (reason == 2) { //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html)
//TODO may ba also mark as read?
}
if (reason != 1) { //just expired, can be activated again from history, so no removing for now
storage.erase(id);
qDebug() << "Notification" << id << "was closed";
}
}
}
void Application::onNotificationInvoked(quint32 id, const QString& action) {
qDebug() << "Notification" << id << action << "request";
Notifications::const_iterator itr = storage.find(id);
if (itr != storage.end()) {
if (action == "markAsRead")
roster.markMessageAsRead(itr->second.first, itr->second.second);
else if (action == "openConversation")
focusConversation(itr->second.first, "", itr->second.second);
}
}
void Application::unreadMessagesCountChanged(int count) {
QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update");
signal << qApp->desktopFileName() + QLatin1String(".desktop");
signal << QVariantMap ({
{"count-visible", count != 0},
{"count", count}
});
QDBusConnection::sessionBus().send(signal);
}
void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId) {
if (squawk != nullptr) {
if (squawk->currentConversationId() != id) {
QModelIndex index = roster.getContactIndex(id.account, id.name, resource);
squawk->select(index);
}
if (squawk->isMinimized())
squawk->showNormal();
else
squawk->show();
squawk->raise();
squawk->activateWindow();
} else {
openConversation(id, resource);
}
SHARED_UNUSED(messageId);
//TODO focus messageId;
}
void Application::setState(Shared::Availability p_availability) {
if (availability != p_availability) {
availability = p_availability;
emit changeState(availability);
}
}
void Application::stateChanged(Shared::Availability state) {
availability = state;
if (squawk != nullptr)
squawk->stateChanged(state);
}
void Application::readSettings() {
QSettings settings;
settings.beginGroup("ui");
int avail;
if (settings.contains("availability"))
avail = settings.value("availability").toInt();
else
avail = static_cast<int>(Shared::Availability::online);
settings.beginGroup("roster");
QStringList entries = settings.allKeys();
for (const QString& entry : entries) {
QStringList p = entry.split("/");
if (p.last() == "expanded" && settings.value(entry, false).toBool()) {
p.pop_back();
expandedPaths.emplace(p.begin(), p.end());
}
}
settings.endGroup();
settings.endGroup();
setState(Shared::Global::fromInt<Shared::Availability>(avail));
createMainWindow();
if (settings.value("tray", false).toBool() && !settings.value("hideTray", false).toBool())
createTrayIcon();
}
void Application::writeSettings() {
QSettings settings;
settings.beginGroup("ui");
settings.setValue("availability", static_cast<int>(availability));
settings.remove("roster");
settings.beginGroup("roster");
for (const std::list<QString>& address : expandedPaths) {
QString path = "";
for (const QString& hop : address)
path += hop + "/";
path += "expanded";
settings.setValue(path, true);
}
settings.endGroup();
settings.endGroup();
}
void Application::requestPassword(const QString& account, bool authenticationError) {
if (authenticationError)
dialogueQueue.addAction(account, DialogQueue::askCredentials);
else
dialogueQueue.addAction(account, DialogQueue::askPassword);
}
void Application::onConversationClosed() {
Conversation* conv = static_cast<Conversation*>(sender());
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
Conversations::const_iterator itr = conversations.find(id);
if (itr != conversations.end())
conversations.erase(itr);
if (conv->isMuc) {
Room* room = static_cast<Room*>(conv);
if (!room->autoJoined())
emit setRoomJoined(id.account, id.name, false);
}
}
void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe) {
Models::Item::Type type = roster.getContactType(id);
switch (type) {
case Models::Item::contact:
if (subscribe)
emit subscribeContact(id.account, id.name, "");
else
emit unsubscribeContact(id.account, id.name, "");
break;
case Models::Item::room:
setRoomAutoJoin(id.account, id.name, subscribe);
if (!isConverstationOpened(id))
emit setRoomJoined(id.account, id.name, subscribe);
break;
default:
break;
}
}
void Application::subscribeConversation(Conversation* conv) {
connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend);
connect(conv, &Conversation::setEncryption, this, &Application::onConversationSetEncryption);
connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
}
void Application::openConversation(const Models::Roster::ElId& id, const QString& resource) {
Conversations::const_iterator itr = conversations.find(id);
Models::Account* acc = roster.getAccount(id.account);
Conversation* conv = nullptr;
bool created = false;
if (itr != conversations.end()) {
conv = itr->second;
} else {
Models::Element* el = roster.getElement(id);
if (el != nullptr) {
if (el->type == Models::Item::room) {
created = true;
Models::Room* room = static_cast<Models::Room*>(el);
conv = new Room(acc, room);
if (!room->getJoined())
emit setRoomJoined(id.account, id.name, true);
} else if (el->type == Models::Item::contact) {
created = true;
conv = new Chat(acc, static_cast<Models::Contact*>(el));
}
}
}
if (conv != nullptr) {
if (created) {
conv->setAttribute(Qt::WA_DeleteOnClose);
subscribeConversation(conv);
conversations.insert(std::make_pair(id, conv));
}
conv->show();
conv->raise();
conv->activateWindow();
if (resource.size() > 0)
conv->setPalResource(resource);
}
}
void Application::onConversationMessage(const Shared::Message& msg) {
Conversation* conv = static_cast<Conversation*>(sender());
QString acc = conv->getAccount();
roster.addMessage(acc, msg);
emit sendMessage(acc, msg);
}
void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg) {
Conversation* conv = static_cast<Conversation*>(sender());
QString acc = conv->getAccount();
roster.changeMessage(acc, msg.getPenPalJid(), originalId, {
{"state", static_cast<uint>(Shared::Message::State::pending)}
});
emit replaceMessage(acc, originalId, msg);
}
void Application::onConversationSetEncryption(Shared::EncryptionProtocol value) {
Conversation* conv = static_cast<Conversation*>(sender());
QString acc = conv->getAccount();
QString jid = conv->getJid();
emit setEncryption(acc, jid, value);
}
void Application::onConversationResend(const QString& id) {
Conversation* conv = static_cast<Conversation*>(sender());
QString acc = conv->getAccount();
QString jid = conv->getJid();
emit resendMessage(acc, jid, id);
}
void Application::onSquawkOpenedConversation() {
subscribeConversation(squawk->currentConversation);
Models::Roster::ElId id = squawk->currentConversationId();
const Models::Element* el = roster.getElementConst(id);
if (el != nullptr && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
emit setRoomJoined(id.account, id.name, true);
}
}
void Application::removeAccount(const QString& account) {
Conversations::const_iterator itr = conversations.begin();
while (itr != conversations.end()) {
if (itr->first.account == account) {
Conversations::const_iterator lItr = itr;
++itr;
Conversation* conv = lItr->second;
disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
conv->close();
conversations.erase(lItr);
} else {
++itr;
}
}
if (squawk != nullptr && squawk->currentConversationId().account == account)
squawk->closeCurrentConversation();
roster.removeAccount(account);
}
void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data) {
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
QString attr = itr.key();
roster.updateAccount(account, attr, *itr);
}
}
void Application::addGroup(const QString& account, const QString& name) {
roster.addGroup(account, name);
if (squawk != nullptr) {
QSettings settings;
settings.beginGroup("ui");
settings.beginGroup("roster");
settings.beginGroup(account);
if (settings.value("expanded", false).toBool()) {
QModelIndex ind = roster.getAccountIndex(account);
squawk->expand(ind);
if (settings.value(name + "/expanded", false).toBool())
squawk->expand(roster.getGroupIndex(account, name));
}
settings.endGroup();
settings.endGroup();
settings.endGroup();
}
}
bool Application::isConverstationOpened(const Models::Roster::ElId& id) const {
return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);}

134
main/application.h Normal file
View File

@ -0,0 +1,134 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef APPLICATION_H
#define APPLICATION_H
#include <map>
#include <list>
#include <set>
#include <QObject>
#include <QDBusInterface>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include "dialogqueue.h"
#include "core/squawk.h"
#include "ui/squawk.h"
#include "ui/models/roster.h"
#include "ui/widgets/conversation.h"
#include "shared/message.h"
#include "shared/enums.h"
/**
* @todo write docs
*/
class Application : public QObject
{
Q_OBJECT
public:
Application(Core::Squawk* core);
~Application();
bool isConverstationOpened(const Models::Roster::ElId& id) const;
signals:
void sendMessage(const QString& account, const Shared::Message& data);
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
void resendMessage(const QString& account, const QString& jid, const QString& id);
void changeState(Shared::Availability state);
void setRoomJoined(const QString& account, const QString& jid, bool joined);
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
void setEncryption(const QString& account, const QString& jid, Shared::EncryptionProtocol value);
void quitting();
void readyToQuit();
public slots:
void readSettings();
void quit();
protected slots:
void notify(const QString& account, const Shared::Message& msg);
void unreadMessagesCountChanged(int count);
void setState(Shared::Availability availability);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account);
void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
void addGroup(const QString& account, const QString& name);
void requestPassword(const QString& account, bool authenticationError);
void writeSettings();
private slots:
void onConversationClosed();
void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
void onSquawkOpenedConversation();
void onConversationMessage(const Shared::Message& msg);
void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
void onConversationResend(const QString& id);
void stateChanged(Shared::Availability state);
void onSquawkClosing();
void onSquawkDestroyed();
void onNotificationClosed(quint32 id, quint32 reason);
void onNotificationInvoked(quint32 id, const QString& action);
void onChangeTray(bool enabled, bool hide);
void trayClicked(QSystemTrayIcon::ActivationReason reason);
void toggleSquawk();
void onItemExpanded(const QModelIndex& index);
void onItemCollapsed(const QModelIndex& index);
void onAddedElement(const std::list<QString>& path);
void onConversationSetEncryption(Shared::EncryptionProtocol value);
private:
void createMainWindow();
void subscribeConversation(Conversation* conv);
void checkForTheLastWindow();
void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = "");
void createTrayIcon();
private:
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
typedef std::map<uint32_t, std::pair<Models::Roster::ElId, QString>> Notifications;
Shared::Availability availability;
Core::Squawk* core;
Squawk* squawk;
QDBusInterface notifications;
Models::Roster roster;
Conversations conversations;
DialogQueue dialogueQueue;
bool nowQuitting;
bool destroyingSquawk;
Notifications storage;
QSystemTrayIcon* trayIcon;
QAction actionQuit;
QAction actionToggle;
std::set<std::list<QString>> expandedPaths;
};
#endif // APPLICATION_H

187
main/dialogqueue.cpp Normal file
View File

@ -0,0 +1,187 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "dialogqueue.h"
#include <QDebug>
DialogQueue::DialogQueue(const Models::Roster& p_roster):
QObject(),
currentSource(),
currentAction(none),
queue(),
collection(queue.get<0>()),
sequence(queue.get<1>()),
prompt(nullptr),
parent(nullptr),
roster(p_roster)
{
}
DialogQueue::~DialogQueue()
{
}
void DialogQueue::quit()
{
queue.clear();
if (currentAction != none) {
actionDone();
}
}
void DialogQueue::setParentWidnow(QMainWindow* p_parent)
{
parent = p_parent;
}
bool DialogQueue::addAction(const QString& source, DialogQueue::Action action)
{
if (action == none) {
return false;
}
if (currentAction == none) {
currentAction = action;
currentSource = source;
performNextAction();
return true;
} else {
if (currentAction != action || currentSource != source) {
std::pair<Queue::iterator, bool> result = queue.emplace(source, action);
return result.second;
} else {
return false;
}
}
}
bool DialogQueue::cancelAction(const QString& source, DialogQueue::Action action)
{
if (source == currentSource && action == currentAction) {
actionDone();
return true;
} else {
Collection::iterator itr = collection.find(ActionId{source, action});
if (itr != collection.end()) {
collection.erase(itr);
return true;
} else {
return false;
}
}
}
void DialogQueue::performNextAction()
{
switch (currentAction) {
case none:
actionDone();
break;
case askPassword: {
QInputDialog* dialog = new QInputDialog(parent);
prompt = dialog;
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
dialog->setInputMode(QInputDialog::TextInput);
dialog->setTextEchoMode(QLineEdit::Password);
dialog->setLabelText(tr("Input the password for account %1").arg(currentSource));
dialog->setWindowTitle(tr("Password for account %1").arg(currentSource));
dialog->setTextValue("");
dialog->exec();
}
break;
case askCredentials: {
CredentialsPrompt* dialog = new CredentialsPrompt(parent);
prompt = dialog;
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
const Models::Account* acc = roster.getAccountConst(currentSource);
dialog->setAccount(currentSource);
dialog->setLogin(acc->getLogin());
dialog->setPassword(acc->getPassword());
dialog->exec();
}
break;
}
}
void DialogQueue::onPropmtAccepted()
{
switch (currentAction) {
case none:
break;
case askPassword: {
QInputDialog* dialog = static_cast<QInputDialog*>(prompt);
emit responsePassword(currentSource, dialog->textValue());
}
break;
case askCredentials: {
CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt);
emit modifyAccountRequest(currentSource, {
{"login", dialog->getLogin()},
{"password", dialog->getPassword()}
});
}
break;
}
actionDone();
}
void DialogQueue::onPropmtRejected()
{
switch (currentAction) {
case none:
break;
case askPassword:
case askCredentials:
emit disconnectAccount(currentSource);
break;
}
actionDone();
}
void DialogQueue::actionDone()
{
prompt->deleteLater();
prompt = nullptr;
if (queue.empty()) {
currentAction = none;
currentSource = "";
} else {
Sequence::iterator itr = sequence.begin();
currentAction = itr->action;
currentSource = itr->source;
sequence.erase(itr);
performNextAction();
}
}
DialogQueue::ActionId::ActionId(const QString& p_source, DialogQueue::Action p_action):
source(p_source),
action(p_action) {}
bool DialogQueue::ActionId::operator < (const DialogQueue::ActionId& other) const
{
if (action == other.action) {
return source < other.source;
} else {
return action < other.action;
}
}
DialogQueue::ActionId::ActionId(const DialogQueue::ActionId& other):
source(other.source),
action(other.action) {}

101
main/dialogqueue.h Normal file
View File

@ -0,0 +1,101 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef DIALOGQUEUE_H
#define DIALOGQUEUE_H
#include <QObject>
#include <QInputDialog>
#include <QMainWindow>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <ui/widgets/accounts/credentialsprompt.h>
#include <ui/models/roster.h>
class DialogQueue : public QObject
{
Q_OBJECT
public:
enum Action {
none,
askPassword,
askCredentials
};
DialogQueue(const Models::Roster& roster);
~DialogQueue();
bool addAction(const QString& source, Action action);
bool cancelAction(const QString& source, Action action);
signals:
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
void responsePassword(const QString& account, const QString& password);
void disconnectAccount(const QString&);
public:
void setParentWidnow(QMainWindow* parent);
void quit();
private:
void performNextAction();
void actionDone();
private slots:
void onPropmtAccepted();
void onPropmtRejected();
private:
QString currentSource;
Action currentAction;
struct ActionId {
public:
ActionId(const QString& p_source, Action p_action);
ActionId(const ActionId& other);
const QString source;
const Action action;
bool operator < (const ActionId& other) const;
};
typedef boost::multi_index_container <
ActionId,
boost::multi_index::indexed_by <
boost::multi_index::ordered_unique <
boost::multi_index::identity <ActionId>
>,
boost::multi_index::sequenced<>
>
> Queue;
typedef Queue::nth_index<0>::type Collection;
typedef Queue::nth_index<1>::type Sequence;
Queue queue;
Collection& collection;
Sequence& sequence;
QDialog* prompt;
QMainWindow* parent;
const Models::Roster& roster;
};
#endif // DIALOGQUEUE_H

58
main/main.cpp Normal file
View File

@ -0,0 +1,58 @@
/*
* 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 "root.h"
#include "shared/messageinfo.h"
#include "shared/identity.h"
#include "shared/info.h"
#include <QObject>
#ifdef WITH_OMEMO
#include <QXmppOmemoStorage.h>
#endif
int main(int argc, char *argv[]) {
qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
qRegisterMetaType<Shared::VCard>("Shared::VCard");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
qRegisterMetaType<std::list<QString>>("std::list<QString>");
qRegisterMetaType<std::set<QString>>("std::set<QString>");
qRegisterMetaType<std::list<Shared::Identity>>("std::list<Shared::Identity>");
qRegisterMetaType<QSet<QString>>("QSet<QString>");
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
qRegisterMetaType<Shared::Availability>("Shared::Availability");
qRegisterMetaType<Shared::EncryptionProtocol>("Shared::EncryptionProtocol");
qRegisterMetaType<Shared::KeyInfo>("Shared::KeyInfo");
qRegisterMetaType<Shared::Info>("Shared::Info");
qRegisterMetaType<Shared::TrustLevel>("Shared::TrustLevel");
#ifdef WITH_OMEMO
qRegisterMetaType<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
qRegisterMetaTypeStreamOperators<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
qRegisterMetaType<QXmppOmemoStorage::Device>("QXmppOmemoStorage::Device");
#endif
Root app(argc, argv);
if (!app.initializeSettings())
return -1;
return app.run();
}

189
main/root.cpp Normal file
View File

@ -0,0 +1,189 @@
// 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 "root.h"
#include <QDebug>
#include <QThread>
#include <QLibraryInfo>
#include <QStandardPaths>
#include <string>
#include <shared/pathcheck.h>
const std::vector<unsigned int> Root::appIconSizes({
16, 24, 32, 48, 64, 96, 128, 256, 512
});
Root::Root(int& argc, char *argv[]) :
QApplication(argc, argv),
signalCatcher(this),
defaultTranslator(),
currentTranslator(),
appIcon(),
settings(),
componentsInitialized(false),
global(nullptr),
coreThread(nullptr),
core(nullptr),
gui(nullptr)
{
setApplicationName("squawk");
setOrganizationName("macaw.me");
setApplicationDisplayName("Squawk");
setApplicationVersion("0.2.3");
setDesktopFileName("squawk");
initializeTranslation();
initializeAppIcon();
global = new Shared::Global(); //important to instantiate after initialization of translations;
}
Root::~Root() {
if (componentsInitialized) {
delete gui;
if (core != nullptr)
delete core;
delete coreThread;
}
delete global;
}
void Root::initializeTranslation() {
defaultTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
installTranslator(&defaultTranslator);
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
bool found = false;
for (QString share : shares) {
found = currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
if (found)
break;
}
if (!found)
currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
installTranslator(&currentTranslator);
}
void Root::initializeAppIcon() {
for (std::vector<unsigned int>::size_type i = 0; i < appIconSizes.size(); ++i)
appIcon.addFile(":images/logo.svg", QSize(appIconSizes[i], appIconSizes[i]));
Root::setWindowIcon(appIcon);
}
bool Root::initializeSettings() {
QVariant vs = settings.value("style");
if (vs.isValid()) {
QString style = vs.toString().toLower();
if (style != "system")
Shared::Global::setStyle(style);
}
if (Shared::Global::supported("colorSchemeTools")) {
QVariant vt = settings.value("theme");
if (vt.isValid()) {
QString theme = vt.toString();
if (theme.toLower() != "system")
Shared::Global::setTheme(theme);
}
}
QString path = Shared::downloadsPathCheck();
if (path.size() > 0) {
settings.setValue("downloadsPath", path);
} else {
qDebug() << "couldn't initialize directory for downloads, quitting";
return false;
}
return true;
}
int Root::run() {
if (!componentsInitialized)
initializeComponents();
coreThread->start();
int result = exec();
qDebug("Event loop stopped");
if (result == 0) {
processEvents(); //I dont like all of this mess
if (coreThread->isRunning()) { //but it's the best solution for now
if (core != nullptr) { //Ideally, following line should never appear in the log
qDebug() << "Core is still seems to be running, killing manually";
core->deleteLater();
coreThread->quit();
processEvents();
core = nullptr;
}
coreThread->wait();
}
}
return result;
}
void Root::initializeComponents() {
core = new Core::Squawk();
coreThread = new QThread();
core->moveToThread(coreThread);
gui = new Application(core);
QObject::connect(&signalCatcher, &SignalCatcher::interrupt, gui, &Application::quit);
QObject::connect(coreThread, &QThread::started, core, &Core::Squawk::start);
QObject::connect(gui, &Application::quitting, core, &Core::Squawk::stop);
QObject::connect(core, &Core::Squawk::quit, core, &Core::Squawk::deleteLater);
QObject::connect(core, &Core::Squawk::destroyed, this, &Root::onCoreDestroyed);
QObject::connect(core, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
QObject::connect(coreThread, &QThread::finished, this, &Root::quit, Qt::QueuedConnection);
componentsInitialized = true;
}
bool Root::notify(QObject* receiver, QEvent* e) {
try {
return QApplication::notify(receiver, e);
} catch(const std::runtime_error& e) {
qDebug() << "std::runtime_error in thread:" << QThread::currentThreadId();
qDebug() << "error message:" << e.what();
} catch(const std::exception& e) {
qDebug() << "std::exception in thread:" << QThread::currentThreadId();
qDebug() << "error message:" << e.what();
} catch(const int& e) {
qDebug() << "integer exception in thread:" << QThread::currentThreadId();
qDebug() << "thrown integer:" << std::to_string(e).c_str();
} catch(...) {
qDebug() << "unhandled exception thread:" << QThread::currentThreadId();
}
qDebug() << "Squawk is crashing...";
exit(1);
return false;
}
void Root::onCoreDestroyed() {
core = nullptr;
}

70
main/root.h Normal file
View File

@ -0,0 +1,70 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef ROOT_H
#define ROOT_H
#include <QApplication>
#include <QTranslator>
#include <QIcon>
#include <QSettings>
#include <QThread>
#include <vector>
#include <core/squawk.h>
#include <core/signalcatcher.h>
#include <shared/global.h>
#include "application.h"
class Root : public QApplication {
Q_OBJECT
public:
Root(int& argc, char* argv[]);
~Root();
bool notify(QObject* receiver, QEvent* e) override;
int run();
bool initializeSettings();
private slots:
void onCoreDestroyed();
private:
void initializeTranslation();
void initializeAppIcon();
void initializeComponents();
private:
static const std::vector<unsigned int> appIconSizes;
SignalCatcher signalCatcher;
QTranslator defaultTranslator;
QTranslator currentTranslator;
QIcon appIcon;
QSettings settings;
bool componentsInitialized;
Shared::Global* global;
QThread* coreThread;
Core::Squawk* core;
Application* gui;
};
#endif // ROOT_H

154
order.h
View File

@ -1,154 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ORDER_H
#define ORDER_H
#include <map>
#include <list>
#include "exception.h"
namespace W
{
template <typename data_type, typename comparator = std::less<data_type>>
class Order
{
public:
class Duplicates:
public Utils::Exception
{
public:
Duplicates():Exception(){}
std::string getMessage() const{return "Inserting element duplicates existing";}
};
class NotFound:
public Utils::Exception
{
public:
NotFound():Exception(){}
std::string getMessage() const{return "Erasing element haven't been found";}
};
protected:
typedef std::list<data_type> List;
public:
typedef typename List::size_type size_type;
typedef typename List::const_iterator const_iterator;
typedef typename List::iterator iterator;
protected:
typedef std::map<data_type, const_iterator, comparator> Map;
typedef typename Map::const_iterator m_const_itr;
typedef typename Map::iterator m_itr;
public:
Order():
order(),
r_map()
{}
~Order() {};
size_type size() const {
return order.size();
}
void push_back(data_type element) {
m_const_itr m_itr = r_map.find(element);
if (m_itr != r_map.end()) {
throw Duplicates();
}
const_iterator itr = order.insert(order.end(), element);
r_map.insert(std::make_pair(element, itr));
}
void erase(data_type element) {
m_const_itr itr = r_map.find(element);
if (itr == r_map.end()) {
throw NotFound();
}
order.erase(itr->second);
r_map.erase(itr);
}
void clear() {
order.clear();
r_map.clear();
}
void insert(const_iterator pos, data_type element) {
m_const_itr m_itr = r_map.find(element);
if (m_itr != r_map.end()) {
throw Duplicates();
}
const_iterator itr = order.insert(pos, element);
r_map.insert(std::make_pair(element, itr));
}
void insert(iterator pos, data_type element) {
m_const_itr m_itr = r_map.find(element);
if (m_itr != r_map.end()) {
throw Duplicates();
}
const_iterator itr = order.insert(pos, element);
r_map.insert(std::make_pair(element, itr));
}
const_iterator find(data_type element) const {
m_const_itr itr = r_map.find(element);
if (itr == r_map.end()) {
return end();
} else {
return itr->second;
}
}
const_iterator begin() const {
return order.begin();
}
const_iterator end() const {
return order.end();
}
iterator begin() {
return order.begin();
}
iterator end() {
return order.end();
}
private:
List order;
Map r_map;
};
}
#endif // ORDER_H

View File

@ -1,23 +1,26 @@
# Maintainer: Yury Gubich <blue@macaw.me> # Maintainer: Yury Gubich <blue@macaw.me>
pkgname=squawk pkgname=squawk
pkgver=0.1.5 pkgver=0.2.4
pkgrel=1 pkgrel=1
pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)" pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
arch=('i686' 'x86_64') arch=('i686' 'x86_64')
url="https://git.macaw.me/blue/squawk" url="https://git.macaw.me/blue/squawk"
license=('GPL3') license=('GPL3')
depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0') depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal-qt6' 'qxmpp-qt6')
makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools') makedepends=('cmake>=3.3' 'imagemagick' 'qt6-tools' 'boost')
optdepends=('kwallet: secure password storage (requires rebuild)') optdepends=('kwallet6: secure password storage (requires rebuild)'
'kconfig6: system themes support (requires rebuild)'
'kconfigwidgets6: system themes support (requires rebuild)'
'kio6: better show in folder action (requires rebuild)')
source=("$pkgname-$pkgver.tar.gz") source=("$pkgname-$pkgver-$pkgrel.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz")
sha256sums=('e1a4c88be9f0481d2aa21078faf42fd0e9d66b490b6d8af82827d441cb58df25') sha256sums=('SKIP')
build() { build() {
cd "$srcdir/squawk" cd "$srcdir/squawk"
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
cmake --build . -j $nproc cmake --build .
} }
package() { package() {
cd "$srcdir/squawk" cd "$srcdir/squawk"
DESTDIR="$pkgdir/" cmake --build . --target install DESTDIR="$pkgdir/" cmake --install .
} }

7
packaging/CMakeLists.txt Normal file
View File

@ -0,0 +1,7 @@
configure_file(squawk.desktop squawk.desktop COPYONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
configure_file(macaw.me.squawk.appdata.xml macaw.me.squawk.appdata.xml COPYONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/macaw.me.squawk.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<component xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:its="http://www.w3.org/2005/11/its" xmlns="https://specifications.freedesktop.org/metainfo/1.0" type="desktop-application">
<id>macaw.me.squawk</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<name>Squawk</name>
<summary>Desktop Qt based XMPP messenger</summary>
<description>
<p>
Squawk is a lightweight XMPP desktop messenger.
The primary objective of this project is to offer
you a fast and user-friendly messaging experience
that closely aligns with your systems style, while
also minimizing resource consumption.
</p>
<p>
Squawk is still at a very early stage and might not suit
everyone but you are welcome to try it out.
</p>
</description>
<launchable type="desktop-id">macaw.me.squawk.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>https://macaw.me/projects/squawk/0.2.2.png</image>
<caption>View XMPP contacts and conversations</caption>
</screenshot>
</screenshots>
<url type="homepage">https://macaw.me/projects/squawk/</url>
<provides>
<binary>squawk</binary>
</provides>
<update_contact>blue@macaw.me</update_contact>
</component>

View File

@ -1,12 +1,14 @@
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Version=1.0 Version=0.2.4
Name=Squawk Name=Squawk
GenericName=Instant Messenger GenericName=Instant Messenger
GenericName[ru]=Мгновенные сообщения GenericName[ru]=Мгновенные сообщения
GenericName[pt_BR]=Mensageiro instantâneo
Comment=XMPP (Jabber) instant messenger client Comment=XMPP (Jabber) instant messenger client
Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями
Comment[pt_BR]=Cliente de mensagem instantânea XMPP (Jabber)
Exec=squawk %u Exec=squawk %u
Icon=squawk Icon=squawk
StartupNotify=true StartupNotify=true

14
plugins/CMakeLists.txt Normal file
View File

@ -0,0 +1,14 @@
if (WITH_KIO)
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
target_link_libraries(openFileManagerWindowJob PRIVATE KF${QT_VERSION_MAJOR}::KIOWidgets)
install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
endif ()
if (WITH_KCONFIG)
add_library(colorSchemeTools SHARED colorschemetools.cpp)
target_link_libraries(colorSchemeTools PRIVATE KF${QT_VERSION_MAJOR}::ConfigCore)
target_link_libraries(colorSchemeTools PRIVATE KF${QT_VERSION_MAJOR}::ConfigWidgets)
install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
endif()

View File

@ -0,0 +1,70 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QIcon>
#include <QPainter>
#include <QFileInfo>
#include <KSharedConfig>
#include <KConfigGroup>
#include <KColorScheme>
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection);
extern "C" QIcon* createPreview(const QString& path) {
KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path);
QIcon* result = new QIcon();
KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig);
KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig);
KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig);
KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig);
result->addPixmap(createPixmap(16, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
result->addPixmap(createPixmap(24, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
return result;
}
extern "C" void deletePreview(QIcon* icon) {
delete icon;
}
extern "C" void colorSchemeName(const QString& path, QString& answer) {
KSharedConfigPtr config = KSharedConfig::openConfig(path);
KConfigGroup group(config, QStringLiteral("General"));
answer = group.readEntry("Name", QFileInfo(path).baseName());
}
extern "C" void createPalette(const QString& path, QPalette& answer) {
KSharedConfigPtr config = KSharedConfig::openConfig(path);
answer = KColorScheme::createApplicationPalette(config);
}
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) {
QPixmap pix(size, size);
pix.fill(Qt::black);
QPainter p;
p.begin(&pix);
const int itemSize = size / 2 - 1;
p.fillRect(1, 1, itemSize, itemSize, window);
p.fillRect(1 + itemSize, 1, itemSize, itemSize, button);
p.fillRect(1, 1 + itemSize, itemSize, itemSize, view);
p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, selection);
p.end();
return pix;
}

View File

@ -0,0 +1,26 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QUrl>
#include <QObject>
#include <KIO/OpenFileManagerWindowJob>
extern "C" void highlightInFileManager(const QUrl& url) {
KIO::OpenFileManagerWindowJob* job = KIO::highlightInFileManager({url});
QObject::connect(job, &KIO::OpenFileManagerWindowJob::result, job, &KIO::OpenFileManagerWindowJob::deleteLater);
}

56
resources/CMakeLists.txt Normal file
View File

@ -0,0 +1,56 @@
target_sources(squawk PRIVATE resources.qrc)
configure_file(images/logo.svg squawk.svg COPYONLY)
configure_file(squawk.rc squawk.rc COPYONLY)
set(CONVERT_BIN magick)
execute_process(COMMAND ${CONVERT_BIN} -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
if (WIN32)
execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE)
target_sources(squawk PRIVATE ${SQUAWK_WIN_RC})
endif (WIN32)
if (APPLE)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset")
execute_process(COMMAND ${CONVERT_BIN} -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${CONVERT_BIN} -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
set(MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} PARENT_SCOPE)
set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns)
set(APP_ICON_MACOSX ${APP_ICON_MACOSX} PARENT_SCOPE)
target_sources(squawk PRIVATE ${APP_ICON_MACOSX})
set_source_files_properties(${APP_ICON_MACOSX} TARGET_DIRECTORY squawk PROPERTIES
MACOSX_PACKAGE_LOCATION "Resources")
if (CMAKE_BUILD_TYPE STREQUAL "Release")
if (APPLE)
set_target_properties(squawk PROPERTIES
MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
endif (APPLE)
endif ()
endif (APPLE)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)

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

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