Compare commits

...

225 Commits

Author SHA1 Message Date
antonpavanvo 1af20e27f2 fix: Pop up windows: About, Accounts, Account, Settings - opening on
centr of parent
2022-05-27 10:02:36 +04:00
antonpavanvo ea7dcc5f18 fix: About window now is a dialog 2022-05-26 19:00:18 +04:00
Blue 645b92ba51
release 0.2.2 preparation 2022-05-05 20:46:49 +03:00
Blue 80c5e2f2b4
added en lolcalization file, actualized localizations 2022-05-04 19:20:30 +03:00
Blue 1f065f23e6
double click word selection handle, sigint sermentation fault fix 2022-05-03 12:17:08 +03:00
Blue 3c48577eee
selection message body now actually working 2022-05-02 22:25:50 +03:00
Blue 0340db7f2f
first successfull attempt to visualize selection on message body 2022-05-01 23:19:52 +03:00
Blue c3a45ec58c
merge conflicts, text copying from context menu in message line 2022-04-30 21:41:25 +03:00
Blue 7ba94e9deb
link clicking and hovering in message body now works! 2022-04-29 00:29:44 +03:00
Blue eac87e713f
seem to have found a text block, to activate with the click later 2022-04-28 00:08:59 +03:00
Blue d86e2c28a0
an attempt to display text in a better way with QTextDocument + QTextBrowser 2022-04-27 01:17:53 +03:00
Blue 2fcc432aef
some polish 2022-04-26 23:08:25 +03:00
Blue e58213b294
Now notifications have actions! Some more usefull functions to roster model 2022-04-24 18:52:29 +03:00
Blue 3916aec358
unread messages count now is displayed on the launcher icon 2022-04-23 16:58:08 +03:00
Blue 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
Blue 83cb220175
better notification sending, edited message now modifies notification (or sends), little structure change 2022-04-19 20:24:41 +03:00
Blue 18859cb960
first ideas for notifications 2022-04-18 19:54:42 +03:00
Blue 4c20a314f0
a crash fix on one of archive corner cases 2022-04-17 16:25:15 +03:00
Blue 51ac1ac709
first attempt 2022-04-17 14:58:46 +03:00
Blue 8f949277f6
actual pasword reasking on failed authentication 2022-04-14 11:13:27 +03:00
Blue ce686e121b
account removal bugfix, some testing 2022-04-13 22:02:48 +03:00
Blue 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
Blue 2c26c7e264
ui squawk refactoring 2022-04-11 18:45:12 +03:00
Blue 69e0c88d8d
account refactoring, pep support discovery started 2022-04-08 19:18:15 +03:00
Blue 82d54ba4df
Report bugs tab and thanks to tab in about widget 2022-04-07 18:26:43 +03:00
Blue 1b66fda318
License is now can be viewed locally, some organization name packaging issies 2022-04-05 22:00:56 +03:00
Blue 9f746d203b
new tab in About: components 2022-04-04 23:49:01 +03:00
Blue 27377e0ec5
first attempt to make About window 2022-04-03 23:53:46 +03:00
Blue 4baa3bccbf
new screenshot 2022-04-02 16:09:11 +03:00
Blue 4786388822
0.2.1 2022-04-02 15:53:23 +03:00
Blue 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
Blue 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
Blue 5f6691067a
minor bugfixes about message body, automatic focus and that quirk with font becomming bigger 2022-03-29 19:05:24 +03:00
Blue 788c6ca556
now it's possible to fix your messages 2022-03-28 23:25:33 +03:00
Blue bf4a27f35d
Bug with the edited message fixed, some further work on message correction 2022-03-27 22:05:31 +03:00
Blue 0823b35148
removed unused old message line files, first thoughts on message edition 2022-02-20 22:10:09 +03:00
Blue 73b1b58a96
Downloads folder now is movable 2022-02-19 21:31:49 +03:00
Blue 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
Blue 243edff8bd
first thoughts about downloads path changing 2022-02-17 20:26:15 +03:00
Blue da19eb86bb
color theme setting is now working 2022-01-27 20:44:32 +03:00
Blue 0ff9f12157
new optional KDE Frameworks plugin to support system color schemes 2022-01-26 23:53:44 +03:00
Blue 802e2f11a1
may be a bit better quit handling 2022-01-25 23:35:55 +03:00
Blue c708c33a92
basic theme changing 2022-01-21 22:02:50 +03:00
Blue a8a7ce2538
some more thoughts about settings widgets 2022-01-19 23:46:42 +03:00
Blue 841e526e59
just some toying with designer 2022-01-17 23:52:07 +03:00
Blue 6bee149e6b
started to work on settings 2022-01-16 22:54:57 +03:00
Blue 62a59eb7a1
Added logs for Shura to help me to debug a download attachment issue 2022-01-15 15:36:49 +03:00
Blue 296328f12d
a bit of polish 2022-01-11 23:50:42 +03:00
Blue 4d3ba6b11f
0.2.0 finalization 2022-01-09 17:32:23 +03:00
Blue 8a2658e4fc
message bubbles, avatar rounding, roster adjusments 2022-01-09 01:28:29 +03:00
Blue 9ac0ca10f3
avatar painting is returned to delegate; sender names now are not painted in every message 2022-01-07 17:02:49 +03:00
Blue 7130e674c4
some warnings fixed, new way of drawing avatars in message line 2022-01-05 22:29:34 +03:00
Blue 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
Blue 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
Blue 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
Blue 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
Blue 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
Blue 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
Blue 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
Blue 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
shunf4 39f2f3d975 feat: copy pasted image file to download folder after successful upload 2021-10-16 00:21:02 +08:00
shunf4 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
shunf4 50d710de04 remove ./signalcatcher_win32.cpp 2021-10-13 11:41:48 +08:00
Blue 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
shunf4 3a70df21f8 feat: paste image in chat 2021-10-06 23:09:18 +08:00
shunf4 a24e8382d1 correctly retrieve latest archived messages per XEP-0313 2021-10-06 23:01:11 +08:00
shunf4 d20fd84d39 respect password type when adding account, preventing loading bad password 2021-10-06 22:55:23 +08:00
shunf4 5862f1552b don't save settings on quit, if readSettings() not finished 2021-10-06 22:52:20 +08:00
shunf4 ebeb4089eb add fallback icons for buttons 2021-10-06 22:45:10 +08:00
shunf4 a53126d8bc messages may have the same timestamp, put MDB_DUPSORT flag with order db 2021-10-06 22:04:29 +08:00
shunf4 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
shunf4 67e5f9744e fix ci macos matrix item 2021-10-06 18:50:00 +08:00
shunf4 d0bdb374a0 add flag -fno-sized-deallocation, eliminating _ZdlPvm 2021-10-06 18:47:59 +08:00
shunf4 8b3752ef47 fix ci for macos; adjust ci for linux executable with changed rpath 2021-10-06 18:12:26 +08:00
shunf4 8c6ac1a21d add icns to macos bundle; use macdeployqt post build 2021-10-06 17:34:33 +08:00
shunf4 1c0802c8ca fix win32 ci by place LMDB_INCLUDE_DIRS in core/ 2021-10-06 01:51:01 +08:00
shunf4 d84a33e144 fix linux ci by finding and linking thread libraries 2021-10-06 01:33:58 +08:00
shunf4 4f6946a5fc add LMDB_INCLUDE_DIRS as include dir, fixing win32 ci 2021-10-06 01:28:08 +08:00
shunf4 f3153ef1db ci: add appveyor.yml 2021-10-06 01:17:30 +08:00
shunf4 41d9fa7a1c fix macos bundle build 2021-10-06 01:07:47 +08:00
shunf4 261b34b712 add forgotten cmake/MacOSXBundleInfo.plist.in 2021-10-06 00:55:39 +08:00
shunf4 d1f108e69d update docs for win32 build 2021-10-06 00:53:50 +08:00
shunf4 1e37aa762c generate app bundle for macOS 2021-10-06 00:48:25 +08:00
shunf4 a1f3c00a54 remove dependency uuid 2021-10-06 00:15:25 +08:00
shunf4 923afe2420 [Fix] Merge branch 'feat/adapt_win_osx' into upstream_messageFeed 2021-10-05 23:45:37 +08:00
shunf4 faa7d396a5 add liblmdb.so as possible lmdb lib name 2021-10-05 16:09:31 +08:00
shunf4 c55b7c6baf update docs for removed uuid dep and LMDB_DIR var 2021-10-05 15:20:25 +08:00
shunf4 6764d8ea11 remove dependency libuuid 2021-10-05 12:56:36 +08:00
shunf4 5fbb96618f adjust CMakeLists.txt, to prepare for win32 and macos builds 2021-10-05 12:49:06 +08:00
Blue d89c030e66
translation verification
Portugues Brazil localization added provided by most welcome Bruno Fontes
2021-09-22 23:43:03 +03:00
Blue 5787af8a4f
Merge remote-tracking branch 'origin/master' into messageFeed 2021-09-22 23:09:48 +03:00
Blue 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
Blue 3f09b8f838
Date dividers between messages from different dates 2021-09-22 01:17:43 +03:00
Bruno F. Fontes 87c216b491
Add Brazilian Portuguese translations 2021-07-21 19:37:31 -03:00
Blue 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
Blue 3f1fba4de2
doovers for failed messages, some corner cases fixes with handling errors during message sending 2021-05-23 01:03:14 +03:00
Blue ddfaa63a24
big image preview optimisations, preview positioning fix, memory leaks fix 2021-05-17 23:32:44 +03:00
Blue 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
Blue 0d584c5aba
message preview refactor, several bugs about label size, animations are now playing in previews 2021-05-16 01:07:49 +03:00
Blue 4307262f6e basic error download/upload files handling, need more testing 2021-05-14 22:49:38 +03:00
Blue 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
Blue 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
Blue 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
Blue 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
Blue 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
Blue d514db9c4a message context menu began, open and show in folder features 2021-05-04 17:09:41 +03:00
Blue f34289399e text inside of message is selectable again, links are clickable, some refactor, some bugfixes 2021-05-03 14:23:41 +03:00
Blue 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
Blue 216dcd29e9 bug fixing, better progres indicator positioning 2021-05-02 02:03:08 +03:00
Blue 0973cb2991 first lousy attempt to make load indicator in feedView 2021-04-30 23:07:00 +03:00
Blue 50190f3eac handled a case when user removes downloaded file, minor optimizations on message changing 2021-04-28 23:26:19 +03:00
Blue b44873d587 pal resourse sticking, notifications of unread messages, new message icons 2021-04-27 22:29:15 +03:00
Blue 4c5efad9dc CMake build error, status icon text tooltip 2021-04-26 19:37:36 +03:00
Blue 8310708c92 First attemtps to upload files, debug, reused of once uploaded or downloaded files 2021-04-23 14:53:48 +03:00
Blue d936c0302d bug with downloads in group chats, status icons in messages, visuals, feedView optimisations 2021-04-23 01:41:32 +03:00
Blue 0e937199b0 debug and actual first way to display pictures in messageFeed 2021-04-21 00:56:47 +03:00
Blue 48e498be25 some debug, message changing in messageFeed 2021-04-20 00:49:24 +03:00
Blue 3a7735b192 First steps on the new idea of file up/downloading 2021-04-18 15:49:20 +03:00
Blue 8f914c02a7 temp url storage commit 2021-04-13 16:27:31 +03:00
Blue 50bb3f5fd7 started progress bars, changed gcc standard to 17 2021-03-22 21:04:26 +03:00
Blue a0348b8fd2 file progress events delivery methonds 2021-02-27 15:21:27 +03:00
Blue 85555da81f just a temp one 2021-02-07 20:02:11 +03:00
Blue ebe5addfb5 just proxying button event from feed view delegate to the feed model 2021-02-06 14:02:42 +03:00
Blue b3c6860e25 seems like i have found solution how to properly render buttons 2021-02-02 01:55:15 +03:00
Blue 00ffbac6b0 initial attempt to paint buttons in the messagefeed 2021-01-14 14:22:02 +03:00
Blue ff4124d1f0 Resolved the bug about crash with an empty history chat 2021-01-12 20:15:21 +03:00
Blue 15342f3c53 self nick in the chat fix, hovering message feature 2021-01-08 00:50:12 +03:00
Blue 270a32db9e achive from the beginning memorizing bugfix, limitation of the requests in the model 2020-08-21 23:57:48 +03:00
Blue e0ef1ef797 Some basic message painting 2020-08-21 00:32:30 +03:00
Blue e1eea2f3a2 made the first prototype, scrolling and word wrapping seems to be working 2020-08-17 13:27:14 +03:00
Blue 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
Blue 4e6bd04b02 experimenting with qml 2020-08-12 19:55:01 +03:00
Blue 38159eafeb first compiling prototype, doesnt work yet 2020-08-12 01:49:51 +03:00
Blue ef1a9846bf found a way to avoid requesting all the MUCs history alltogether on startup 2020-08-09 19:28:03 +03:00
Blue 3a120c773a reconnection issues 2020-08-08 02:33:03 +03:00
Blue 5f64321c2a fix compilation on older qt versions 2020-08-04 17:52:16 +03:00
Blue a543eb1aef 0.1.5 2020-07-29 23:26:56 +03:00
Blue 480c78cf61 non lower cased jids error handled 2020-07-26 22:41:30 +03:00
Blue 0dcfc5eedc some better cleanup and restore state on connect disconnect, workaround for that wired undefined condition error on every other reconnection 2020-06-21 01:26:30 +03:00
Blue 87426ee20f account refactoring 2020-06-15 00:23:43 +03:00
Blue 20bcae5ab2 finally history works in mucs 2020-05-22 19:28:26 +03:00
Blue 6b65910ded stanzaId based muc archive request, account object refactoring 2020-05-21 18:42:40 +03:00
Blue 9ca4aa29d4 started account refactoring 2020-04-28 23:35:52 +03:00
Blue a625ecb47b Merge pull request 'rosterReferences' (#39) from rosterReferences into master 2020-04-19 13:18:24 +00:00
Blue 55ae5858b5 some workaround about disconnection segfault 2020-04-19 16:13:15 +03:00
Blue 9c855553c5 referencing seems to be working now 2020-04-18 15:02:01 +03:00
Blue 83a2e6af85 first prototype 2020-04-18 02:17:47 +03:00
Blue 494afcf2b5 initial class reference 2020-04-16 23:23:02 +03:00
Blue a8698cc94f minor bugfixes 2020-04-15 20:27:38 +03:00
Blue b50ce146b8 attaching messages fix, bad alloc fix 2020-04-15 16:48:49 +03:00
Blue 6657dc79ce qxmpp bump, ifdef to assemble on lower versions 2020-04-14 22:40:32 +03:00
Blue bdca0aa6b1 Merge pull request 'safePasswords' (#36) from safePasswords into master 2020-04-14 16:32:28 +00:00
Blue cb44b12a7e 0.1.4 kwallet optimisation related fix, DnD files into convs, visual fixes 2020-04-14 19:30:33 +03:00
Blue 21c7d65027 offline avatars in mucs 2020-04-13 22:57:23 +03:00
Blue 29c7d31c89 context menu now doesn't select items, just temporarily, statuses and messahes html wrapping fix 2020-04-12 18:55:05 +03:00
Blue a77dfd191a single window mode 2020-04-11 23:00:15 +03:00
Blue b95028e33e testing, ability to build without kwallet, translations, disabling unsupported storage types in combobox 2020-04-11 01:15:08 +03:00
Blue 543538fc56 first working prototype of dynamically loaded kwallet storage 2020-04-10 01:48:08 +03:00
Blue 7ce27d1c11 pasword storing options: jammed an alwaysAsk, external lib for password jamming, changelog 2020-04-07 23:33:03 +03:00
Blue 95f0d4008a minor fix about updating muc avatars 2020-04-05 16:25:27 +03:00
Blue 3477226367 first moves to safe pasword storing, preparing the structure 2020-04-04 19:40:32 +03:00
Blue ddfb3419cc Shared namespace refactoring, enum class refactoring, going offline related crash fix 2020-04-04 01:28:15 +03:00
Blue b309100f99 0.1.3 release 2020-03-31 18:56:49 +03:00
Blue c793f56647 a little bit of restyling, now the integration with transparent themes is supposed to be better 2020-03-31 00:17:10 +03:00
Blue ff2c9831cf corrected messages now are supposed to display correctly 2020-03-28 17:05:49 +03:00
Blue fe1ae8567a Message receipt manager not takes care of the message reception, switched on embedded reconnect 2020-03-27 23:59:30 +03:00
Blue 57d6e3adab Message error handling as state, errorText to store, fake ID for message without 2020-03-26 18:08:44 +03:00
Blue 91cc851bfc delivery statuses now actually mean something for MUC messages 2020-03-25 18:28:36 +03:00
Blue 6d1b83d0f8 ui logick of changing message, not connected yet 2020-02-08 14:44:15 +03:00
Blue ed56cca2e7 some visual tweaks, moving on message delivery statuses 2020-02-04 18:14:51 +03:00
Blue a4136ff9fe progress spinner fix, new lines in messages now display again 2020-01-27 18:07:38 +03:00
Blue 565449f176 bug with selection, some right padding to the avatars 2020-01-25 11:10:24 +03:00
Blue 626227db93 file comment fix, avatar dropping bug fix, url detection bug fix 2020-01-16 11:52:54 +03:00
Blue 13a1970047 a method for setting states to messages 2020-01-07 12:26:07 +03:00
Blue ad1977f05f badges and screenshots in readme typo 2020-01-02 17:22:51 +03:00
Blue 0a4c4aa042 badges and screenshots in readme typo 2020-01-02 17:19:00 +03:00
Blue 5e7c247bb4 badges and screenshots in readme 2020-01-02 17:18:03 +03:00
Blue 5a59d54b18 first thoughts on message states 2020-01-02 15:04:49 +03:00
Blue 52efc2b1a4 now we have avatars in muc chats 2019-12-31 21:14:12 +03:00
Blue 55703c2007 muc participant avatars 2019-12-30 23:22:04 +03:00
Blue efc90e18c3 verion bump 2019-12-25 14:11:29 +03:00
Blue 3e594c7e13 connectivity, roster position size and state, expanded anccounts and groups restoration with the settings 2019-12-25 13:24:20 +03:00
Blue 0bcfd779b8 some bugs and corner cases 2019-12-24 11:42:46 +03:00
Blue dd62f84acc pal avatars in one on one dialogs 2019-12-23 09:28:23 +03:00
Blue f13b43d38b some bug fixes, initial work on avatars in dialog windows 2019-12-20 18:41:20 +03:00
Blue 867c3a18e9 avatars for mucs, some tooltip tweaks 2019-12-17 19:54:53 +03:00
Blue f367502b8e Merge branch 'master' of git.macaw.me:blue/squawk 2019-12-06 11:01:06 +03:00
Blue fe27955689 uploading multiple messages fix, some warnings fix 2019-12-06 11:00:48 +03:00
Blue 0c33d81c59 insignifican fixes about urls parsing 2019-12-06 11:00:09 +03:00
Blue 0cb25a25cf uploading multiple messages fix, some warnings fix 2019-11-17 13:24:12 +03:00
Blue 09a946c204 Merge branch 'fileUpload' of blue/squawk into master 2019-11-16 18:03:47 +00:00
Blue ae3a1c97e3 uploading message destruction bug, optimisations for release warnings for debug, packaging, readme 2019-11-15 16:30:29 +03:00
Blue 326eef864b some fixes about uploading, some error handling 2019-11-14 14:43:43 +03:00
Blue 166a7ac83a first working prototype of file upload 2019-11-12 16:38:01 +03:00
Blue a6e48599aa some progress on upload 2019-11-11 18:19:54 +03:00
Blue 3f710e26d0 some more work about uploading, not done yet 2019-11-09 17:04:27 +03:00
Blue 09749bac51 Merge branch 'master' into fileUpload 2019-11-09 12:54:30 +03:00
Blue 0b78470aaa Merge branch 'vCard' of blue/squawk into master
vCard support
2019-11-08 07:47:37 +00:00
Blue dfa4d10c36 some VCard polishing, missing icons and translations 2019-11-07 14:17:46 +03:00
Blue 2c13f0d77c phones now also saveble 2019-11-06 18:14:49 +03:00
Blue c1c1de1b7b backend adapter for vCard phones list 2019-11-05 21:55:21 +03:00
Blue 5bbacad84a VCard: email list now displays and stores on server vcard 2019-11-04 18:22:39 +03:00
Blue 0b57e6a77f Refactoring of signal/slots connection to new qt syntax 2019-11-03 21:46:40 +03:00
Blue 9d491e9e93 deprecation fix about qxmpp, beginning of adding email addresses in VCards 2019-11-02 23:50:25 +03:00
Blue c067835b00 button to add addresses phones and emails, yet dummy 2019-11-01 18:06:03 +03:00
Blue e924715a57 progress for VCard widget, date of receiving 2019-10-31 17:01:48 +03:00
Blue f3515f1104 refactored progress 2019-10-30 16:47:21 +03:00
Blue 566fc1f2fb new fallback icons, new fields in vCard, fix about recreation new avatar on each request 2019-10-25 16:38:48 +03:00
Blue 36c71968bc basic avatar/vcard changes uploads and roster reaction 2019-10-24 12:42:38 +03:00
Blue 652381b067 changing avatar in local vcard, no uploading yet 2019-10-23 17:49:56 +03:00
Blue 2a37f36b83 first primitive vcard in graphic interface 2019-10-22 18:13:56 +03:00
Blue c4d22c9c14 some methods to cVard 2019-10-21 18:02:41 +03:00
Blue e7be046e9f vcard ui file 2019-10-20 22:39:11 +03:00
Blue d050cd82dd some lines to vCard object 2019-10-19 22:34:25 +03:00
Blue 46e74ad5e8 initial vCard class, signal-slots refactoring 2019-10-17 23:54:27 +03:00
Blue dc1ec1c9d4 receiving account owner vCard, displaying avatars in roster 2019-10-16 22:38:35 +03:00
Blue 64e33b6139 receiving avatars, generating missing avatars, storing state of avatars, global color palette 2019-10-15 22:25:40 +03:00
Blue c678a790e5 some more methods for archive to store avatar states 2019-10-14 23:18:51 +03:00
Blue 323a549eca got rid of organization name, made building with system qxmpp by default 2019-10-10 14:45:21 +03:00
Blue 7c32056918 working on file upload 2019-09-23 15:09:15 +03:00
229 changed files with 26432 additions and 5627 deletions

153
CHANGELOG.md Normal file
View File

@ -0,0 +1,153 @@
# Changelog
## Squawk 0.2.2 (May 05, 2022)
### Bug fixes
- now when you remove an account it actually gets removed
- segfault on unitialized Availability in some rare occesions
- fixed crash when you open a dialog with someone that has only error messages in archive
- message height is now calculated correctly on Chinese and Japanese paragraphs
- the app doesn't crash on SIGINT anymore
### Improvements
- there is a way to disable an account and it wouldn't connect when you change availability
- if you cancel password query an account becomes inactive and doesn't annoy you anymore
- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
- if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it
- accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting
- actualized translations, added English localization file
### New features
- new "About" window with links, license, gratitudes
- if the authentication failed Squawk will ask againg for your password and login
- now there is an amount of unread messages showing on top of Squawk launcher icon
- notifications now have buttons to open a conversation or to mark that message as read
## Squawk 0.2.1 (Apr 02, 2022)
### Bug fixes
- build in release mode now no longer spams warnings
- build now correctly installs all build plugin libs
- a bug where the correction message was received, the indication was on but the text didn't actually change
- message body now doesn't intecept context menu from the whole message
- message input now doesn't increase font when you remove everything from it
### Improvements
- reduced amount of places where platform specific path separator is used
- now message input is automatically focused when you open a dialog or a room
- what() method on unhandled exception now actually tells what happened
### New features
- the settings are here! You con config different stuff from there
- now it's possible to set up different qt styles from settings
- if you have KConfig nad KConfigWidgets packages installed - you can choose from global color schemes
- it's possible now to choose a folder where squawk is going to store downloaded files
- now you can correct your message
## Squawk 0.2.0 (Jan 10, 2022)
### Bug fixes
- carbon copies switches on again after reconnection
- requesting the history of the current chat after reconnection
- global availability (in drop down list) gets restored after reconnection
- status icon in active chat changes when presence of the pen pal changes
- infinite progress when open the dialogue with something that has no history to show
- fallback icons for buttons, when no supported theme is installed (shunf4)
- better handling messages with no id (shunf4)
- removed dependency: uuid, now it's on Qt (shunf4)
- better requesting latest history (shunf4)
### Improvements
- slightly reduced the traffic on the startup by not requesting history of all MUCs
- completely rewritten message feed, now it works way faster and looks cooler
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
- once uploaded local files don't get second time uploaded - the remote URL is reused
- way better compilation time (vae)
### New features
- pasting images from clipboard to attachment (shunf4)
- possible compilation for windows and macOS (shunf4)
## Squawk 0.1.5 (Jul 29, 2020)
### Bug fixes
- error with sending attached files to the conference
- error with building on lower versions of QXmpp
- error on the first access to the conference database
- quit now actually quits the app
- history in MUC now works properly and requests messages from server
- error with handling non lower cased JIDs
- some workaround upon reconnection
## Squawk 0.1.4 (Apr 14, 2020)
### New features
- message line now is in the same window with roster (new window dialog is still able to opened on context menu)
- several new ways to manage your account password:
- store it in plain text with the config (like it always was)
- store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)
- ask the account password on each program launch
- store it in KWallet which is dynamically loaded
- dragging into conversation now attach files
### Bug fixes
- never updating MUC avatars now get updated
- going offline related segfault fix
- statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there
- messages and statuses don't loose content if you use < ore > symbols
- now avatars of those who are not in the MUC right now but was also display next to the message
- fix crash on attempt to attach the same file for the second time
## Squawk 0.1.3 (Mar 31, 2020)
### New features
- delivery states for the messages
- delivery receipts now work for real
- avatars in conferences
- edited messages now display correctly
- restyling to get better look with different desktop themes
### Bug fixes
- clickable links now detects better
- fixed lost messages that came with no ID
- avatar related fixes
- message carbons now get turned on only if the server supports them
- progress spinner fix
- files in dialog now have better comment
## Squawk 0.1.2 (Dec 25, 2019)
### New features
- icons in roster for conferences
- pal avatar in dialog window
- avatars next to every message in dialog windows (not in conferences yet)
- roster window position and size now are stored in config
- expanded accounts and groups are stored in config
- availability (from top combobox) now is stored in config
### Bug fixes
- segfault when sending more then one attached file
- wrong path and name of saving file
- wrong message syntax when attaching file and writing text in the save message
- problem with links highlighting in dialog
- project building fixes
## Squawk 0.1.1 (Nov 16, 2019)
A lot of bug fixes, memory leaks fixes
### New features
- exchange files via HTTP File Upload
- download VCards of your contacts
- upload your VCard with information about your contact phones email addresses, names, career information and avatar
- avatars of your contacts in roster and in notifications
## Squawk 0.0.5 (Oct 10, 2019)
### Features
- chat directly
- have multiple accounts
- add contacts
- remove contacts
- assign contact to different groups
- chat in MUCs
- join MUCs, leave them, keep them subscribed or unsubscribed
- download attachmets
- local history
- desktop notifications of new messages

View File

@ -1,65 +1,173 @@
cmake_minimum_required(VERSION 3.0)
project(squawk)
cmake_minimum_required(VERSION 3.4)
project(squawk VERSION 0.2.2 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 11)
cmake_policy(SET CMP0076 NEW)
cmake_policy(SET CMP0079 NEW)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
include(GNUInstallDirs)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
find_package(Qt5Widgets CONFIG REQUIRED)
find_package(Qt5LinguistTools)
set(squawk_SRC
main.cpp
global.cpp
exception.cpp
signalcatcher.cpp
)
configure_file(resources/images/logo.svg squawk.svg COPYONLY)
execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
configure_file(packaging/squawk.desktop squawk.desktop COPYONLY)
set(TS_FILES
translations/squawk.ru.ts
)
qt5_add_translation(QM_FILES ${TS_FILES})
add_custom_target(translations ALL DEPENDS ${QM_FILES})
qt5_add_resources(RCC resources/resources.qrc)
add_executable(squawk ${squawk_SRC} ${RCC})
target_link_libraries(squawk Qt5::Widgets)
add_subdirectory(ui)
add_subdirectory(core)
option(SYSTEM_QXMPP "Use system qxmpp lib" OFF)
if(NOT SYSTEM_QXMPP)
add_subdirectory(external/qxmpp)
set(WIN32_FLAG "")
set(MACOSX_BUNDLE_FLAG "")
if (CMAKE_BUILD_TYPE STREQUAL "Release")
if (WIN32)
set(WIN32_FLAG WIN32)
endif(WIN32)
if (APPLE)
set(MACOSX_BUNDLE_FLAG MACOSX_BUNDLE)
endif(APPLE)
endif()
target_link_libraries(squawk squawkUI)
target_link_libraries(squawk squawkCORE)
target_link_libraries(squawk uuid)
add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
add_dependencies(${CMAKE_PROJECT_NAME} translations)
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
option(WITH_KWALLET "Build KWallet support module" ON)
option(WITH_KIO "Build KIO support module" ON)
option(WITH_KCONFIG "Build KConfig support module" ON)
# Dependencies
## Qt
set(QT_VERSION_MAJOR 5)
find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
find_package(Boost COMPONENTS)
target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
target_include_directories(squawk PRIVATE ${Qt5_INCLUDE_DIRS})
target_include_directories(squawk PRIVATE ${Qt5Widgets_INCLUDE_DIRS})
target_include_directories(squawk PRIVATE ${Qt5DBus_INCLUDE_DIRS})
target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS})
target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS})
target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS})
target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS})
## QXmpp
if (SYSTEM_QXMPP)
find_package(QXmpp CONFIG)
if (NOT QXmpp_FOUND)
set(SYSTEM_QXMPP OFF)
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
else ()
message("Building with system QXmpp")
endif ()
endif ()
if (NOT SYSTEM_QXMPP)
target_link_libraries(squawk PRIVATE qxmpp)
add_subdirectory(external/qxmpp)
else ()
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
endif ()
## KIO
if (WITH_KIO)
find_package(KF5KIO CONFIG)
if (NOT KF5KIO_FOUND)
set(WITH_KIO OFF)
message("KIO package wasn't found, KIO support modules wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KIO)
message("Building with support of KIO")
endif ()
endif ()
## KWallet
if (WITH_KWALLET)
find_package(KF5Wallet CONFIG)
if (NOT KF5Wallet_FOUND)
set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
message("Building with support of KWallet")
endif ()
endif ()
if (WITH_KCONFIG)
find_package(KF5Config CONFIG)
if (NOT KF5Config_FOUND)
set(WITH_KCONFIG OFF)
message("KConfig package wasn't found, KConfig support modules wouldn't be built")
else()
find_package(KF5ConfigWidgets CONFIG)
if (NOT KF5ConfigWidgets_FOUND)
set(WITH_KCONFIG OFF)
message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built")
else()
target_compile_definitions(squawk PRIVATE WITH_KCONFIG)
message("Building with support of KConfig")
message("Building with support of KConfigWidgets")
endif()
endif()
endif()
## Signal (TODO)
# find_package(Signal REQUIRED)
## LMDB
find_package(LMDB REQUIRED)
# Linking
target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
target_link_libraries(squawk PRIVATE lmdb)
target_link_libraries(squawk PRIVATE simpleCrypt)
# Link thread libraries on Linux
if(UNIX AND NOT APPLE)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(squawk PRIVATE Threads::Threads)
endif()
# Build type
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif ()
message("Build type: ${CMAKE_BUILD_TYPE}")
if(CMAKE_COMPILER_IS_GNUCXX)
set (COMPILE_OPTIONS -fno-sized-deallocation) # for eliminating _ZdlPvm
if (CMAKE_BUILD_TYPE STREQUAL "Release")
list(APPEND COMPILE_OPTIONS -O3)
endif()
if (CMAKE_BUILD_TYPE STREQUAL Debug)
list(APPEND COMPILE_OPTIONS -g)
list(APPEND COMPILE_OPTIONS -Wall)
list(APPEND COMPILE_OPTIONS -Wextra)
endif()
message("Compilation options: " ${COMPILE_OPTIONS})
target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
endif(CMAKE_COMPILER_IS_GNUCXX)
add_subdirectory(main)
add_subdirectory(core)
add_subdirectory(external/simpleCrypt)
add_subdirectory(packaging)
add_subdirectory(plugins)
add_subdirectory(resources)
add_subdirectory(shared)
add_subdirectory(translations)
add_subdirectory(ui)
# Install the executable
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/Macaw/Squawk/l10n)
install(FILES squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
install(FILES squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
install(FILES squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
install(FILES squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
install(FILES squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
install(FILES squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
if (CMAKE_BUILD_TYPE STREQUAL "Release")
if (APPLE)
add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..."
COMMAND "${Qt5Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
)
endif(APPLE)
endif()

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

View File

@ -1,31 +1,101 @@
# Sqwawk
# Squawk - a compact XMPP desktop messenger
A compact XMPP desktop messenger
[![AUR license](https://img.shields.io/aur/license/squawk?style=flat-square)](https://git.macaw.me/blue/squawk/raw/branch/master/LICENSE.md)
[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png)
### Prerequisites
- QT 5.12 *(lower versions might work but it wasn't tested)*
- uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_
- lmdb
- CMake 3.0 or higher
- CMake 3.4 or higher
- qxmpp 1.1.0 or higher
- KDE Frameworks: kwallet (optional)
- KDE Frameworks: KIO (optional)
- KDE Frameworks: KConfig (optional)
- KDE Frameworks: KConfigWidgets (optional)
- Boost (just one little hpp from there)
- Imagemagick (for compilation, to rasterize an SVG logo)
### Getting
The easiest way to get the Squawk is to install it from AUR (if you use Archlinux like distribution)
Here is the [link](https://aur.archlinux.org/packages/squawk/) for the AUR package
You can also install it from console if you use some AUR wrapper. Here what it's going to look like with *pacaur*
```
$ pacaur -S squawk
```
### Building
You can also clone the repo and build it from source
Squawk requires Qt with SSL enabled. It uses CMake as build system.
Squawk uses upstream version of QXmpp library so first we need to pull it
Please check the prerequisites and install them before installation.
#### For Windows (Mingw-w64) build
You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
The best way to acquire library `lmdb` and `boost` is through Msys2.
First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86_64-boost` by pacman.
Then you need to provide the cmake cache entry when calling cmake for configuration:
```
git submodule update --init --recursive
cmake .. -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
```
Then create a folder for the build, go there and build the project using CMake
`<Msys2 Mingw64 Root Directory>`: e.g. `C:/msys64/mingw64`.
---
There are two ways to build, it depends whether you have qxmpp installed in your system
#### Building with system qxmpp
Here is what you do
```
mkdir build
cd build
cmake ..
cmake --build .
$ git clone https://git.macaw.me/blue/squawk
$ cd squawk
$ mkdir build
$ cd build
$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
$ cmake --build .
```
#### Building with bundled qxmpp
Here is what you do
```
$ git clone --recurse-submodules https://git.macaw.me/blue/squawk
$ cd squawk
$ mkdir build
$ cd build
$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
$ cmake --build .
```
You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
### List of keys
Here is the list of keys you can pass to configuration phase of `cmake ..`.
- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
- `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
## License
This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details

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)

52
cmake/FindLMDB.cmake Normal file
View File

@ -0,0 +1,52 @@
#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license
#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code
# Try to find LMDB headers and library.
#
# Usage of this module as follows:
#
# find_package(LMDB)
#
# Variables used by this module, they can change the default behaviour and need
# to be set before calling find_package:
#
# LMDB_ROOT_DIR Set this variable to the root installation of
# LMDB if the module has problems finding the
# proper installation path.
#
# Variables defined by this module:
#
# LMDB_FOUND System has LMDB library/headers.
# LMDB_LIBRARIES The LMDB library.
# LMDB_INCLUDE_DIRS The location of LMDB headers.
find_path(LMDB_ROOT_DIR
NAMES include/lmdb.h
)
find_library(LMDB_LIBRARIES
NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible
HINTS ${LMDB_ROOT_DIR}/lib
)
add_library(lmdb UNKNOWN IMPORTED)
set_target_properties(lmdb PROPERTIES
IMPORTED_LOCATION ${LMDB_LIBRARIES}
)
find_path(LMDB_INCLUDE_DIRS
NAMES lmdb.h
HINTS ${LMDB_ROOT_DIR}/include
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LMDB DEFAULT_MSG
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
)
mark_as_advanced(
LMDB_ROOT_DIR
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
)

15
cmake/FindSignal.cmake Normal file
View File

@ -0,0 +1,15 @@
find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
find_library(Signal_LIBRARY signal-protocol-c)
mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
if (Signal_FOUND AND NOT TARGET Signal::Signal)
add_library(Signal::Signal UNKNOWN IMPORTED)
set_target_properties(Signal::Signal PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${Signal_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
)
endif ()

View File

@ -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,34 +1,29 @@
cmake_minimum_required(VERSION 3.0)
project(squawkCORE)
set(SIGNALCATCHER_SOURCE signalcatcher.cpp)
if(WIN32)
set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
endif(WIN32)
set(CMAKE_AUTOMOC ON)
target_sources(squawk PRIVATE
account.cpp
account.h
adapterfunctions.cpp
adapterfunctions.h
conference.cpp
conference.h
contact.cpp
contact.h
networkaccess.cpp
networkaccess.h
rosteritem.cpp
rosteritem.h
${SIGNALCATCHER_SOURCE}
signalcatcher.h
squawk.cpp
squawk.h
)
find_package(Qt5Widgets CONFIG REQUIRED)
find_package(Qt5Network CONFIG REQUIRED)
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
set(squawkCORE_SRC
squawk.cpp
account.cpp
archive.cpp
rosteritem.cpp
contact.cpp
conference.cpp
storage.cpp
networkaccess.cpp
)
# Tell CMake to create the helloworld executable
add_library(squawkCORE ${squawkCORE_SRC})
if(SYSTEM_QXMPP)
find_package(QXmpp CONFIG REQUIRED)
get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
endif()
# Use the Widgets module from Qt 5.
target_link_libraries(squawkCORE Qt5::Core)
target_link_libraries(squawkCORE Qt5::Network)
target_link_libraries(squawkCORE qxmpp)
target_link_libraries(squawkCORE lmdb)
add_subdirectory(handlers)
add_subdirectory(storage)
add_subdirectory(passwordStorageEngines)

File diff suppressed because it is too large Load Diff

View File

@ -19,20 +19,37 @@
#ifndef CORE_ACCOUNT_H
#define CORE_ACCOUNT_H
#include <QtCore/QObject>
#include <QObject>
#include <QCryptographicHash>
#include <QFile>
#include <QMimeDatabase>
#include <QStandardPaths>
#include <QDir>
#include <QTimer>
#include <map>
#include <set>
#include <QXmppRosterManager.h>
#include <QXmppCarbonManager.h>
#include <QXmppDiscoveryManager.h>
#include <QXmppMamManager.h>
#include <QXmppMucManager.h>
#include <QXmppClient.h>
#include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h>
#include "../global.h"
#include <QXmppUploadRequestManager.h>
#include <QXmppVCardManager.h>
#include <QXmppMessageReceiptManager.h>
#include "shared/shared.h"
#include "contact.h"
#include "conference.h"
#include "networkaccess.h"
#include "handlers/messagehandler.h"
#include "handlers/rosterhandler.h"
#include "handlers/vcardhandler.h"
namespace Core
{
@ -40,21 +57,39 @@ namespace Core
class Account : public QObject
{
Q_OBJECT
friend class MessageHandler;
friend class RosterHandler;
friend class VCardHandler;
public:
Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, QObject* parent = 0);
enum class Error {
authentication,
other,
none
};
Account(
const QString& p_login,
const QString& p_server,
const QString& p_password,
const QString& p_name,
bool p_active,
NetworkAccess* p_net,
QObject* parent = 0);
~Account();
void connect();
void disconnect();
void reconnect();
Shared::ConnectionState getState() const;
QString getName() const;
QString getLogin() const;
QString getServer() const;
QString getPassword() const;
QString getResource() const;
QString getAvatarPath() const;
QString getBareJid() const;
QString getFullJid() const;
Shared::Availability getAvailability() const;
Shared::AccountPassword getPasswordType() const;
Error getLastError() const;
bool getActive() const;
void setName(const QString& p_name);
void setLogin(const QString& p_login);
@ -62,10 +97,10 @@ public:
void setPassword(const QString& p_password);
void setResource(const QString& p_resource);
void setAvailability(Shared::Availability avail);
QString getFullJid() const;
void setPasswordType(Shared::AccountPassword pt);
void sendMessage(const Shared::Message& data);
void setActive(bool p_active);
void requestArchive(const QString& jid, int count, const QString& before);
void setReconnectTimes(unsigned int times);
void subscribeToContact(const QString& jid, const QString& reason);
void unsubscribeFromContact(const QString& jid, const QString& reason);
void removeContactRequest(const QString& jid);
@ -73,15 +108,27 @@ public:
void addContactToGroupRequest(const QString& jid, const QString& groupName);
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
void renameContactRequest(const QString& jid, const QString& newName);
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
void setRoomJoined(const QString& jid, bool joined);
void setRoomAutoJoin(const QString& jid, bool joined);
void removeRoomRequest(const QString& jid);
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void uploadVCard(const Shared::VCard& card);
void resendMessage(const QString& jid, const QString& id);
void replaceMessage(const QString& originalId, const Shared::Message& data);
void invalidatePassword();
public slots:
void connect();
void disconnect();
void reconnect();
void requestVCard(const QString& jid);
signals:
void connectionStateChanged(int);
void availabilityChanged(int);
void changed(const QMap<QString, QVariant>& data);
void connectionStateChanged(Shared::ConnectionState);
void availabilityChanged(Shared::Availability);
void addGroup(const QString& name);
void removeGroup(const QString& name);
void addRoom(const QString& jid, const QMap<QString, QVariant>& data);
@ -94,90 +141,67 @@ signals:
void addPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removePresence(const QString& jid, const QString& name);
void message(const Shared::Message& data);
void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
void error(const QString& text);
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& jid, const QString& nickName);
void receivedVCard(const QString& jid, const Shared::VCard& card);
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
void needPassword();
private:
QString name;
std::map<QString, QString> achiveQueries;
std::map<QString, QString> archiveQueries;
QXmppClient client;
QXmppConfiguration config;
QXmppPresence presence;
Shared::ConnectionState state;
std::map<QString, std::set<QString>> groups;
QXmppCarbonManager* cm;
QXmppMamManager* am;
QXmppMucManager* mm;
QXmppBookmarkManager* bm;
std::map<QString, Contact*> contacts;
std::map<QString, Conference*> conferences;
unsigned int maxReconnectTimes;
unsigned int reconnectTimes;
QXmppRosterManager* rm;
QXmppVCardManager* vm;
QXmppUploadRequestManager* um;
QXmppDiscoveryManager* dm;
QXmppMessageReceiptManager* rcpm;
bool reconnectScheduled;
QTimer* reconnectTimer;
std::map<QString, QString> queuedContacts;
std::set<QString> outOfRosterContacts;
NetworkAccess* network;
Shared::AccountPassword passwordType;
Error lastError;
bool pepSupport;
bool active;
bool notReadyPassword;
MessageHandler* mh;
RosterHandler* rh;
VCardHandler* vh;
private slots:
void onClientConnected();
void onClientDisconnected();
void onClientStateChange(QXmppClient::State state);
void onClientError(QXmppClient::Error err);
void onRosterReceived();
void onRosterItemAdded(const QString& bareJid);
void onRosterItemChanged(const QString& bareJid);
void onRosterItemRemoved(const QString& bareJid);
void onRosterPresenceChanged(const QString& bareJid, const QString& resource);
void onPresenceReceived(const QXmppPresence& presence);
void onMessageReceived(const QXmppMessage& message);
void onCarbonMessageReceived(const QXmppMessage& message);
void onCarbonMessageSent(const QXmppMessage& message);
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
void onMamMessageReceived(const QString& bareJid, const QXmppMessage& message);
void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
void onMucRoomAdded(QXmppMucRoom* room);
void onMucJoinedChanged(bool joined);
void onMucAutoJoinChanged(bool autoJoin);
void onMucNickNameChanged(const QString& nickName);
void onMucSubjectChanged(const QString& subject);
void onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
void onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
void onMucRemoveParticipant(const QString& nickName);
void bookmarksReceived(const QXmppBookmarkSet& bookmarks);
void onContactGroupAdded(const QString& group);
void onContactGroupRemoved(const QString& group);
void onContactNameChanged(const QString& name);
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
void onContactHistoryResponse(const std::list<Shared::Message>& list);
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
private:
void addedAccount(const QString &bareJid);
void handleNewContact(Contact* contact);
void handleNewRosterItem(RosterItem* contact);
void handleNewConference(Conference* contact);
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
void addToGroup(const QString& jid, const QString& group);
void removeFromGroup(const QString& jid, const QString& group);
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const;
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
void storeConferences();
void clearConferences();
void handleDisconnection();
void onReconnectTimer();
};
}

271
core/adapterfunctions.cpp Normal file
View File

@ -0,0 +1,271 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "adapterfunctions.h"
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
{
vCard.setFullName(card.fullName());
vCard.setFirstName(card.firstName());
vCard.setMiddleName(card.middleName());
vCard.setLastName(card.lastName());
vCard.setBirthday(card.birthday());
vCard.setNickName(card.nickName());
vCard.setDescription(card.description());
vCard.setUrl(card.url());
QXmppVCardOrganization org = card.organization();
vCard.setOrgName(org.organization());
vCard.setOrgRole(org.role());
vCard.setOrgUnit(org.unit());
vCard.setOrgTitle(org.title());
QList<QXmppVCardEmail> emails = card.emails();
std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
for (const QXmppVCardEmail& em : emails) {
QString addr = em.address();
if (addr.size() != 0) {
QXmppVCardEmail::Type et = em.type();
bool prefered = false;
bool accounted = false;
if (et & QXmppVCardEmail::Preferred) {
prefered = true;
}
if (et & QXmppVCardEmail::Home) {
myEmails.emplace_back(addr, Shared::VCard::Email::home, prefered);
accounted = true;
}
if (et & QXmppVCardEmail::Work) {
myEmails.emplace_back(addr, Shared::VCard::Email::work, prefered);
accounted = true;
}
if (!accounted) {
myEmails.emplace_back(addr, Shared::VCard::Email::none, prefered);
}
}
}
QList<QXmppVCardPhone> phones = card.phones();
std::deque<Shared::VCard::Phone>& myPhones = vCard.getPhones();
for (const QXmppVCardPhone& ph : phones) {
QString num = ph.number();
if (num.size() != 0) {
QXmppVCardPhone::Type pt = ph.type();
bool prefered = false;
bool accounted = false;
if (pt & QXmppVCardPhone::Preferred) {
prefered = true;
}
bool home = false;
bool work = false;
if (pt & QXmppVCardPhone::Home) {
home = true;
}
if (pt & QXmppVCardPhone::Work) {
work = true;
}
if (pt & QXmppVCardPhone::Fax) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Voice) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Pager) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Cell) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Video) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (pt & QXmppVCardPhone::Modem) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered);
}
accounted = true;
}
if (!accounted) {
if (home || work) {
if (home) {
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered);
}
if (work) {
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered);
}
} else {
myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered);
}
}
}
}
}
void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
iq.setFullName(card.getFullName());
iq.setFirstName(card.getFirstName());
iq.setMiddleName(card.getMiddleName());
iq.setLastName(card.getLastName());
iq.setNickName(card.getNickName());
iq.setBirthday(card.getBirthday());
iq.setDescription(card.getDescription());
iq.setUrl(card.getUrl());
QXmppVCardOrganization org;
org.setOrganization(card.getOrgName());
org.setUnit(card.getOrgUnit());
org.setRole(card.getOrgRole());
org.setTitle(card.getOrgTitle());
iq.setOrganization(org);
const std::deque<Shared::VCard::Email>& myEmails = card.getEmails();
QList<QXmppVCardEmail> emails;
for (const Shared::VCard::Email& mEm : myEmails) {
QXmppVCardEmail em;
QXmppVCardEmail::Type t = QXmppVCardEmail::Internet;
if (mEm.prefered) {
t = t | QXmppVCardEmail::Preferred;
}
if (mEm.role == Shared::VCard::Email::home) {
t = t | QXmppVCardEmail::Home;
} else if (mEm.role == Shared::VCard::Email::work) {
t = t | QXmppVCardEmail::Work;
}
em.setType(t);
em.setAddress(mEm.address);
emails.push_back(em);
}
std::map<QString, QXmppVCardPhone> phones;
QList<QXmppVCardPhone> phs;
const std::deque<Shared::VCard::Phone>& myPhones = card.getPhones();
for (const Shared::VCard::Phone& mPh : myPhones) {
std::map<QString, QXmppVCardPhone>::iterator itr = phones.find(mPh.number);
if (itr == phones.end()) {
itr = phones.emplace(mPh.number, QXmppVCardPhone()).first;
}
QXmppVCardPhone& phone = itr->second;
phone.setNumber(mPh.number);
switch (mPh.type) {
case Shared::VCard::Phone::fax:
phone.setType(phone.type() | QXmppVCardPhone::Fax);
break;
case Shared::VCard::Phone::pager:
phone.setType(phone.type() | QXmppVCardPhone::Pager);
break;
case Shared::VCard::Phone::voice:
phone.setType(phone.type() | QXmppVCardPhone::Voice);
break;
case Shared::VCard::Phone::cell:
phone.setType(phone.type() | QXmppVCardPhone::Cell);
break;
case Shared::VCard::Phone::video:
phone.setType(phone.type() | QXmppVCardPhone::Video);
break;
case Shared::VCard::Phone::modem:
phone.setType(phone.type() | QXmppVCardPhone::Modem);
break;
case Shared::VCard::Phone::other:
phone.setType(phone.type() | QXmppVCardPhone::PCS); //loss of information, but I don't even know what the heck is this type of phone!
break;
}
switch (mPh.role) {
case Shared::VCard::Phone::home:
phone.setType(phone.type() | QXmppVCardPhone::Home);
break;
case Shared::VCard::Phone::work:
phone.setType(phone.type() | QXmppVCardPhone::Work);
break;
default:
break;
}
if (mPh.prefered) {
phone.setType(phone.type() | QXmppVCardPhone::Preferred);
}
}
for (const std::pair<const QString, QXmppVCardPhone>& phone : phones) {
phs.push_back(phone.second);
}
iq.setEmails(emails);
iq.setPhones(phs);
}

32
core/adapterfunctions.h Normal file
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/>.
*/
#ifndef CORE_ADAPTER_FUNCTIONS_H
#define CORE_ADAPTER_FUNCTIONS_H
#include <QXmppVCardIq.h>
#include <shared/vcard.h>
namespace Core {
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
}
#endif // CORE_ADAPTER_FUNCTIONS_H

View File

@ -1,510 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "archive.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <QStandardPaths>
#include <QDebug>
#include <QDataStream>
#include <QDir>
Core::Archive::Archive(const QString& p_jid, QObject* parent):
QObject(parent),
jid(p_jid),
opened(false),
fromTheBeginning(false),
environment(),
main(),
order(),
stats()
{
}
Core::Archive::~Archive()
{
close();
}
void Core::Archive::open(const QString& account)
{
if (!opened) {
mdb_env_create(&environment);
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + account + "/" + jid;
QDir cache(path);
if (!cache.exists()) {
bool res = cache.mkpath(path);
if (!res) {
throw Directory(path.toStdString());
}
}
mdb_env_set_maxdbs(environment, 4);
mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL);
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
mdb_dbi_open(txn, "main", MDB_CREATE, &main);
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
mdb_txn_commit(txn);
fromTheBeginning = _isFromTheBeginning();
opened = true;
}
}
void Core::Archive::close()
{
if (opened) {
mdb_dbi_close(environment, stats);
mdb_dbi_close(environment, order);
mdb_dbi_close(environment, main);
mdb_env_close(environment);
opened = false;
}
}
bool Core::Archive::addElement(const Shared::Message& message)
{
if (!opened) {
throw Closed("addElement", jid.toStdString());
}
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
message.serialize(ds);
quint64 stamp = message.getTime().toMSecsSinceEpoch();
const std::string& id = message.getId().toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc == 0) {
MDB_val orderKey;
orderKey.mv_size = 8;
orderKey.mv_data = (uint8_t*) &stamp;
rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
if (rc) {
qDebug() << "An element couldn't be inserted into the index" << mdb_strerror(rc);
mdb_txn_abort(txn);
return false;
} else {
rc = mdb_txn_commit(txn);
if (rc) {
qDebug() << "A transaction error: " << mdb_strerror(rc);
return false;
}
return true;
}
} else {
qDebug() << "An element couldn't been added to the archive, skipping" << mdb_strerror(rc);
mdb_txn_abort(txn);
return false;
}
}
void Core::Archive::clear()
{
if (!opened) {
throw Closed("clear", jid.toStdString());
}
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
mdb_drop(txn, main, 0);
mdb_drop(txn, order, 0);
mdb_drop(txn, stats, 0);
mdb_txn_commit(txn);
}
Shared::Message Core::Archive::getElement(const QString& id)
{
if (!opened) {
throw Closed("getElement", jid.toStdString());
}
std::string strKey = id.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = strKey.size();
lmdbKey.mv_data = (char*)strKey.c_str();
MDB_txn *txn;
int rc;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
if (rc) {
qDebug() <<"Get error: " << mdb_strerror(rc);
mdb_txn_abort(txn);
throw NotFound(id.toStdString(), jid.toStdString());
} else {
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
QDataStream ds(&ba, QIODevice::ReadOnly);
Shared::Message msg;
msg.deserialize(ds);
mdb_txn_abort(txn);
return msg;
}
}
Shared::Message Core::Archive::newest()
{
QString id = newestId();
return getElement(id);
}
QString Core::Archive::newestId()
{
if (!opened) {
throw Closed("newestId", jid.toStdString());
}
MDB_txn *txn;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_cursor* cursor;
rc = mdb_cursor_open(txn, order, &cursor);
MDB_val lmdbKey, lmdbData;
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
if (rc) {
qDebug() << "Error geting newestId " << mdb_strerror(rc);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw Empty(jid.toStdString());
} else {
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
return sId.c_str();
}
}
QString Core::Archive::oldestId()
{
if (!opened) {
throw Closed("oldestId", jid.toStdString());
}
MDB_txn *txn;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_cursor* cursor;
rc = mdb_cursor_open(txn, order, &cursor);
MDB_val lmdbKey, lmdbData;
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
if (rc) {
qDebug() << "Error geting oldestId " << mdb_strerror(rc);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw Empty(jid.toStdString());
} else {
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
return sId.c_str();
}
}
Shared::Message Core::Archive::oldest()
{
return getElement(oldestId());
}
unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages)
{
if (!opened) {
throw Closed("addElements", jid.toStdString());
}
int success = 0;
int rc = 0;
MDB_val lmdbKey, lmdbData;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
std::list<Shared::Message>::const_iterator itr = messages.begin();
while (rc == 0 && itr != messages.end()) {
const Shared::Message& message = *itr;
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
message.serialize(ds);
quint64 stamp = message.getTime().toMSecsSinceEpoch();
const std::string& id = message.getId().toStdString();
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
if (rc == 0) {
MDB_val orderKey;
orderKey.mv_size = 8;
orderKey.mv_data = (uint8_t*) &stamp;
rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
if (rc) {
qDebug() << "An element couldn't be inserted into the index, aborting the transaction" << mdb_strerror(rc);
} else {
//qDebug() << "element added with id" << message.getId() << "stamp" << message.getTime();
success++;
}
} else {
if (rc == MDB_KEYEXIST) {
rc = 0;
} else {
qDebug() << "An element couldn't been added to the archive, aborting the transaction" << mdb_strerror(rc);
}
}
itr++;
}
if (rc != 0) {
mdb_txn_abort(txn);
success = 0;
} else {
mdb_txn_commit(txn);
}
return success;
}
long unsigned int Core::Archive::size() const
{
if (!opened) {
throw Closed("size", jid.toStdString());
}
MDB_txn *txn;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_stat stat;
mdb_stat(txn, order, &stat);
mdb_txn_abort(txn);
return stat.ms_entries;
}
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
{
if (!opened) {
throw Closed("getBefore", jid.toStdString());
}
std::list<Shared::Message> res;
MDB_cursor* cursor;
MDB_txn *txn;
MDB_val lmdbKey, lmdbData;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
if (id == "") {
rc = mdb_cursor_open(txn, order, &cursor);
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
if (rc) {
qDebug() << "Error getting before" << mdb_strerror(rc) << ", id:" << id;
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw Empty(jid.toStdString());
}
} else {
std::string stdId(id.toStdString());
lmdbKey.mv_size = stdId.size();
lmdbKey.mv_data = (char*)stdId.c_str();
rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
if (rc) {
qDebug() <<"Error getting before: no reference message" << mdb_strerror(rc) << ", id:" << id;
mdb_txn_abort(txn);
throw NotFound(stdId, jid.toStdString());
} else {
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
QDataStream ds(&ba, QIODevice::ReadOnly);
Shared::Message msg;
msg.deserialize(ds);
quint64 stamp = msg.getTime().toMSecsSinceEpoch();
lmdbKey.mv_data = (quint8*)&stamp;
lmdbKey.mv_size = 8;
rc = mdb_cursor_open(txn, order, &cursor);
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET);
if (rc) {
qDebug() << "Error getting before: couldn't set " << mdb_strerror(rc);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw NotFound(stdId, jid.toStdString());
} else {
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV);
if (rc) {
qDebug() << "Error getting before, couldn't prev " << mdb_strerror(rc);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
throw NotFound(stdId, jid.toStdString());
}
}
}
}
do {
MDB_val dKey, dData;
dKey.mv_size = lmdbData.mv_size;
dKey.mv_data = lmdbData.mv_data;
rc = mdb_get(txn, main, &dKey, &dData);
if (rc) {
qDebug() <<"Get error: " << mdb_strerror(rc);
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
mdb_txn_abort(txn);
throw NotFound(sId, jid.toStdString());
} else {
QByteArray ba((char*)dData.mv_data, dData.mv_size);
QDataStream ds(&ba, QIODevice::ReadOnly);
res.emplace_back();
Shared::Message& msg = res.back();
msg.deserialize(ds);
}
--count;
} while (count > 0 && mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV) == 0);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
return res;
}
bool Core::Archive::_isFromTheBeginning()
{
std::string strKey = "beginning";
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = strKey.size();
lmdbKey.mv_data = (char*)strKey.c_str();
MDB_txn *txn;
int rc;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
if (rc == MDB_NOTFOUND) {
mdb_txn_abort(txn);
return false;
} else if (rc) {
qDebug() <<"isFromTheBeginning error: " << mdb_strerror(rc);
mdb_txn_abort(txn);
throw NotFound(strKey, jid.toStdString());
} else {
uint8_t value = *(uint8_t*)(lmdbData.mv_data);
bool is;
if (value == 144) {
is = false;
} else if (value == 72) {
is = true;
} else {
qDebug() <<"isFromTheBeginning error: stored value doesn't match any magic number, the answer is most probably wrong";
}
mdb_txn_abort(txn);
return is;
}
}
bool Core::Archive::isFromTheBeginning()
{
if (!opened) {
throw Closed("isFromTheBeginning", jid.toStdString());
}
return fromTheBeginning;
}
void Core::Archive::setFromTheBeginning(bool is)
{
if (!opened) {
throw Closed("setFromTheBeginning", jid.toStdString());
}
if (fromTheBeginning != is) {
fromTheBeginning = is;
const std::string& id = "beginning";
uint8_t value = 144;
if (is) {
value = 72;
}
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = sizeof value;
lmdbData.mv_data = &value;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
qDebug() << "Couldn't store beginning key into stat database:" << mdb_strerror(rc);
mdb_txn_abort(txn);
}
mdb_txn_commit(txn);
}
}
void Core::Archive::printOrder()
{
qDebug() << "Printing order";
MDB_txn *txn;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_cursor* cursor;
rc = mdb_cursor_open(txn, order, &cursor);
MDB_val lmdbKey, lmdbData;
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
do {
std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
qDebug() << QString(sId.c_str());
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
}
void Core::Archive::printKeys()
{
MDB_txn *txn;
int rc;
rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
MDB_cursor* cursor;
rc = mdb_cursor_open(txn, main, &cursor);
MDB_val lmdbKey, lmdbData;
rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
do {
std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
qDebug() << QString(sId.c_str());
} while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
}

View File

@ -25,25 +25,28 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
nick(p_nick),
room(p_room),
joined(false),
autoJoin(p_autoJoin)
autoJoin(p_autoJoin),
exParticipants()
{
muc = true;
name = p_name;
connect(room, SIGNAL(joined()), this, SLOT(onRoomJoined()));
connect(room, SIGNAL(left()), this, SLOT(onRoomLeft()));
connect(room, SIGNAL(nameChanged(const QString&)), this, SLOT(onRoomNameChanged(const QString&)));
connect(room, SIGNAL(subjectChanged(const QString&)), this, SLOT(onRoomSubjectChanged(const QString&)));
connect(room, SIGNAL(participantAdded(const QString&)), this, SLOT(onRoomParticipantAdded(const QString&)));
connect(room, SIGNAL(participantChanged(const QString&)), this, SLOT(onRoomParticipantChanged(const QString&)));
connect(room, SIGNAL(participantRemoved(const QString&)), this, SLOT(onRoomParticipantRemoved(const QString&)));
connect(room, SIGNAL(nickNameChanged(const QString&)), this, SLOT(onRoomNickNameChanged(const QString&)));
connect(room, SIGNAL(error(const QXmppStanza::Error&)), this, SLOT(onRoomError(const QXmppStanza::Error&)));
connect(room, &QXmppMucRoom::joined, this, &Conference::onRoomJoined);
connect(room, &QXmppMucRoom::left, this, &Conference::onRoomLeft);
connect(room, &QXmppMucRoom::nameChanged, this, &Conference::onRoomNameChanged);
connect(room, &QXmppMucRoom::subjectChanged, this, &Conference::onRoomSubjectChanged);
connect(room, &QXmppMucRoom::participantAdded, this, &Conference::onRoomParticipantAdded);
connect(room, &QXmppMucRoom::participantChanged, this, &Conference::onRoomParticipantChanged);
connect(room, &QXmppMucRoom::participantRemoved, this, &Conference::onRoomParticipantRemoved);
connect(room, &QXmppMucRoom::nickNameChanged, this, &Conference::onRoomNickNameChanged);
connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError);
room->setNickName(nick);
if (autoJoin) {
room->join();
}
archive->readAllResourcesAvatars(exParticipants);
}
Core::Conference::~Conference()
@ -134,23 +137,66 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
{
QStringList comps = p_name.split("/");
QString resource = comps.back();
QXmppPresence pres = room->participantPresence(p_name);
QXmppMucItem mi = pres.mucItem();
if (resource == jid) {
qDebug() << "Room" << jid << "is reporting of adding itself to the list participants. Not sure what to do with that yet, skipping";
} else {
QXmppPresence pres = room->participantPresence(p_name);
resource = "";
}
std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource);
bool hasAvatar = itr != exParticipants.end();
if (resource.size() > 0) {
QDateTime lastInteraction = pres.lastUserInteraction();
if (!lastInteraction.isValid()) {
lastInteraction = QDateTime::currentDateTime();
lastInteraction = QDateTime::currentDateTimeUtc();
}
QXmppMucItem mi = pres.mucItem();
emit addParticipant(resource, {
QMap<QString, QVariant> cData = {
{"lastActivity", lastInteraction},
{"availability", pres.availableStatusType()},
{"status", pres.statusText()},
{"affiliation", mi.affiliation()},
{"role", mi.role()}
});
};
if (hasAvatar) {
if (itr->second.autogenerated) {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
}
cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type);
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
cData.insert("avatarPath", "");
requestVCard(p_name);
}
emit addParticipant(resource, cData);
}
switch (pres.vCardUpdateType()) {
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
break;
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
break;
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
if (!hasAvatar || !itr->second.autogenerated) {
setAutoGeneratedAvatar(resource);
}
}
break;
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
if (hasAvatar) {
if (itr->second.autogenerated || itr->second.hash != pres.photoHash()) {
emit requestVCard(p_name);
}
} else {
emit requestVCard(p_name);
}
break;
}
}
}
@ -158,15 +204,14 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
{
QStringList comps = p_name.split("/");
QString resource = comps.back();
if (resource == jid) {
qDebug() << "Room" << jid << "is reporting of changing his own presence. Not sure what to do with that yet, skipping";
} else {
QXmppPresence pres = room->participantPresence(p_name);
QXmppPresence pres = room->participantPresence(p_name);
QXmppMucItem mi = pres.mucItem();
handlePresence(pres);
if (resource != jid) {
QDateTime lastInteraction = pres.lastUserInteraction();
if (!lastInteraction.isValid()) {
lastInteraction = QDateTime::currentDateTime();
lastInteraction = QDateTime::currentDateTimeUtc();
}
QXmppMucItem mi = pres.mucItem();
emit changeParticipant(resource, {
{"lastActivity", lastInteraction},
@ -202,3 +247,117 @@ void Core::Conference::onRoomSubjectChanged(const QString& p_name)
{
emit subjectChanged(p_name);
}
void Core::Conference::handlePresence(const QXmppPresence& pres)
{
QString id = pres.from();
QStringList comps = id.split("/");
QString jid = comps.front();
QString resource("");
if (comps.size() > 1) {
resource = comps.back();
}
switch (pres.vCardUpdateType()) {
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
break;
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
break;
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info, resource);
if (!hasAvatar || !info.autogenerated) {
setAutoGeneratedAvatar(resource);
}
}
break;
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info, resource);
if (hasAvatar) {
if (info.autogenerated || info.hash != pres.photoHash()) {
emit requestVCard(id);
}
} else {
emit requestVCard(id);
}
break;
}
}
}
bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
{
Archive::AvatarInfo newInfo;
bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource);
if (result && resource.size() != 0) {
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
if (itr == exParticipants.end()) {
exParticipants.insert(std::make_pair(resource, newInfo));
} else {
itr->second = newInfo;
}
emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
{"avatarPath", avatarPath(resource) + "." + newInfo.type}
});
}
return result;
}
bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
{
bool result = RosterItem::setAvatar(data, info, resource);
if (result && resource.size() != 0) {
if (data.size() > 0) {
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
if (itr == exParticipants.end()) {
exParticipants.insert(std::make_pair(resource, info));
} else {
itr->second = info;
}
emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
{"avatarPath", avatarPath(resource) + "." + info.type}
});
} else {
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
if (itr != exParticipants.end()) {
exParticipants.erase(itr);
}
emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(Shared::Avatar::empty)},
{"avatarPath", ""}
});
}
}
return result;
}
Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource)
{
Shared::VCard result = RosterItem::handleResponseVCard(card, resource);
if (resource.size() > 0) {
emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(result.getAvatarType())},
{"avatarPath", result.getAvatarPath()}
});
}
return result;
}
QMap<QString, QVariant> Core::Conference::getAllAvatars() const
{
QMap<QString, QVariant> result;
for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants) {
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
}
return result;
}

View File

@ -19,9 +19,15 @@
#ifndef CORE_CONFERENCE_H
#define CORE_CONFERENCE_H
#include "rosteritem.h"
#include <QDir>
#include <QXmppMucManager.h>
#include <set>
#include "rosteritem.h"
#include "shared/global.h"
namespace Core
{
@ -44,6 +50,10 @@ public:
bool getAutoJoin();
void setAutoJoin(bool p_autoJoin);
void handlePresence(const QXmppPresence & pres) override;
bool setAutoGeneratedAvatar(const QString& resource = "") override;
Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override;
QMap<QString, QVariant> getAllAvatars() const;
signals:
void nickChanged(const QString& nick);
@ -54,11 +64,16 @@ signals:
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
void removeParticipant(const QString& name);
protected:
bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override;
private:
QString nick;
QXmppMucRoom* room;
bool joined;
bool autoJoin;
std::map<QString, Archive::AvatarInfo> exParticipants;
static const std::set<QString> supportedList;
private slots:
void onRoomJoined();

View File

@ -22,7 +22,7 @@
Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent):
RosterItem(pJid, account, parent),
groups(),
subscriptionState(Shared::unknown)
subscriptionState(Shared::SubscriptionState::unknown)
{
}
@ -68,3 +68,33 @@ void Core::Contact::setSubscriptionState(Shared::SubscriptionState state)
emit subscriptionStateChanged(subscriptionState);
}
}
void Core::Contact::handlePresence(const QXmppPresence& pres)
{
switch (pres.vCardUpdateType()) {
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
break;
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
break;
case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info);
if (!hasAvatar || !info.autogenerated) {
setAutoGeneratedAvatar();
}
}
break;
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
Archive::AvatarInfo info;
bool hasAvatar = readAvatarInfo(info);
if (hasAvatar) {
if (info.autogenerated || info.hash != pres.photoHash()) {
emit requestVCard(jid);
}
} else {
emit requestVCard(jid);
}
break;
}
}
}

View File

@ -38,6 +38,7 @@ public:
void setSubscriptionState(Shared::SubscriptionState state);
Shared::SubscriptionState getSubscriptionState() const;
void handlePresence(const QXmppPresence & pres) override;
signals:
void groupAdded(const QString& name);

View File

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

View File

@ -0,0 +1,595 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "messagehandler.h"
#include "core/account.h"
Core::MessageHandler::MessageHandler(Core::Account* account):
QObject(),
acc(account),
pendingStateMessages(),
uploadingSlotsQueue()
{
}
void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
{
bool handled = false;
switch (msg.type()) {
case QXmppMessage::Normal:
qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping";
break;
case QXmppMessage::Chat:
handled = handleChatMessage(msg);
break;
case QXmppMessage::GroupChat:
handled = handleGroupMessage(msg);
break;
case QXmppMessage::Error: {
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
if (std::get<0>(ids)) {
QString id = std::get<1>(ids);
QString jid = std::get<2>(ids);
RosterItem* cnt = acc->rh->getRosterItem(jid);
QMap<QString, QVariant> cData = {
{"state", static_cast<uint>(Shared::Message::State::error)},
{"errorText", msg.error().text()}
};
if (cnt != 0) {
cnt->changeMessage(id, cData);
}
emit acc->changeMessage(jid, id, cData);
handled = true;
} else {
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
}
}
break;
case QXmppMessage::Headline:
qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
break;
}
if (!handled) {
logMessage(msg);
}
}
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
{
if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
Shared::Message sMsg(Shared::Message::chat);
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
QString jid = sMsg.getPenPalJid();
Contact* cnt = acc->rh->getContact(jid);
if (cnt == 0) {
cnt = acc->rh->addOutOfRosterContact(jid);
qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
}
if (outgoing) {
if (forwarded) {
sMsg.setState(Shared::Message::State::sent);
}
} else {
sMsg.setState(Shared::Message::State::delivered);
}
QString oId = msg.replaceId();
if (oId.size() > 0) {
QMap<QString, QVariant> cData = {
{"body", sMsg.getBody()},
{"stamp", sMsg.getTime()}
};
cnt->correctMessageInArchive(oId, sMsg);
emit acc->changeMessage(jid, oId, cData);
} else {
cnt->appendMessageToArchive(sMsg);
emit acc->message(sMsg);
}
return true;
}
return false;
}
bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
{
const QString& body(msg.body());
if (body.size() != 0) {
Shared::Message sMsg(Shared::Message::groupChat);
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
QString jid = sMsg.getPenPalJid();
Conference* cnt = acc->rh->getConference(jid);
if (cnt == 0) {
return false;
}
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
if (std::get<0>(ids)) {
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
cnt->changeMessage(std::get<1>(ids), cData);
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
} else {
QString oId = msg.replaceId();
if (oId.size() > 0) {
QMap<QString, QVariant> cData = {
{"body", sMsg.getBody()},
{"stamp", sMsg.getTime()}
};
cnt->correctMessageInArchive(oId, sMsg);
emit acc->changeMessage(jid, oId, cData);
} else {
cnt->appendMessageToArchive(sMsg);
QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
if (sMsg.getTime() > minAgo) { //otherwise it's considered a delayed delivery, most probably MUC history receipt
emit acc->message(sMsg);
} else {
//qDebug() << "Delayed delivery: ";
}
}
}
return true;
}
return false;
}
void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
{
const QDateTime& time(source.stamp());
QString id;
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
id = source.originId();
if (id.size() == 0) {
id = source.id();
}
target.setStanzaId(source.stanzaId());
qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
#else
id = source.id();
#endif
target.setId(id);
QString messageId = target.getId();
if (messageId.size() == 0) {
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
messageId = target.getId();
qDebug() << "Had do initialize a message with no id, assigning autogenerated" << messageId;
}
target.setFrom(source.from());
target.setTo(source.to());
target.setBody(source.body());
target.setForwarded(forwarded);
if (guessing) {
if (target.getFromJid() == acc->getBareJid()) {
outgoing = true;
} else {
outgoing = false;
}
}
target.setOutgoing(outgoing);
if (time.isValid()) {
target.setTime(time);
} else {
target.setCurrentTime();
}
QString oob = source.outOfBandUrl();
if (oob.size() > 0) {
target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
}
target.setOutOfBandUrl(oob);
}
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
{
qDebug() << reason;
qDebug() << "- from: " << msg.from();
qDebug() << "- to: " << msg.to();
qDebug() << "- body: " << msg.body();
qDebug() << "- type: " << msg.type();
qDebug() << "- state: " << msg.state();
qDebug() << "- stamp: " << msg.stamp();
qDebug() << "- id: " << msg.id();
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
qDebug() << "- stanzaId: " << msg.stanzaId();
#endif
qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl();
qDebug() << "==============================";
}
void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg)
{
handleChatMessage(msg, false, true);
}
void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
{
handleChatMessage(msg, true, true);
}
std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id)
{
std::tuple<bool, QString, QString> result({false, "", ""});
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
if (itr != pendingStateMessages.end()) {
std::get<0>(result) = true;
std::get<2>(result) = itr->second;
std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
if (itrC != pendingCorrectionMessages.end()) {
if (itrC->second.size() > 0) {
std::get<1>(result) = itrC->second;
} else {
std::get<1>(result) = itr->first;
}
pendingCorrectionMessages.erase(itrC);
} else {
std::get<1>(result) = itr->first;
}
pendingStateMessages.erase(itr);
}
return result;
}
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
{
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
if (std::get<0>(ids)) {
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
if (ri != 0) {
ri->changeMessage(std::get<1>(ids), cData);
}
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
}
}
void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId)
{
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId));
prepareUpload(data, newMessage);
} else {
performSending(data, originalId, newMessage);
}
}
void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage)
{
QString jid = data.getPenPalJid();
QString id = data.getId();
qDebug() << "Sending message with id:" << id;
if (originalId.size() > 0) {
qDebug() << "To replace one with id:" << originalId;
}
RosterItem* ri = acc->rh->getRosterItem(jid);
bool sent = false;
if (newMessage && originalId.size() > 0) {
newMessage = false;
}
QDateTime sendTime = QDateTime::currentDateTimeUtc();
if (acc->state == Shared::ConnectionState::connected) {
QXmppMessage msg(createPacket(data, sendTime, originalId));
sent = acc->client.sendPacket(msg);
if (sent) {
data.setState(Shared::Message::State::sent);
} else {
data.setState(Shared::Message::State::error);
data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
}
} else {
data.setState(Shared::Message::State::error);
data.setErrorText("You are is offline or reconnecting");
}
QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
QString realId;
if (originalId.size() > 0) {
realId = originalId;
} else {
realId = id;
}
if (ri != 0) {
if (newMessage) {
ri->appendMessageToArchive(data);
} else {
ri->changeMessage(realId, changes);
}
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
if (originalId.size() > 0) {
pendingCorrectionMessages.insert(std::make_pair(id, originalId));
}
} else {
pendingStateMessages.erase(id);
pendingCorrectionMessages.erase(id);
}
}
emit acc->changeMessage(jid, realId, changes);
}
QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const
{
QMap<QString, QVariant> changes;
QString oob = data.getOutOfBandUrl();
Shared::Message::State mstate = data.getState();
changes.insert("state", static_cast<uint>(mstate));
if (mstate == Shared::Message::State::error) {
changes.insert("errorText", data.getErrorText());
}
if (oob.size() > 0) {
changes.insert("outOfBandUrl", oob);
}
if (newMessage) {
data.setTime(time);
}
if (originalId.size() > 0) {
changes.insert("body", data.getBody());
}
changes.insert("stamp", time);
//sometimes (when the image is pasted with ctrl+v)
//I start sending message with one path, then copy it to downloads directory
//so, the final path changes. Let's assume it changes always since it costs me close to nothing
QString attachPath = data.getAttachPath();
if (attachPath.size() > 0) {
QString squawkified = Shared::squawkifyPath(attachPath);
changes.insert("attachPath", squawkified);
if (attachPath != squawkified) {
data.setAttachPath(squawkified);
}
}
return changes;
}
QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const
{
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
QString id(data.getId());
if (originalId.size() > 0) {
msg.setReplaceId(originalId);
}
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
msg.setOriginId(id);
#endif
msg.setId(id);
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
msg.setOutOfBandUrl(data.getOutOfBandUrl());
msg.setReceiptRequested(true);
msg.setStamp(time);
return msg;
}
void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage)
{
if (acc->state == Shared::ConnectionState::connected) {
QString jid = data.getPenPalJid();
QString id = data.getId();
RosterItem* ri = acc->rh->getRosterItem(jid);
if (!ri) {
qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
return;
}
QString path = data.getAttachPath();
QString url = acc->network->getFileRemoteUrl(path);
if (url.size() != 0) {
sendMessageWithLocalUploadedFile(data, url, newMessage);
} else {
pendingStateMessages.insert(std::make_pair(id, jid));
if (newMessage) {
ri->appendMessageToArchive(data);
} else {
QMap<QString, QVariant> changes({
{"state", (uint)Shared::Message::State::pending}
});
ri->changeMessage(id, changes);
emit acc->changeMessage(jid, id, changes);
}
//this checks if the file is already uploading, and if so it subscribes to it's success, so, i need to do stuff only if the network knows nothing of this file
if (!acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
if (acc->um->serviceFound()) {
QFileInfo file(path);
if (file.exists() && file.isReadable()) {
pendingStateMessages.insert(std::make_pair(id, jid));
uploadingSlotsQueue.emplace_back(path, id);
if (uploadingSlotsQueue.size() == 1) {
acc->um->requestUploadSlot(file);
}
} else {
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
}
} else {
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
}
}
}
} else {
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
}
}
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
{
if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
} else {
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
const QString& mId = pair.second;
QString palJid = pendingStateMessages.at(mId);
acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
}
}
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
{
QString err(request.error().text());
if (uploadingSlotsQueue.size() == 0) {
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
qDebug() << err;
} else {
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
uploadingSlotsQueue.pop_front();
if (uploadingSlotsQueue.size() > 0) {
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
}
}
}
void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
{
QMap<QString, QVariant> cData = {
{"attachPath", path}
};
for (const Shared::MessageInfo& info : msgs) {
if (info.account == acc->getName()) {
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
if (cnt != 0) {
if (cnt->changeMessage(info.messageId, cData)) {
emit acc->changeMessage(info.jid, info.messageId, cData);
}
}
}
}
}
void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
{
if (up) {
for (const Shared::MessageInfo& info : msgs) {
if (info.account == acc->getName()) {
handleUploadError(info.jid, info.messageId, text);
}
}
}
}
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
{
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
pendingStateMessages.erase(messageId);
pendingCorrectionMessages.erase(messageId);
requestChangeMessage(jid, messageId, {
{"state", static_cast<uint>(Shared::Message::State::error)},
{"errorText", errorText}
});
}
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
{
for (const Shared::MessageInfo& info : msgs) {
if (info.account == acc->getName()) {
RosterItem* ri = acc->rh->getRosterItem(info.jid);
if (ri != 0) {
Shared::Message msg = ri->getMessage(info.messageId);
msg.setAttachPath(path);
sendMessageWithLocalUploadedFile(msg, url, false);
} else {
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
}
}
}
}
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
{
msg.setOutOfBandUrl(url);
if (msg.getBody().size() == 0) { //not sure why, but most messages do that
msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
}
performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage);
//TODO removal/progress update
}
static const std::set<QString> allowedToChangeKeys({
"attachPath",
"outOfBandUrl",
"state",
"errorText"
});
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
{
RosterItem* cnt = acc->rh->getRosterItem(jid);
if (cnt != 0) {
bool allSupported = true;
QString unsupportedString;
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
if (allowedToChangeKeys.count(itr.key()) != 1) { //to not allow this method
allSupported = false; //to make a message to look like if it was edited
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
break; //because the underlying tech assumes that
} //the change is initiated by user, not by system
}
if (allSupported) {
cnt->changeMessage(messageId, data);
emit acc->changeMessage(jid, messageId, data);
} else {
qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping";
}
}
}
void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
{
RosterItem* cnt = acc->rh->getRosterItem(jid);
if (cnt != 0) {
try {
Shared::Message msg = cnt->getMessage(id);
if (msg.getState() == Shared::Message::State::error) {
if (msg.getEdited()){
QString originalId = msg.getId();
msg.generateRandomId();
sendMessage(msg, false, originalId);
} else {
sendMessage(msg, false);
}
} else {
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping";
}
} catch (const Archive::NotFound& err) {
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message wasn't found in history, skipping";
}
} else {
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this jid isn't present in account roster, skipping";
}
}

View File

@ -0,0 +1,86 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_MESSAGEHANDLER_H
#define CORE_MESSAGEHANDLER_H
#include <QObject>
#include <deque>
#include <map>
#include <QXmppMessage.h>
#include <QXmppHttpUploadIq.h>
#include <shared/message.h>
#include <shared/messageinfo.h>
#include <shared/pathcheck.h>
namespace Core {
/**
* @todo write docs
*/
class Account;
class MessageHandler : public QObject
{
Q_OBJECT
public:
MessageHandler(Account* account);
public:
void sendMessage(const Shared::Message& data, bool newMessage = true, QString originalId = "");
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
void resendMessage(const QString& jid, const QString& id);
public slots:
void onMessageReceived(const QXmppMessage& message);
void onCarbonMessageReceived(const QXmppMessage& message);
void onCarbonMessageSent(const QXmppMessage& message);
void onReceiptReceived(const QString& jid, const QString& id);
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
private:
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
void performSending(Shared::Message data, const QString& originalId, bool newMessage = true);
void prepareUpload(const Shared::Message& data, bool newMessage = true);
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const;
QMap<QString, QVariant> getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const;
std::tuple<bool, QString, QString> getOriginalPendingMessageId(const QString& id);
private:
Account* acc;
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
std::map<QString, QString> pendingCorrectionMessages; //key is new mesage, value is originalOne
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
};
}
#endif // CORE_MESSAGEHANDLER_H

View File

@ -0,0 +1,600 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rosterhandler.h"
#include "core/account.h"
Core::RosterHandler::RosterHandler(Core::Account* account):
QObject(),
acc(account),
contacts(),
conferences(),
groups(),
queuedContacts(),
outOfRosterContacts(),
pepSupport(false)
{
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
connect(acc->rm, &QXmppRosterManager::itemRemoved, this, &RosterHandler::onRosterItemRemoved);
connect(acc->rm, &QXmppRosterManager::itemChanged, this, &RosterHandler::onRosterItemChanged);
connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded);
connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
}
Core::RosterHandler::~RosterHandler()
{
for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
delete itr->second;
}
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
delete itr->second;
}
}
void Core::RosterHandler::onRosterReceived()
{
acc->requestVCard(acc->getBareJid()); //TODO need to make sure server actually supports vCards
QStringList bj = acc->rm->getRosterBareJids();
for (int i = 0; i < bj.size(); ++i) {
const QString& jid = bj[i];
addedAccount(jid.toLower());
}
}
void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
{
QString lcJid = bareJid.toLower();
addedAccount(lcJid);
std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid);
if (itr != queuedContacts.end()) {
acc->rm->subscribe(lcJid, itr->second);
queuedContacts.erase(itr);
}
}
void Core::RosterHandler::addedAccount(const QString& jid)
{
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
QXmppRosterIq::Item re = acc->rm->getRosterEntry(jid);
Contact* contact;
bool newContact = false;
if (itr == contacts.end()) {
newContact = true;
contact = new Contact(jid, acc->name);
contacts.insert(std::make_pair(jid, contact));
} else {
contact = itr->second;
}
QSet<QString> gr = re.groups();
Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
contact->setGroups(gr);
contact->setSubscriptionState(state);
contact->setName(re.name());
if (newContact) {
QMap<QString, QVariant> cData({
{"name", re.name()},
{"state", QVariant::fromValue(state)}
});
careAboutAvatar(contact, cData);
int grCount = 0;
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
const QString& groupName = *itr;
addToGroup(jid, groupName);
emit acc->addContact(jid, groupName, cData);
grCount++;
}
if (grCount == 0) {
emit acc->addContact(jid, "", cData);
}
handleNewContact(contact);
}
}
void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin)
{
QXmppMucRoom* room = acc->mm->addRoom(jid);
QString lNick = nick;
if (lNick.size() == 0) {
lNick = acc->getName();
}
Conference* conf = new Conference(jid, acc->getName(), autoJoin, roomName, lNick, room);
conferences.insert(std::make_pair(jid, conf));
handleNewConference(conf);
QMap<QString, QVariant> cData = {
{"autoJoin", conf->getAutoJoin()},
{"joined", conf->getJoined()},
{"nick", conf->getNick()},
{"name", conf->getName()},
{"avatars", conf->getAllAvatars()}
};
careAboutAvatar(conf, cData);
emit acc->addRoom(jid, cData);
}
void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data)
{
Archive::AvatarInfo info;
bool hasAvatar = item->readAvatarInfo(info);
if (hasAvatar) {
if (info.autogenerated) {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
} else {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
}
data.insert("avatarPath", item->avatarPath() + "." + info.type);
} else {
data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
data.insert("avatarPath", "");
acc->requestVCard(item->jid);
}
}
void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
{
if (acc->state == Shared::ConnectionState::connected) {
std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
if (itr != queuedContacts.end()) {
qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is already queued for adding, skipping";
} else {
queuedContacts.insert(std::make_pair(jid, "")); //TODO need to add reason here;
acc->rm->addItem(jid, name, groups);
}
} else {
qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is not in the connected state, skipping";
}
}
void Core::RosterHandler::removeContactRequest(const QString& jid)
{
QString lcJid = jid.toLower();
if (acc->state == Shared::ConnectionState::connected) {
std::set<QString>::const_iterator itr = outOfRosterContacts.find(lcJid);
if (itr != outOfRosterContacts.end()) {
outOfRosterContacts.erase(itr);
onRosterItemRemoved(lcJid);
} else {
acc->rm->removeItem(lcJid);
}
} else {
qDebug() << "An attempt to remove contact " << lcJid << " from account " << acc->name << " but the account is not in the connected state, skipping";
}
}
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
{
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
}
void Core::RosterHandler::handleNewContact(Core::Contact* contact)
{
handleNewRosterItem(contact);
connect(contact, &Contact::groupAdded, this, &RosterHandler::onContactGroupAdded);
connect(contact, &Contact::groupRemoved, this, &RosterHandler::onContactGroupRemoved);
connect(contact, &Contact::subscriptionStateChanged, this, &RosterHandler::onContactSubscriptionStateChanged);
}
void Core::RosterHandler::handleNewConference(Core::Conference* contact)
{
handleNewRosterItem(contact);
connect(contact, &Conference::nickChanged, this, &RosterHandler::onMucNickNameChanged);
connect(contact, &Conference::subjectChanged, this, &RosterHandler::onMucSubjectChanged);
connect(contact, &Conference::joinedChanged, this, &RosterHandler::onMucJoinedChanged);
connect(contact, &Conference::autoJoinChanged, this, &RosterHandler::onMucAutoJoinChanged);
connect(contact, &Conference::addParticipant, this, &RosterHandler::onMucAddParticipant);
connect(contact, &Conference::changeParticipant, this, &RosterHandler::onMucChangeParticipant);
connect(contact, &Conference::removeParticipant, this, &RosterHandler::onMucRemoveParticipant);
}
void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->addRoomParticipant(room->jid, nickName, data);
}
void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoomParticipant(room->jid, nickName, data);
}
void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->removeRoomParticipant(room->jid, nickName);
}
void Core::RosterHandler::onMucSubjectChanged(const QString& subject)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, {
{"subject", subject}
});
}
void Core::RosterHandler::onContactGroupAdded(const QString& group)
{
Contact* contact = static_cast<Contact*>(sender());
if (contact->groupsCount() == 1) {
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
}
QMap<QString, QVariant> cData({
{"name", contact->getName()},
{"state", QVariant::fromValue(contact->getSubscriptionState())}
});
addToGroup(contact->jid, group);
emit acc->addContact(contact->jid, group, cData);
}
void Core::RosterHandler::onContactGroupRemoved(const QString& group)
{
Contact* contact = static_cast<Contact*>(sender());
if (contact->groupsCount() == 0) {
// not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
}
emit acc->removeContact(contact->jid, group);
removeFromGroup(contact->jid, group);
}
void Core::RosterHandler::onContactNameChanged(const QString& cname)
{
Contact* contact = static_cast<Contact*>(sender());
QMap<QString, QVariant> cData({
{"name", cname},
});
emit acc->changeContact(contact->jid, cData);
}
void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate)
{
Contact* contact = static_cast<Contact*>(sender());
QMap<QString, QVariant> cData({
{"state", QVariant::fromValue(cstate)},
});
emit acc->changeContact(contact->jid, cData);
}
void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
{
std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
if (gItr == groups.end()) {
gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
emit acc->addGroup(group);
}
gItr->second.insert(jid.toLower());
}
void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group)
{
QSet<QString> toRemove;
std::map<QString, std::set<QString>>::iterator itr = groups.find(group);
if (itr == groups.end()) {
qDebug() << "An attempt to remove contact" << jid << "of account" << acc->name << "from non existing group" << group << ", skipping";
return;
}
std::set<QString> contacts = itr->second;
std::set<QString>::const_iterator cItr = contacts.find(jid.toLower());
if (cItr != contacts.end()) {
contacts.erase(cItr);
if (contacts.size() == 0) {
emit acc->removeGroup(group);
groups.erase(group);
}
}
}
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
{
RosterItem* item = 0;
QString lcJid = jid.toLower();
std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid);
if (citr != contacts.end()) {
item = citr->second;
} else {
std::map<QString, Conference*>::const_iterator coitr = conferences.find(lcJid);
if (coitr != conferences.end()) {
item = coitr->second;
}
}
return item;
}
Core::Conference * Core::RosterHandler::getConference(const QString& jid)
{
Conference* item = 0;
std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
if (coitr != conferences.end()) {
item = coitr->second;
}
return item;
}
Core::Contact * Core::RosterHandler::getContact(const QString& jid)
{
Contact* item = 0;
std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
if (citr != contacts.end()) {
item = citr->second;
}
return item;
}
Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid)
{
QString lcJid = jid.toLower();
Contact* cnt = new Contact(lcJid, acc->name);
contacts.insert(std::make_pair(lcJid, cnt));
outOfRosterContacts.insert(lcJid);
cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
emit acc->addContact(lcJid, "", QMap<QString, QVariant>({
{"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
}));
handleNewContact(cnt);
return cnt;
}
void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
{
QString lcJid = bareJid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) {
qDebug() << "An attempt to change non existing contact" << lcJid << "from account" << acc->name << ", skipping";
return;
}
Contact* contact = itr->second;
QXmppRosterIq::Item re = acc->rm->getRosterEntry(lcJid);
Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
contact->setGroups(re.groups());
contact->setSubscriptionState(state);
contact->setName(re.name());
}
void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid)
{
QString lcJid = bareJid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) {
qDebug() << "An attempt to remove non existing contact" << lcJid << "from account" << acc->name << ", skipping";
return;
}
Contact* contact = itr->second;
contacts.erase(itr);
QSet<QString> cGroups = contact->getGroups();
for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) {
removeFromGroup(lcJid, *itr);
}
emit acc->removeContact(lcJid);
contact->deleteLater();
}
void Core::RosterHandler::onMucRoomAdded(QXmppMucRoom* room)
{
qDebug() << "room" << room->jid() << "added with name" << room->name()
<< ", account" << acc->getName() << "joined:" << room->isJoined();
}
void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
{
QList<QXmppBookmarkConference> confs = bookmarks.conferences();
for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) {
const QXmppBookmarkConference& c = *itr;
QString jid = c.jid().toLower();
std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
if (cItr == conferences.end()) {
addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
} else {
if (c.autoJoin()) {
cItr->second->setJoined(true);
} else {
cItr->second->setAutoJoin(false);
}
}
}
}
void Core::RosterHandler::onMucJoinedChanged(bool joined)
{
Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, {
{"joined", joined}
});
}
void Core::RosterHandler::onMucAutoJoinChanged(bool autoJoin)
{
storeConferences();
Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, {
{"autoJoin", autoJoin}
});
}
void Core::RosterHandler::onMucNickNameChanged(const QString& nickName)
{
storeConferences();
Conference* room = static_cast<Conference*>(sender());
emit acc->changeRoom(room->jid, {
{"nick", nickName}
});
}
Shared::SubscriptionState Core::RosterHandler::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs)
{
Shared::SubscriptionState state;
if (qs == QXmppRosterIq::Item::NotSet) {
state = Shared::SubscriptionState::unknown;
} else {
state = static_cast<Shared::SubscriptionState>(qs);
}
return state;
}
void Core::RosterHandler::storeConferences()
{
QXmppBookmarkSet bms = acc->bm->bookmarks();
QList<QXmppBookmarkConference> confs;
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
Conference* conference = itr->second;
QXmppBookmarkConference conf;
conf.setJid(conference->jid);
conf.setName(conference->getName());
conf.setNickName(conference->getNick());
conf.setAutoJoin(conference->getAutoJoin());
confs.push_back(conf);
}
bms.setConferences(confs);
acc->bm->setBookmarks(bms);
}
void Core::RosterHandler::clearConferences()
{
for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) {
itr->second->deleteLater();
emit acc->removeRoom(itr->first);
}
conferences.clear();
}
void Core::RosterHandler::removeRoomRequest(const QString& jid)
{
QString lcJid = jid.toLower();
std::map<QString, Conference*>::const_iterator itr = conferences.find(lcJid);
if (itr == conferences.end()) {
qDebug() << "An attempt to remove non existing room" << lcJid << "from account" << acc->name << ", skipping";
}
itr->second->deleteLater();
conferences.erase(itr);
emit acc->removeRoom(lcJid);
storeConferences();
}
void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin)
{
QString lcJid = jid.toLower();
std::map<QString, Conference*>::const_iterator cItr = conferences.find(lcJid);
if (cItr == conferences.end()) {
addNewRoom(lcJid, nick, "", autoJoin);
storeConferences();
} else {
qDebug() << "An attempt to add a MUC " << lcJid << " which is already present in the rester, skipping";
}
}
void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName)
{
QString lcJid = jid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) {
qDebug() << "An attempt to add non existing contact" << lcJid << "of account"
<< acc->name << "to the group" << groupName << ", skipping";
} else {
QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
QSet<QString> groups = item.groups();
if (groups.find(groupName) == groups.end()) { //TODO need to change it, I guess that sort of code is better in qxmpp lib
groups.insert(groupName);
item.setGroups(groups);
QXmppRosterIq iq;
iq.setType(QXmppIq::Set);
iq.addItem(item);
acc->client.sendPacket(iq);
} else {
qDebug() << "An attempt to add contact" << jid << "of account"
<< acc->name << "to the group" << groupName << "but it's already in that group, skipping";
}
}
}
void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName)
{
QString lcJid = jid.toLower();
std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
if (itr == contacts.end()) {
qDebug() << "An attempt to remove non existing contact" << lcJid << "of account"
<< acc->name << "from the group" << groupName << ", skipping";
} else {
QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
QSet<QString> groups = item.groups();
QSet<QString>::const_iterator gItr = groups.find(groupName);
if (gItr != groups.end()) {
groups.erase(gItr);
item.setGroups(groups);
QXmppRosterIq iq;
iq.setType(QXmppIq::Set);
iq.addItem(item);
acc->client.sendPacket(iq);
} else {
qDebug() << "An attempt to remove contact" << lcJid << "of account"
<< acc->name << "from the group" << groupName << "but it's not in that group, skipping";
}
}
}
void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QString& path)
{
RosterItem* item = static_cast<RosterItem*>(sender());
QMap<QString, QVariant> cData({
{"avatarState", static_cast<uint>(type)},
{"avatarPath", path}
});
emit acc->changeContact(item->jid, cData);
}
void Core::RosterHandler::handleOffline()
{
for (const std::pair<const QString, Conference*>& pair : conferences) {
pair.second->clearArchiveRequests();
pair.second->downgradeDatabaseState();
}
for (const std::pair<const QString, Contact*>& pair : contacts) {
pair.second->clearArchiveRequests();
pair.second->downgradeDatabaseState();
}
setPepSupport(false);
}
void Core::RosterHandler::setPepSupport(bool support)
{
if (pepSupport != support) {
pepSupport = support;
}
}

View File

@ -0,0 +1,116 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_ROSTERHANDLER_H
#define CORE_ROSTERHANDLER_H
#include <QObject>
#include <QSet>
#include <QString>
#include <QDateTime>
#include <list>
#include <map>
#include <set>
#include <QXmppBookmarkSet.h>
#include <QXmppMucManager.h>
#include <QXmppRosterIq.h>
#include <shared/message.h>
#include <core/contact.h>
#include <core/conference.h>
namespace Core {
class Account;
class RosterHandler : public QObject
{
Q_OBJECT
public:
RosterHandler(Account* account);
~RosterHandler();
void addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups);
void removeContactRequest(const QString& jid);
void addContactToGroupRequest(const QString& jid, const QString& groupName);
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
void removeRoomRequest(const QString& jid);
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void handleOffline();
Core::Contact* getContact(const QString& jid);
Core::Conference* getConference(const QString& jid);
Core::RosterItem* getRosterItem(const QString& jid);
Core::Contact* addOutOfRosterContact(const QString& jid);
void storeConferences();
void clearConferences();
void setPepSupport(bool support);
private slots:
void onRosterReceived();
void onRosterItemAdded(const QString& bareJid);
void onRosterItemChanged(const QString& bareJid);
void onRosterItemRemoved(const QString& bareJid);
void onMucRoomAdded(QXmppMucRoom* room);
void onMucJoinedChanged(bool joined);
void onMucAutoJoinChanged(bool autoJoin);
void onMucNickNameChanged(const QString& nickName);
void onMucSubjectChanged(const QString& subject);
void onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
void onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
void onMucRemoveParticipant(const QString& nickName);
void bookmarksReceived(const QXmppBookmarkSet& bookmarks);
void onContactGroupAdded(const QString& group);
void onContactGroupRemoved(const QString& group);
void onContactNameChanged(const QString& name);
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
void onContactAvatarChanged(Shared::Avatar, const QString& path);
private:
void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
void addToGroup(const QString& jid, const QString& group);
void removeFromGroup(const QString& jid, const QString& group);
void addedAccount(const QString &bareJid);
void handleNewRosterItem(Core::RosterItem* contact);
void handleNewContact(Core::Contact* contact);
void handleNewConference(Core::Conference* contact);
void careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data);
static Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs);
private:
Account* acc;
std::map<QString, Contact*> contacts;
std::map<QString, Conference*> conferences;
std::map<QString, std::set<QString>> groups;
std::map<QString, QString> queuedContacts;
std::set<QString> outOfRosterContacts;
bool pepSupport;
};
}
#endif // CORE_ROSTERHANDLER_H

View File

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

View File

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

View File

@ -16,15 +16,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtWidgets/QApplication>
#include <QtCore/QDir>
#include "networkaccess.h"
Core::NetworkAccess::NetworkAccess(QObject* parent):
QObject(parent),
running(false),
manager(0),
files("files"),
downloads()
storage("fileURLStorage"),
downloads(),
uploads(),
currentPath()
{
QSettings settings;
currentPath = settings.value("downloadsPath").toString();
}
Core::NetworkAccess::~NetworkAccess()
@ -32,60 +40,31 @@ Core::NetworkAccess::~NetworkAccess()
stop();
}
void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
void Core::NetworkAccess::downladFile(const QString& url)
{
std::map<QString, Download*>::iterator itr = downloads.find(url);
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
Download* dwn = itr->second;
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
if (mItr == dwn->messages.end()) {
dwn->messages.insert(messageId);
}
emit downloadFileProgress(messageId, dwn->progress);
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
} else {
try {
QString path = files.getRecord(url);
QFileInfo info(path);
if (info.exists() && info.isFile()) {
emit fileLocalPathResponse(messageId, path);
std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
if (p.first.size() > 0) {
QFileInfo info(p.first);
if (info.exists() && info.isFile()) {
emit downloadFileComplete(p.second, p.first);
} else {
startDownload(p.second, url);
}
} else {
files.removeRecord(url);
emit fileLocalPathResponse(messageId, "");
startDownload(p.second, url);
}
} catch (Archive::NotFound e) {
emit fileLocalPathResponse(messageId, "");
} catch (Archive::Unknown e) {
} catch (const Archive::NotFound& e) {
qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
storage.addFile(url);
startDownload(std::list<Shared::MessageInfo>(), url);
} catch (const Archive::Unknown& e) {
qDebug() << "Error requesting file path:" << e.what();
emit fileLocalPathResponse(messageId, "");
}
}
}
void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
{
std::map<QString, Download*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
Download* dwn = itr->second;
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
if (mItr == dwn->messages.end()) {
dwn->messages.insert(messageId);
}
emit downloadFileProgress(messageId, dwn->progress);
} else {
try {
QString path = files.getRecord(url);
QFileInfo info(path);
if (info.exists() && info.isFile()) {
emit fileLocalPathResponse(messageId, path);
} else {
files.removeRecord(url);
startDownload(messageId, url);
}
} catch (Archive::NotFound e) {
startDownload(messageId, url);
} catch (Archive::Unknown e) {
qDebug() << "Error requesting file path:" << e.what();
startDownload(messageId, url);
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
}
}
}
@ -94,7 +73,10 @@ void Core::NetworkAccess::start()
{
if (!running) {
manager = new QNetworkAccessManager();
files.open();
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
manager->setTransferTimeout();
#endif
storage.open();
running = true;
}
}
@ -102,12 +84,12 @@ void Core::NetworkAccess::start()
void Core::NetworkAccess::stop()
{
if (running) {
files.close();
storage.close();
manager->deleteLater();
manager = 0;
running = false;
for (std::map<QString, Download*>::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) {
for (std::map<QString, Transfer*>::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) {
itr->second->success = false;
itr->second->reply->abort(); //assuming it's gonna call onRequestFinished slot
}
@ -118,215 +100,490 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
{
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
if (itr == downloads.end()) {
qDebug() << "an error downloading" << url << ": the request had some progress but seems like noone is waiting for it, skipping";
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
} else {
Download* dwn = itr->second;
qreal received = bytesReceived;
qreal total = bytesTotal;
qreal progress = received/total;
dwn->progress = progress;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit downloadFileProgress(*mItr, progress);
Transfer* dwn = itr->second;
if (dwn->success) {
qreal received = bytesReceived;
qreal total = bytesTotal;
qreal progress = received/total;
dwn->progress = progress;
emit loadFileProgress(dwn->messages, progress, false);
}
}
}
void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code)
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
{
qDebug() << "DEBUG: DOWNLOAD ERROR";
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
qDebug() << rpl->errorString();
QString url = rpl->url().toString();
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
if (itr == downloads.end()) {
qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
} else {
QString errorText;
switch (code) {
case QNetworkReply::NoError:
//this never is supposed to happen
break;
// network layer errors [relating to the destination server] (1-99):
case QNetworkReply::ConnectionRefusedError:
errorText = "Connection refused";
break;
case QNetworkReply::RemoteHostClosedError:
errorText = "Remote server closed the connection";
break;
case QNetworkReply::HostNotFoundError:
errorText = "Remote host is not found";
break;
case QNetworkReply::TimeoutError:
errorText = "Connection was closed because it timed out";
break;
case QNetworkReply::OperationCanceledError:
//this means I closed it myself by abort() or close(), don't think I need to notify here
break;
case QNetworkReply::SslHandshakeFailedError:
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
break;
case QNetworkReply::TemporaryNetworkFailureError:
//this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify
break;
case QNetworkReply::NetworkSessionFailedError:
errorText = "Outgoing connection problem";
break;
case QNetworkReply::BackgroundRequestNotAllowedError:
errorText = "Background request is not allowed";
break;
case QNetworkReply::TooManyRedirectsError:
errorText = "The request was redirected too many times";
break;
case QNetworkReply::InsecureRedirectError:
errorText = "The request was redirected to insecure connection";
break;
case QNetworkReply::UnknownNetworkError:
errorText = "Unknown network error";
break;
// proxy errors (101-199):
case QNetworkReply::ProxyConnectionRefusedError:
errorText = "The connection to the proxy server was refused";
break;
case QNetworkReply::ProxyConnectionClosedError:
errorText = "Proxy server closed the connection";
break;
case QNetworkReply::ProxyNotFoundError:
errorText = "Proxy host was not found";
break;
case QNetworkReply::ProxyTimeoutError:
errorText = "Connection to the proxy server was closed because it timed out";
break;
case QNetworkReply::ProxyAuthenticationRequiredError:
errorText = "Couldn't connect to proxy server, authentication is required";
break;
case QNetworkReply::UnknownProxyError:
errorText = "Unknown proxy error";
break;
// content errors (201-299):
case QNetworkReply::ContentAccessDenied:
errorText = "The access to file is denied";
break;
case QNetworkReply::ContentOperationNotPermittedError:
errorText = "The operation over requesting file is not permitted";
break;
case QNetworkReply::ContentNotFoundError:
errorText = "The file was not found";
break;
case QNetworkReply::AuthenticationRequiredError:
errorText = "Couldn't access the file, authentication is required";
break;
case QNetworkReply::ContentReSendError:
errorText = "Sending error, one more attempt will probably solve this problem";
break;
case QNetworkReply::ContentConflictError:
errorText = "The request could not be completed due to a conflict with the current state of the resource";
break;
case QNetworkReply::ContentGoneError:
errorText = "The requested resource is no longer available at the server";
break;
case QNetworkReply::UnknownContentError:
errorText = "Unknown content error";
break;
// protocol errors
case QNetworkReply::ProtocolUnknownError:
errorText = "Unknown protocol error";
break;
case QNetworkReply::ProtocolInvalidOperationError:
errorText = "Requested operation is not permitted in this protocol";
break;
case QNetworkReply::ProtocolFailure:
errorText = "Low level protocol error";
break;
// Server side errors (401-499)
case QNetworkReply::InternalServerError:
errorText = "Internal server error";
break;
case QNetworkReply::OperationNotImplementedError:
errorText = "Server doesn't support requested operation";
break;
case QNetworkReply::ServiceUnavailableError:
errorText = "The server is not available for this operation right now";
break;
case QNetworkReply::UnknownServerError:
errorText = "Unknown server error";
break;
}
if (errorText.size() > 0) {
QString errorText = getErrorText(code);
//if (errorText.size() > 0) {
itr->second->success = false;
Download* dwn = itr->second;
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit downloadFileError(*mItr, errorText);
}
}
Transfer* dwn = itr->second;
emit loadFileError(dwn->messages, errorText, false);
//}
}
}
void Core::NetworkAccess::onRequestFinished()
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
{
QString path("");
qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
for (const QSslError& err : errors) {
qDebug() << err.errorString();
}
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Download*>::const_iterator itr = downloads.find(url);
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
if (itr == downloads.end()) {
qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
qDebug() << "an SSL error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
} else {
Download* dwn = itr->second;
//if (errorText.size() > 0) {
itr->second->success = false;
Transfer* dwn = itr->second;
emit loadFileError(dwn->messages, "SSL errors occured", false);
//}
}
}
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
{
QString errorText("");
switch (code) {
case QNetworkReply::NoError:
//this never is supposed to happen
break;
// network layer errors [relating to the destination server] (1-99):
case QNetworkReply::ConnectionRefusedError:
errorText = "Connection refused";
break;
case QNetworkReply::RemoteHostClosedError:
errorText = "Remote server closed the connection";
break;
case QNetworkReply::HostNotFoundError:
errorText = "Remote host is not found";
break;
case QNetworkReply::TimeoutError:
errorText = "Connection was closed because it timed out";
break;
case QNetworkReply::OperationCanceledError:
//this means I closed it myself by abort() or close()
//I don't call them directory, but this is the error code
//Qt returns when it can not resume donwload after the network failure
//or when the download is canceled by the timout;
errorText = "Connection lost";
break;
case QNetworkReply::SslHandshakeFailedError:
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
break;
case QNetworkReply::TemporaryNetworkFailureError:
//this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify
break;
case QNetworkReply::NetworkSessionFailedError:
errorText = "Outgoing connection problem";
break;
case QNetworkReply::BackgroundRequestNotAllowedError:
errorText = "Background request is not allowed";
break;
case QNetworkReply::TooManyRedirectsError:
errorText = "The request was redirected too many times";
break;
case QNetworkReply::InsecureRedirectError:
errorText = "The request was redirected to insecure connection";
break;
case QNetworkReply::UnknownNetworkError:
errorText = "Unknown network error";
break;
// proxy errors (101-199):
case QNetworkReply::ProxyConnectionRefusedError:
errorText = "The connection to the proxy server was refused";
break;
case QNetworkReply::ProxyConnectionClosedError:
errorText = "Proxy server closed the connection";
break;
case QNetworkReply::ProxyNotFoundError:
errorText = "Proxy host was not found";
break;
case QNetworkReply::ProxyTimeoutError:
errorText = "Connection to the proxy server was closed because it timed out";
break;
case QNetworkReply::ProxyAuthenticationRequiredError:
errorText = "Couldn't connect to proxy server, authentication is required";
break;
case QNetworkReply::UnknownProxyError:
errorText = "Unknown proxy error";
break;
// content errors (201-299):
case QNetworkReply::ContentAccessDenied:
errorText = "The access to file is denied";
break;
case QNetworkReply::ContentOperationNotPermittedError:
errorText = "The operation over requesting file is not permitted";
break;
case QNetworkReply::ContentNotFoundError:
errorText = "The file was not found";
break;
case QNetworkReply::AuthenticationRequiredError:
errorText = "Couldn't access the file, authentication is required";
break;
case QNetworkReply::ContentReSendError:
errorText = "Sending error, one more attempt will probably solve this problem";
break;
case QNetworkReply::ContentConflictError:
errorText = "The request could not be completed due to a conflict with the current state of the resource";
break;
case QNetworkReply::ContentGoneError:
errorText = "The requested resource is no longer available at the server";
break;
case QNetworkReply::UnknownContentError:
errorText = "Unknown content error";
break;
// protocol errors
case QNetworkReply::ProtocolUnknownError:
errorText = "Unknown protocol error";
break;
case QNetworkReply::ProtocolInvalidOperationError:
errorText = "Requested operation is not permitted in this protocol";
break;
case QNetworkReply::ProtocolFailure:
errorText = "Low level protocol error";
break;
// Server side errors (401-499)
case QNetworkReply::InternalServerError:
errorText = "Internal server error";
break;
case QNetworkReply::OperationNotImplementedError:
errorText = "Server doesn't support requested operation";
break;
case QNetworkReply::ServiceUnavailableError:
errorText = "The server is not available for this operation right now";
break;
case QNetworkReply::UnknownServerError:
errorText = "Unknown server error";
break;
}
return errorText;
}
void Core::NetworkAccess::onDownloadFinished()
{
qDebug() << "DEBUG: DOWNLOAD FINISHED";
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
if (itr == downloads.end()) {
qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
} else {
Transfer* dwn = itr->second;
if (dwn->success) {
qDebug() << "download success for" << url;
QString err;
QStringList hops = url.split("/");
QString fileName = hops.back();
QStringList parts = fileName.split(".");
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
QString suffix("");
QString realName = parts.front();
for (QStringList::const_iterator sItr = (parts.begin()++), sEnd = parts.end(); sItr != sEnd; ++sItr) {
suffix += "." + (*sItr);
QString jid;
if (dwn->messages.size() > 0) {
jid = dwn->messages.front().jid;
} else {
qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
}
QString postfix("");
QFileInfo proposedName(path + realName + postfix + suffix);
int counter = 0;
while (proposedName.exists()) {
suffix = QString("(") + std::to_string(++counter).c_str() + ")";
proposedName = QFileInfo(path + realName + postfix + suffix);
QString path = prepareDirectory(jid);
if (path.size() > 0) {
path = checkFileName(fileName, path);
QFile file(Shared::resolvePath(path));
if (file.open(QIODevice::WriteOnly)) {
file.write(dwn->reply->readAll());
file.close();
storage.setPath(url, path);
qDebug() << "file" << path << "was successfully downloaded";
} else {
qDebug() << "couldn't save file" << path;
err = "Error opening file to write:" + file.errorString();
}
} else {
err = "Couldn't prepare a directory for file";
}
path = proposedName.absoluteFilePath();
QFile file(path);
if (file.open(QIODevice::WriteOnly)) {
file.write(dwn->reply->readAll());
file.close();
files.addRecord(url, path);
qDebug() << "file" << path << "was successfully downloaded";
if (path.size() > 0) {
emit downloadFileComplete(dwn->messages, path);
} else {
qDebug() << "couldn't save file" << path;
path = "";
emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
}
}
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
emit fileLocalPathResponse(*mItr, path);
}
dwn->reply->deleteLater();
delete dwn;
downloads.erase(itr);
}
}
void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
{
Download* dwn = new Download({{messageId}, 0, 0, true});
Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
QNetworkRequest req(url);
dwn->reply = manager->get(req);
connect(dwn->reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64)));
connect(dwn->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError)));
connect(dwn->reply, SIGNAL(finished()), SLOT(onRequestFinished()));
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
connect(dwn->reply, &QNetworkReply::sslErrors, this, &NetworkAccess::onDownloadSSLError);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
#else
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onDownloadError);
#endif
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
downloads.insert(std::make_pair(url, dwn));
emit downloadFileProgress(messageId, 0);
emit loadFileProgress(dwn->messages, 0, false);
}
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
{
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
if (itr == uploads.end()) {
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
} else {
QString errorText = getErrorText(code);
//if (errorText.size() > 0) {
itr->second->success = false;
Transfer* upl = itr->second;
emit loadFileError(upl->messages, errorText, true);
//}
//TODO deletion?
}
}
void Core::NetworkAccess::onUploadFinished()
{
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
if (itr == downloads.end()) {
qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
} else {
Transfer* upl = itr->second;
if (upl->success) {
qDebug() << "upload success for" << url;
// Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
if (upl->path.startsWith(QDir::tempPath() + QDir::separator() + QStringLiteral("squawk_img_attach_")) && upl->path.endsWith(".png")) {
QString err = "";
QString downloadDirPath = prepareDirectory(upl->messages.front().jid);
if (downloadDirPath.size() > 0) {
QString newPath = downloadDirPath + QDir::separator() + upl->path.mid(QDir::tempPath().length() + 1);
// Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath));
if (copyResult) {
// Change storage
upl->path = newPath;
} else {
err = "copying to " + newPath + " failed";
}
} else {
err = "Couldn't prepare a directory for file";
}
if (err.size() != 0) {
qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
}
}
storage.addFile(upl->messages, upl->url, upl->path);
emit uploadFileComplete(upl->messages, upl->url, upl->path);
}
upl->reply->deleteLater();
upl->file->close();
upl->file->deleteLater();
delete upl;
uploads.erase(itr);
}
}
void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
if (itr == uploads.end()) {
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
} else {
Transfer* upl = itr->second;
if (upl->success) {
qreal received = bytesReceived;
qreal total = bytesTotal;
qreal progress = received/total;
upl->progress = progress;
emit loadFileProgress(upl->messages, progress, true);
}
}
}
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
{
QString p = Shared::squawkifyPath(path);
try {
p = storage.getUrl(p);
} catch (const Archive::NotFound& err) {
p = "";
} catch (...) {
throw;
}
return p;
}
void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
{
QFile* file = new QFile(path);
Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
QNetworkRequest req(put);
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
}
if (file->open(QIODevice::ReadOnly)) {
upl->reply = manager->put(req, file);
connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
#else
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
#endif
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
uploads.insert(std::make_pair(put.toString(), upl));
emit loadFileProgress(upl->messages, 0, true);
} else {
qDebug() << "couldn't upload file" << path;
emit loadFileError(upl->messages, "Error opening file", true);
delete file;
delete upl;
}
}
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
{
storage.addFile(url, account, jid, id);
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay?
}
}
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
{
storage.addFile(url, path, account, jid, id);
}
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
{
for (const std::pair<const QString, Transfer*>& pair : uploads) {
Transfer* info = pair.second;
if (pair.second->path == path) {
std::list<Shared::MessageInfo>& messages = info->messages;
bool dup = false;
for (const Shared::MessageInfo& info : messages) {
if (info.account == acc && info.jid == jid && info.messageId == id) {
dup = true;
break;
}
}
if (!dup) {
info->messages.emplace_back(acc, jid, id); //TODO notification is going to happen the next tick, is that okay?
return true;
}
}
}
return false;
}
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
{
QString path = currentPath;
QString addition;
if (jid.size() > 0) {
addition = jid;
path += QDir::separator() + jid;
}
QDir location(path);
if (!location.exists()) {
bool res = location.mkpath(path);
if (!res) {
return "";
} else {
return "squawk://" + addition;
}
}
return "squawk://" + addition;
}
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
{
QStringList parts = name.split(".");
QString suffix("");
QStringList::const_iterator sItr = parts.begin();
QString realName = *sItr;
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
suffix += "." + (*sItr);
}
QString postfix("");
QString resolvedPath = Shared::resolvePath(path);
QString count("");
QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix);
int counter = 0;
while (proposedName.exists()) {
count = QString("(") + std::to_string(++counter).c_str() + ")";
proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix);
}
return path + QDir::separator() + realName + count + suffix;
}
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
{
return storage.addMessageAndCheckForPath(url, account, jid, id);
}
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
{
return storage.deletedFile(path);
}
void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
{
QDir dir(currentPath);
bool success = true;
qDebug() << "moving" << currentPath << "to" << newPath;
for (QFileInfo fileInfo : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
QString fileName = fileInfo.fileName();
success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
}
if (!success) {
qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
}
currentPath = newPath;
}

View File

@ -26,20 +26,24 @@
#include <QFileInfo>
#include <QFile>
#include <QStandardPaths>
#include <QSettings>
#include <set>
#include "storage.h"
#include "storage/urlstorage.h"
#include "shared/pathcheck.h"
namespace Core {
/**
* @todo write docs
*/
//TODO Need to describe how to get rid of records when file is no longer reachable;
class NetworkAccess : public QObject
{
Q_OBJECT
struct Download;
struct Transfer;
public:
NetworkAccess(QObject* parent = nullptr);
virtual ~NetworkAccess();
@ -47,34 +51,55 @@ public:
void start();
void stop();
QString getFileRemoteUrl(const QString& path);
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
std::list<Shared::MessageInfo> reportPathInvalid(const QString& path);
signals:
void fileLocalPathResponse(const QString& messageId, const QString& path);
void downloadFileProgress(const QString& messageId, qreal value);
void downloadFileError(const QString& messageId, const QString& path);
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
public slots:
void fileLocalPathRequest(const QString& messageId, const QString& url);
void downladFileRequest(const QString& messageId, const QString& url);
void downladFile(const QString& url);
void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
void moveFilesDirectory(const QString& newPath);
private:
void startDownload(const QString& messageId, const QString& url);
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
QString getErrorText(QNetworkReply::NetworkError code);
QString prepareDirectory(const QString& jid);
QString checkFileName(const QString& name, const QString& path);
private slots:
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onRequestError(QNetworkReply::NetworkError code);
void onRequestFinished();
void onDownloadError(QNetworkReply::NetworkError code);
void onDownloadSSLError(const QList<QSslError> &errors);
void onDownloadFinished();
void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onUploadError(QNetworkReply::NetworkError code);
void onUploadFinished();
private:
bool running;
QNetworkAccessManager* manager;
Storage files;
std::map<QString, Download*> downloads;
UrlStorage storage;
std::map<QString, Transfer*> downloads;
std::map<QString, Transfer*> uploads;
QString currentPath;
struct Download {
std::set<QString> messages;
struct Transfer {
std::list<Shared::MessageInfo> messages;
qreal progress;
QNetworkReply* reply;
bool success;
QString path;
QString url;
QFile* file;
};
};

View File

@ -0,0 +1,9 @@
if (WITH_KWALLET)
target_sources(squawk PRIVATE
kwallet.cpp
kwallet.h
)
add_subdirectory(wrappers)
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
endif ()

View File

@ -0,0 +1,230 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kwallet.h"
Core::PSE::KWallet::OpenWallet Core::PSE::KWallet::openWallet = 0;
Core::PSE::KWallet::NetworkWallet Core::PSE::KWallet::networkWallet = 0;
Core::PSE::KWallet::DeleteWallet Core::PSE::KWallet::deleteWallet = 0;
Core::PSE::KWallet::ReadPassword Core::PSE::KWallet::readPassword = 0;
Core::PSE::KWallet::WritePassword Core::PSE::KWallet::writePassword = 0;
Core::PSE::KWallet::HasFolder Core::PSE::KWallet::hasFolder = 0;
Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0;
Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0;
Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial;
QLibrary Core::PSE::KWallet::lib("kwalletWrapper");
Core::PSE::KWallet::KWallet():
QObject(),
cState(disconnected),
everError(false),
wallet(0),
readRequest(),
writeRequest()
{
if (sState == initial) {
lib.load();
if (lib.isLoaded()) {
openWallet = (OpenWallet) lib.resolve("openWallet");
networkWallet = (NetworkWallet) lib.resolve("networkWallet");
deleteWallet = (DeleteWallet) lib.resolve("deleteWallet");
readPassword = (ReadPassword) lib.resolve("readPassword");
writePassword = (WritePassword) lib.resolve("writePassword");
hasFolder = (HasFolder) lib.resolve("hasFolder");
createFolder = (CreateFolder) lib.resolve("createFolder");
setFolder = (SetFolder) lib.resolve("setFolder");
if (openWallet
&& networkWallet
&& deleteWallet
&& readPassword
&& writePassword
) {
sState = success;
} else {
sState = failure;
}
} else {
sState = failure;
}
}
}
Core::PSE::KWallet::~KWallet()
{
close();
}
void Core::PSE::KWallet::open()
{
if (sState == success) {
if (cState == disconnected) {
QString name;
networkWallet(name);
wallet = openWallet(name, 0, ::KWallet::Wallet::Asynchronous);
if (wallet) {
cState = connecting;
connect(wallet, SIGNAL(walletOpened(bool)), this, SLOT(onWalletOpened(bool)));
connect(wallet, SIGNAL(walletClosed()), this, SLOT(onWalletClosed()));
} else {
everError = true;
emit opened(false);
rejectPending();
}
}
}
}
Core::PSE::KWallet::ConnectionState Core::PSE::KWallet::connectionState()
{
return cState;
}
void Core::PSE::KWallet::close()
{
if (sState == success) {
if (cState != disconnected) {
deleteWallet(wallet);
wallet = 0;
}
rejectPending();
}
}
void Core::PSE::KWallet::onWalletClosed()
{
cState = disconnected;
deleteWallet(wallet);
wallet = 0;
emit closed();
rejectPending();
}
void Core::PSE::KWallet::onWalletOpened(bool success)
{
emit opened(success);
if (success) {
QString appName = QCoreApplication::applicationName();
if (!hasFolder(wallet, appName)) {
createFolder(wallet, appName);
}
setFolder(wallet, appName);
cState = connected;
readPending();
} else {
everError = true;
cState = disconnected;
deleteWallet(wallet);
wallet = 0;
rejectPending();
}
}
Core::PSE::KWallet::SupportState Core::PSE::KWallet::supportState()
{
return sState;
}
bool Core::PSE::KWallet::everHadError() const
{
return everError;
}
void Core::PSE::KWallet::resetEverHadError()
{
everError = false;
}
void Core::PSE::KWallet::requestReadPassword(const QString& login, bool askAgain)
{
if (sState == success) {
readRequest.insert(login);
readSwitch(askAgain);
}
}
void Core::PSE::KWallet::requestWritePassword(const QString& login, const QString& password, bool askAgain)
{
if (sState == success) {
std::map<QString, QString>::iterator itr = writeRequest.find(login);
if (itr == writeRequest.end()) {
writeRequest.insert(std::make_pair(login, password));
} else {
itr->second = password;
}
readSwitch(askAgain);
}
}
void Core::PSE::KWallet::readSwitch(bool askAgain)
{
switch (cState) {
case connected:
readPending();
break;
case connecting:
break;
case disconnected:
if (!everError || askAgain) {
open();
}
break;
}
}
void Core::PSE::KWallet::rejectPending()
{
writeRequest.clear();
std::set<QString>::const_iterator i = readRequest.begin();
while (i != readRequest.end()) {
emit rejectPassword(*i);
readRequest.erase(i);
i = readRequest.begin();
}
}
void Core::PSE::KWallet::readPending()
{
std::map<QString, QString>::const_iterator itr = writeRequest.begin();
while (itr != writeRequest.end()) {
int result = writePassword(wallet, itr->first, itr->second);
if (result == 0) {
qDebug() << "Successfully saved password for user" << itr->first;
} else {
qDebug() << "Error writing password for user" << itr->first << ":" << result;
}
writeRequest.erase(itr);
itr = writeRequest.begin();
}
std::set<QString>::const_iterator i = readRequest.begin();
while (i != readRequest.end()) {
QString password;
int result = readPassword(wallet, *i, password);
if (result == 0 && password.size() > 0) { //even though it's written that the error is supposed to be returned in case there were no password
emit responsePassword(*i, password); //it doesn't do so. I assume empty password as a lack of password in KWallet
} else {
emit rejectPassword(*i);
}
readRequest.erase(i);
i = readRequest.begin();
}
}

View File

@ -0,0 +1,112 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_PSE_KWALLET_H
#define CORE_PSE_KWALLET_H
#include <QObject>
#include <QLibrary>
#include <QDebug>
#include <QCoreApplication>
#include <map>
#include <set>
#include <KF5/KWallet/KWallet>
namespace Core {
namespace PSE {
/**
* @todo write docs
*/
class KWallet : public QObject
{
Q_OBJECT
public:
enum SupportState {
initial,
success,
failure
};
enum ConnectionState {
disconnected,
connecting,
connected
};
KWallet();
~KWallet();
static SupportState supportState();
void open();
void close();
ConnectionState connectionState();
bool everHadError() const;
void resetEverHadError();
void requestReadPassword(const QString& login, bool askAgain = false);
void requestWritePassword(const QString& login, const QString& password, bool askAgain = false);
signals:
void opened(bool success);
void closed();
void responsePassword(const QString& login, const QString& password);
void rejectPassword(const QString& login);
private slots:
void onWalletOpened(bool success);
void onWalletClosed();
private:
void readSwitch(bool askAgain);
void readPending();
void rejectPending();
private:
typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType);
typedef void (*NetworkWallet)(QString&);
typedef void (*DeleteWallet)(::KWallet::Wallet*);
typedef int (*ReadPassword)(::KWallet::Wallet*, const QString&, QString&);
typedef int (*WritePassword)(::KWallet::Wallet*, const QString&, const QString&);
typedef bool (*HasFolder)(::KWallet::Wallet* w, const QString &f);
typedef bool (*CreateFolder)(::KWallet::Wallet* w, const QString &f);
typedef bool (*SetFolder)(::KWallet::Wallet* w, const QString &f);
static OpenWallet openWallet;
static NetworkWallet networkWallet;
static DeleteWallet deleteWallet;
static ReadPassword readPassword;
static WritePassword writePassword;
static HasFolder hasFolder;
static CreateFolder createFolder;
static SetFolder setFolder;
static SupportState sState;
static QLibrary lib;
ConnectionState cState;
bool everError;
::KWallet::Wallet* wallet;
std::set<QString> readRequest;
std::map<QString, QString> writeRequest;
};
}}
#endif // CORE_PSE_KWALLET_H

View File

@ -0,0 +1,4 @@
add_library(kwalletWrapper SHARED kwallet.cpp)
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

View File

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

View File

@ -17,12 +17,14 @@
*/
#include "rosteritem.h"
#include "account.h"
#include <QDebug>
Core::RosterItem::RosterItem(const QString& pJid, const QString& account, QObject* parent):
Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObject* parent):
QObject(parent),
jid(pJid),
account(pAccount),
name(),
archiveState(empty),
archive(new Archive(jid)),
@ -33,6 +35,7 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& account, QObjec
appendCache(),
responseCache(),
requestCache(),
toCorrect(),
muc(false)
{
archive->open(account);
@ -73,6 +76,36 @@ void Core::RosterItem::addMessageToArchive(const Shared::Message& msg)
{
if (msg.storable()) {
hisoryCache.push_back(msg);
std::map<QString, Shared::Message>::iterator itr = toCorrect.find(msg.getId());
if (itr != toCorrect.end()) {
hisoryCache.back().change({
{"body", itr->second.getBody()},
{"stamp", itr->second.getTime()}
});
toCorrect.erase(itr);
}
}
}
void Core::RosterItem::correctMessageInArchive(const QString& originalId, const Shared::Message& msg)
{
if (msg.storable()) {
QDateTime thisTime = msg.getTime();
std::map<QString, Shared::Message>::iterator itr = toCorrect.find(originalId);
if (itr != toCorrect.end()) {
if (itr->second.getTime() < thisTime) {
itr->second = msg;
}
return;
}
bool found = changeMessage(originalId, {
{"body", msg.getBody()},
{"stamp", thisTime}
});
if (!found) {
toCorrect.insert(std::make_pair(originalId, msg));
}
}
}
@ -89,7 +122,26 @@ void Core::RosterItem::nextRequest()
{
if (syncronizing) {
if (requestedCount != -1) {
emit historyResponse(responseCache);
bool last = false;
if (archiveState == beginning || archiveState == complete) {
try {
QString firstId = archive->oldestId();
if (responseCache.size() == 0) {
if (requestedBefore == firstId) {
last = true;
}
} else {
if (responseCache.front().getId() == firstId) {
last = true;
}
}
} catch (const Archive::Empty& e) {
last = true;
}
} else if (archiveState == empty && responseCache.size() == 0) {
last = true;
}
emit historyResponse(responseCache, last);
}
}
if (requestCache.size() > 0) {
@ -123,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before)
requestCache.emplace_back(requestedCount, before);
requestedCount = -1;
}
Shared::Message msg = archive->newest();
emit needHistory("", msg.getId(), msg.getTime());
try {
Shared::Message msg = archive->newest();
emit needHistory("", getId(msg), msg.getTime());
} catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example)
emit needHistory(before, "");
}
}
break;
case end:
@ -140,39 +196,49 @@ void Core::RosterItem::performRequest(int count, const QString& before)
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), lBefore);
responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
found = true;
} catch (Archive::NotFound e) {
} catch (const Archive::NotFound& e) {
requestCache.emplace_back(requestedCount, before);
requestedCount = -1;
emit needHistory(archive->oldestId(), "");
} catch (Archive::Empty e) {
emit needHistory(getId(archive->oldest()), "");
} catch (const Archive::Empty& e) {
requestCache.emplace_back(requestedCount, before);
requestedCount = -1;
emit needHistory(archive->oldestId(), "");
emit needHistory(getId(archive->oldest()), "");
}
if (found) {
int rSize = responseCache.size();
if (rSize < count) {
if (rSize != 0) {
emit needHistory(responseCache.front().getId(), "");
emit needHistory(getId(responseCache.front()), "");
} else {
emit needHistory(before, "");
QString bf;
if (muc) {
bf = archive->stanzaIdById(before);
if (bf.size() < 0) {
qDebug() << "Didn't find stanzaId for id requesting history for" << jid << ", falling back to requesting by id";
bf = before;
}
} else {
bf = before;
}
emit needHistory(bf, "");
}
} else {
nextRequest();
}
}
} else {
emit needHistory(archive->oldestId(), "");
emit needHistory(getId(archive->oldest()), "");
}
break;
case complete:
try {
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
} catch (Archive::NotFound e) {
} catch (const Archive::NotFound& e) {
qDebug("requesting id hasn't been found in archive, skipping");
} catch (Archive::Empty e) {
} catch (const Archive::Empty& e) {
qDebug("requesting id hasn't been found in archive, skipping");
}
nextRequest();
@ -180,10 +246,20 @@ void Core::RosterItem::performRequest(int count, const QString& before)
}
}
QString Core::RosterItem::getId(const Shared::Message& msg)
{
QString id;
if (muc) {
id = msg.getStanzaId();
} else {
id = msg.getId();
}
return id;
}
void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
{
const QString& id = msg.getId();
if (id.size() > 0) {
if (msg.getId().size() > 0) {
if (msg.storable()) {
switch (archiveState) {
case empty:
@ -191,22 +267,26 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
archiveState = end;
}
if (!syncronizing) {
requestHistory(-1, id);
requestHistory(-1, getId(msg));
}
break;
case beginning:
appendCache.push_back(msg);
if (!syncronizing) {
requestHistory(-1, id);
if (!archive->hasElement(msg.getId())) {
appendCache.push_back(msg);
if (!syncronizing) {
requestHistory(-1, getId(msg));
}
}
break;
case end:
archive->addElement(msg);
break;
case chunk:
appendCache.push_back(msg);
if (!syncronizing) {
requestHistory(-1, id);
if (!archive->hasElement(msg.getId())) {
appendCache.push_back(msg);
if (!syncronizing) {
requestHistory(-1, getId(msg));
}
}
break;
case complete:
@ -214,11 +294,53 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
break;
}
} else if (!syncronizing && archiveState == empty) {
requestHistory(-1, id);
requestHistory(-1, getId(msg));
}
}
}
bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
{
bool found = false;
for (Shared::Message& msg : appendCache) {
if (msg.getId() == id) {
msg.change(data);
found = true;
break;
}
}
if (!found) {
for (Shared::Message& msg : hisoryCache) {
if (msg.getId() == id) {
msg.change(data);
found = true;
break;
}
}
}
if (!found) {
try {
archive->changeMessage(id, data);
found = true;
} catch (const Archive::NotFound& e) {
qDebug() << "An attempt to change state to the message" << id << "but it couldn't be found";
}
}
if (found) {
for (Shared::Message& msg : responseCache) {
if (msg.getId() == id) {
msg.change(data);
break;
}
}
}
return found;
}
void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId)
{
unsigned int added(0);
@ -253,6 +375,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
case empty:
wasEmpty = true;
archiveState = end;
[[fallthrough]];
case end:
added += archive->addElements(appendCache);
appendCache.clear();
@ -260,6 +383,11 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
archiveState = complete;
archive->setFromTheBeginning(true);
}
if (added == 0 && wasEmpty) {
archiveState = empty;
nextRequest();
break;
}
if (requestedCount != -1) {
QString before;
if (responseCache.size() > 0) {
@ -273,12 +401,12 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
found = true;
} catch (Archive::NotFound e) {
} catch (const Archive::NotFound& e) {
} catch (Archive::Empty e) {
} catch (const Archive::Empty& e) {
}
if (!found || requestedCount > responseCache.size()) {
if (!found || requestedCount > int(responseCache.size())) {
if (archiveState == complete) {
nextRequest();
} else {
@ -301,26 +429,6 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
}
}
void Core::RosterItem::requestFromEmpty(int count, const QString& before)
{
if (syncronizing) {
qDebug("perform from empty didn't work, another request queued");
} else {
if (archiveState != empty) {
qDebug("perform from empty didn't work, the state is not empty");
requestHistory(count, before);
} else {
syncronizing = true;
requestedCount = count;
requestedBefore = "";
hisoryCache.clear();
responseCache.clear();
emit needHistory(before, "");
}
}
}
QString Core::RosterItem::getServer() const
{
QStringList lst = jid.split("@");
@ -331,3 +439,158 @@ bool Core::RosterItem::isMuc() const
{
return muc;
}
QString Core::RosterItem::avatarPath(const QString& resource) const
{
QString path = folderPath() + "/" + (resource.size() == 0 ? jid : resource);
return path;
}
QString Core::RosterItem::folderPath() const
{
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + account + "/" + jid;
return path;
}
bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
{
bool result = archive->setAvatar(data, info, false, resource);
if (resource.size() == 0 && result) {
if (data.size() == 0) {
emit avatarChanged(Shared::Avatar::empty, "");
} else {
emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + info.type);
}
}
return result;
}
bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource)
{
Archive::AvatarInfo info;
return setAutoGeneratedAvatar(info, resource);
}
bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource)
{
QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
QPainter painter(&image);
quint8 colorIndex = rand() % Shared::colorPalette.size();
const QColor& bg = Shared::colorPalette[colorIndex];
painter.fillRect(image.rect(), bg);
QFont f;
f.setBold(true);
f.setPixelSize(72);
painter.setFont(f);
if (bg.lightnessF() > 0.5) {
painter.setPen(Qt::black);
} else {
painter.setPen(Qt::white);
}
painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, resource.size() == 0 ? jid.at(0).toUpper() : resource.at(0).toUpper());
QByteArray arr;
QBuffer stream(&arr);
stream.open(QBuffer::WriteOnly);
image.save(&stream, "PNG");
stream.close();
bool result = archive->setAvatar(arr, info, true, resource);
if (resource.size() == 0 && result) {
emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png");
}
return result;
}
bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const
{
return archive->readAvatarInfo(target, resource);
}
Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource)
{
Archive::AvatarInfo info;
Archive::AvatarInfo newInfo;
bool hasAvatar = readAvatarInfo(info, resource);
QByteArray ava = card.photo();
Shared::VCard vCard;
initializeVCard(vCard, card);
Shared::Avatar type = Shared::Avatar::empty;
QString path = "";
if (ava.size() > 0) {
bool changed = setAvatar(ava, newInfo, resource);
if (changed) {
type = Shared::Avatar::valid;
path = avatarPath(resource) + "." + newInfo.type;
} else if (hasAvatar) {
if (info.autogenerated) {
type = Shared::Avatar::autocreated;
path = avatarPath(resource) + ".png";
} else {
type = Shared::Avatar::valid;
path = avatarPath(resource) + "." + info.type;
}
}
} else {
if (!hasAvatar || !info.autogenerated) {
setAutoGeneratedAvatar(resource);
}
type = Shared::Avatar::autocreated;
path = avatarPath(resource) + ".png";
}
vCard.setAvatarType(type);
vCard.setAvatarPath(path);
if (resource.size() == 0) {
emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath());
}
return vCard;
}
void Core::RosterItem::clearArchiveRequests()
{
syncronizing = false;
requestedCount = 0;
requestedBefore = "";
for (const std::pair<int, QString>& pair : requestCache) {
if (pair.first != -1) {
emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet
}
responseCache.clear();
}
hisoryCache.clear();
responseCache.clear(); //in case the cycle never runned
appendCache.clear();
requestCache.clear();
}
void Core::RosterItem::downgradeDatabaseState()
{
if (archiveState == ArchiveState::complete) {
archiveState = ArchiveState::beginning;
}
if (archiveState == ArchiveState::end) {
archiveState = ArchiveState::chunk;
}
}
Shared::Message Core::RosterItem::getMessage(const QString& id)
{
for (const Shared::Message& msg : appendCache) {
if (msg.getId() == id) {
return msg;
}
}
for (Shared::Message& msg : hisoryCache) {
if (msg.getId() == id) {
return msg;
}
}
return archive->getElement(id);
}

View File

@ -21,11 +21,21 @@
#include <QObject>
#include <QString>
#include <QStandardPaths>
#include <QImage>
#include <QPainter>
#include <QBuffer>
#include <list>
#include "../global.h"
#include "archive.h"
#include <QXmppVCardIq.h>
#include <QXmppPresence.h>
#include "shared/enums.h"
#include "shared/message.h"
#include "shared/vcard.h"
#include "storage/archive.h"
#include "adapterfunctions.h"
namespace Core {
@ -54,19 +64,38 @@ public:
bool isMuc() const;
void addMessageToArchive(const Shared::Message& msg);
void correctMessageInArchive(const QString& originalId, const Shared::Message& msg);
void appendMessageToArchive(const Shared::Message& msg);
void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId);
void requestHistory(int count, const QString& before);
void requestFromEmpty(int count, const QString& before);
QString avatarPath(const QString& resource = "") const;
QString folderPath() const;
bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
virtual bool setAutoGeneratedAvatar(const QString& resource = "");
virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
virtual void handlePresence(const QXmppPresence& pres) = 0;
bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
void clearArchiveRequests();
void downgradeDatabaseState();
Shared::Message getMessage(const QString& id);
signals:
void nameChanged(const QString& name);
void subscriptionStateChanged(Shared::SubscriptionState state);
void historyResponse(const std::list<Shared::Message>& messages);
void historyResponse(const std::list<Shared::Message>& messages, bool last);
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
void avatarChanged(Shared::Avatar, const QString& path);
void requestVCard(const QString& jid);
public:
const QString jid;
const QString account;
protected:
virtual bool setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource = "");
virtual bool setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource = "");
protected:
QString name;
@ -80,11 +109,13 @@ protected:
std::list<Shared::Message> appendCache;
std::list<Shared::Message> responseCache;
std::list<std::pair<int, QString>> requestCache;
std::map<QString, Shared::Message> toCorrect;
bool muc;
private:
void nextRequest();
void performRequest(int count, const QString& before);
QString getId(const Shared::Message& msg);
};
}

View File

@ -38,7 +38,7 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
}
snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
connect(snInt, SIGNAL(activated(int)), this, SLOT(handleSigInt()));
connect(snInt, &QSocketNotifier::activated, this, &SignalCatcher::handleSigInt);
}
SignalCatcher::~SignalCatcher()
@ -48,9 +48,9 @@ void SignalCatcher::handleSigInt()
{
snInt->setEnabled(false);
char tmp;
::read(sigintFd[1], &tmp, sizeof(tmp));
ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
app->quit();
emit interrupt();
snInt->setEnabled(true);
}
@ -58,7 +58,7 @@ void SignalCatcher::handleSigInt()
void SignalCatcher::intSignalHandler(int unused)
{
char a = 1;
::write(sigintFd[0], &a, sizeof(a));
ssize_t s = ::write(sigintFd[0], &a, sizeof(a));
}
int SignalCatcher::setup_unix_signal_handlers()

View File

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

View File

@ -0,0 +1,42 @@
/*
* Squawk messenger.
* Copyright (C) 2021 Shunf4 <shun1048576@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "signalcatcher.h"
#include <unistd.h>
SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
QObject(parent),
app(p_app)
{
}
SignalCatcher::~SignalCatcher()
{}
void SignalCatcher::handleSigInt()
{
}
void SignalCatcher::intSignalHandler(int unused)
{
}
int SignalCatcher::setup_unix_signal_handlers()
{
return 0;
}

View File

@ -26,11 +26,27 @@ Core::Squawk::Squawk(QObject* parent):
QObject(parent),
accounts(),
amap(),
network()
state(Shared::Availability::offline),
network(),
isInitialized(false)
#ifdef WITH_KWALLET
,kwallet()
#endif
{
connect(&network, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), this, SIGNAL(fileLocalPathResponse(const QString&, const QString&)));
connect(&network, SIGNAL(downloadFileProgress(const QString&, qreal)), this, SIGNAL(downloadFileProgress(const QString&, qreal)));
connect(&network, SIGNAL(downloadFileError(const QString&, const QString&)), this, SIGNAL(downloadFileError(const QString&, const QString&)));
connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
#ifdef WITH_KWALLET
if (kwallet.supportState() == PSE::KWallet::success) {
connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword);
Shared::Global::setSupported("KWallet", true);
}
#endif
}
Core::Squawk::~Squawk()
@ -42,26 +58,52 @@ Core::Squawk::~Squawk()
}
}
void Core::Squawk::onWalletOpened(bool success)
{
qDebug() << "KWallet opened: " << success;
}
void Core::Squawk::stop()
{
qDebug("Stopping squawk core..");
network.stop();
QSettings settings;
settings.beginGroup("core");
settings.beginWriteArray("accounts");
for (int i = 0; i < accounts.size(); ++i) {
settings.setArrayIndex(i);
Account* acc = accounts[i];
settings.setValue("name", acc->getName());
settings.setValue("server", acc->getServer());
settings.setValue("login", acc->getLogin());
settings.setValue("password", acc->getPassword());
settings.setValue("resource", acc->getResource());
if (isInitialized) {
QSettings settings;
settings.beginGroup("core");
settings.beginWriteArray("accounts");
SimpleCrypt crypto(passwordHash);
for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
settings.setArrayIndex(i);
Account* acc = accounts[i];
Shared::AccountPassword ap = acc->getPasswordType();
QString password;
switch (ap) {
case Shared::AccountPassword::plain:
password = acc->getPassword();
break;
case Shared::AccountPassword::jammed:
password = crypto.encryptToString(acc->getPassword());
break;
default:
break;
}
settings.setValue("name", acc->getName());
settings.setValue("server", acc->getServer());
settings.setValue("login", acc->getLogin());
settings.setValue("password", password);
settings.setValue("resource", acc->getResource());
settings.setValue("passwordType", static_cast<int>(ap));
settings.setValue("active", acc->getActive());
}
settings.endArray();
settings.endGroup();
settings.sync();
}
settings.endArray();
settings.endGroup();
settings.sync();
emit quit();
}
@ -70,21 +112,8 @@ void Core::Squawk::start()
{
qDebug("Starting squawk core..");
QSettings settings;
settings.beginGroup("core");
int size = settings.beginReadArray("accounts");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
addAccount(
settings.value("login").toString(),
settings.value("server").toString(),
settings.value("password").toString(),
settings.value("name").toString(),
settings.value("resource").toString()
);
}
settings.endArray();
settings.endGroup();
readSettings();
isInitialized = true;
network.start();
}
@ -95,52 +124,62 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
QString server = map.value("server").toString();
QString password = map.value("password").toString();
QString resource = map.value("resource").toString();
int passwordType = map.value("passwordType").toInt();
bool active = map.value("active").toBool();
addAccount(login, server, password, name, resource);
addAccount(login, server, password, name, resource, active, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
}
void Core::Squawk::addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource)
void Core::Squawk::addAccount(
const QString& login,
const QString& server,
const QString& password,
const QString& name,
const QString& resource,
bool active,
Shared::AccountPassword passwordType)
{
QSettings settings;
unsigned int reconnects = settings.value("reconnects", 2).toUInt();
Account* acc = new Account(login, server, password, name);
if (amap.count(name) > 0) {
qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring";
return;
}
Account* acc = new Account(login, server, password, name, active, &network);
acc->setResource(resource);
acc->setReconnectTimes(reconnects);
acc->setPasswordType(passwordType);
accounts.push_back(acc);
amap.insert(std::make_pair(name, acc));
connect(acc, SIGNAL(connectionStateChanged(int)), this, SLOT(onAccountConnectionStateChanged(int)));
connect(acc, SIGNAL(error(const QString&)), this, SLOT(onAccountError(const QString&)));
connect(acc, SIGNAL(availabilityChanged(int)), this, SLOT(onAccountAvailabilityChanged(int)));
connect(acc, SIGNAL(addContact(const QString&, const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onAccountAddContact(const QString&, const QString&, const QMap<QString, QVariant>&)));
connect(acc, SIGNAL(addGroup(const QString&)), this, SLOT(onAccountAddGroup(const QString&)));
connect(acc, SIGNAL(removeGroup(const QString&)), this, SLOT(onAccountRemoveGroup(const QString&)));
connect(acc, SIGNAL(removeContact(const QString&)), this, SLOT(onAccountRemoveContact(const QString&)));
connect(acc, SIGNAL(removeContact(const QString&, const QString&)), this, SLOT(onAccountRemoveContact(const QString&, const QString&)));
connect(acc, SIGNAL(changeContact(const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onAccountChangeContact(const QString&, const QMap<QString, QVariant>&)));
connect(acc, SIGNAL(addPresence(const QString&, const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onAccountAddPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
connect(acc, SIGNAL(removePresence(const QString&, const QString&)), this, SLOT(onAccountRemovePresence(const QString&, const QString&)));
connect(acc, SIGNAL(message(const Shared::Message&)), this, SLOT(onAccountMessage(const Shared::Message&)));
connect(acc, SIGNAL(responseArchive(const QString&, const std::list<Shared::Message>&)),
this, SLOT(onAccountResponseArchive(const QString&, const std::list<Shared::Message>&)));
connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
connect(acc, &Account::error, this, &Squawk::onAccountError);
connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword);
connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
connect(acc, &Account::removeGroup, this, &Squawk::onAccountRemoveGroup);
connect(acc, qOverload<const QString&, const QString&>(&Account::removeContact),
this, qOverload<const QString&, const QString&>(&Squawk::onAccountRemoveContact));
connect(acc, qOverload<const QString&>(&Account::removeContact),
this, qOverload<const QString&>(&Squawk::onAccountRemoveContact));
connect(acc, &Account::changeContact, this, &Squawk::onAccountChangeContact);
connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence);
connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence);
connect(acc, &Account::message, this, &Squawk::onAccountMessage);
connect(acc, &Account::changeMessage, this, &Squawk::onAccountChangeMessage);
connect(acc, &Account::responseArchive, this, &Squawk::onAccountResponseArchive);
connect(acc, &Account::addRoom, this, &Squawk::onAccountAddRoom);
connect(acc, &Account::changeRoom, this, &Squawk::onAccountChangeRoom);
connect(acc, &Account::removeRoom, this, &Squawk::onAccountRemoveRoom);
connect(acc, SIGNAL(addRoom(const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onAccountAddRoom(const QString&, const QMap<QString, QVariant>&)));
connect(acc, SIGNAL(changeRoom(const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onAccountChangeRoom(const QString&, const QMap<QString, QVariant>&)));
connect(acc, SIGNAL(removeRoom(const QString&)), this, SLOT(onAccountRemoveRoom(const QString&)));
connect(acc, &Account::addRoomParticipant, this, &Squawk::onAccountAddRoomPresence);
connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence);
connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence);
connect(acc, SIGNAL(addRoomParticipant(const QString&, const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onAccountAddRoomPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
connect(acc, SIGNAL(changeRoomParticipant(const QString&, const QString&, const QMap<QString, QVariant>&)),
this, SLOT(onAccountChangeRoomPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
connect(acc, SIGNAL(removeRoomParticipant(const QString&, const QString&)),
this, SLOT(onAccountRemoveRoomPresence(const QString&, const QString&)));
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
QMap<QString, QVariant> map = {
{"login", login},
@ -148,24 +187,48 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
{"name", name},
{"password", password},
{"resource", resource},
{"state", Shared::disconnected},
{"offline", Shared::offline},
{"error", ""}
{"state", QVariant::fromValue(Shared::ConnectionState::disconnected)},
{"offline", QVariant::fromValue(Shared::Availability::offline)},
{"error", ""},
{"avatarPath", acc->getAvatarPath()},
{"passwordType", QVariant::fromValue(passwordType)},
{"active", active}
};
emit newAccount(map);
switch (passwordType) {
case Shared::AccountPassword::alwaysAsk:
case Shared::AccountPassword::kwallet:
if (password == "") {
acc->invalidatePassword();
break;
}
default:
break;
}
if (state != Shared::Availability::offline) {
acc->setAvailability(state);
if (acc->getActive()) {
acc->connect();
}
}
}
void Core::Squawk::changeState(int p_state)
void Core::Squawk::changeState(Shared::Availability p_state)
{
Shared::Availability avail;
if (p_state < Shared::availabilityLowest && p_state > Shared::availabilityHighest) {
qDebug("An attempt to set invalid availability to Squawk core, skipping");
}
avail = static_cast<Shared::Availability>(p_state);
state = avail;
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
(*itr)->setAvailability(state);
if (state != p_state) {
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
Account* acc = *itr;
acc->setAvailability(p_state);
if (state == Shared::Availability::offline && acc->getActive()) {
acc->connect();
}
}
state = p_state;
emit stateChanged(p_state);
}
}
@ -176,7 +239,10 @@ void Core::Squawk::connectAccount(const QString& account)
qDebug("An attempt to connect non existing account, skipping");
return;
}
itr->second->connect();
itr->second->setActive(true);
if (state != Shared::Availability::offline) {
itr->second->connect();
}
}
void Core::Squawk::disconnectAccount(const QString& account)
@ -187,26 +253,25 @@ void Core::Squawk::disconnectAccount(const QString& account)
return;
}
itr->second->setActive(false);
itr->second->disconnect();
}
void Core::Squawk::onAccountConnectionStateChanged(int state)
void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
{
Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), {{"state", state}});
if (state == Shared::disconnected) {
bool equals = true;
for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) {
if ((*itr)->getState() != Shared::disconnected) {
equals = false;
}
}
if (equals) {
state = Shared::offline;
emit stateChanged(state);
emit changeAccount(acc->getName(), {
{"state", QVariant::fromValue(p_state)},
{"error", ""}
});
#ifdef WITH_KWALLET
if (p_state == Shared::ConnectionState::connected) {
if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
}
}
#endif
}
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
@ -257,10 +322,16 @@ void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& na
emit removePresence(acc->getName(), jid, name);
}
void Core::Squawk::onAccountAvailabilityChanged(int state)
void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state)
{
Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), {{"availability", state}});
emit changeAccount(acc->getName(), {{"availability", QVariant::fromValue(state)}});
}
void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data)
{
Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), data);
}
void Core::Squawk::onAccountMessage(const Shared::Message& data)
@ -273,13 +344,35 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug("An attempt to send a message with non existing account, skipping");
qDebug() << "An attempt to send a message with non existing account" << account << ", skipping";
return;
}
itr->second->sendMessage(data);
}
void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping";
return;
}
itr->second->replaceMessage(originalId, data);
}
void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping";
return;
}
itr->second->resendMessage(jid, id);
}
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
{
AccountsMap::const_iterator itr = amap.find(account);
@ -290,10 +383,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
itr->second->requestArchive(jid, count, before);
}
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list)
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
{
Account* acc = static_cast<Account*>(sender());
emit responseArchive(acc->getName(), jid, list);
emit responseArchive(acc->getName(), jid, list, last);
}
void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
@ -306,12 +399,46 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
Core::Account* acc = itr->second;
Shared::ConnectionState st = acc->getState();
QMap<QString, QVariant>::const_iterator mItr;
bool needToReconnect = false;
bool wentReconnecting = false;
if (st != Shared::disconnected) {
acc->reconnect();
mItr = map.find("login");
if (mItr != map.end()) {
needToReconnect = acc->getLogin() != mItr->toString();
}
if (!needToReconnect) {
mItr = map.find("password");
if (mItr != map.end()) {
needToReconnect = acc->getPassword() != mItr->toString();
}
}
if (!needToReconnect) {
mItr = map.find("server");
if (mItr != map.end()) {
needToReconnect = acc->getServer() != mItr->toString();
}
}
if (!needToReconnect) {
mItr = map.find("resource");
if (mItr != map.end()) {
needToReconnect = acc->getResource() != mItr->toString();
}
}
bool activeChanged = false;
mItr = map.find("active");
if (mItr == map.end() || mItr->toBool() == acc->getActive()) {
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
acc->reconnect();
wentReconnecting = true;
}
} else {
acc->setActive(mItr->toBool());
activeChanged = true;
}
QMap<QString, QVariant>::const_iterator mItr;
mItr = map.find("login");
if (mItr != map.end()) {
acc->setLogin(mItr->toString());
@ -332,6 +459,28 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
acc->setServer(mItr->toString());
}
mItr = map.find("passwordType");
if (mItr != map.end()) {
acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt()));
}
#ifdef WITH_KWALLET
if (acc->getPasswordType() == Shared::AccountPassword::kwallet
&& kwallet.supportState() == PSE::KWallet::success
&& !needToReconnect
) {
kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
}
#endif
if (state != Shared::Availability::offline) {
if (activeChanged && acc->getActive()) {
acc->connect();
} else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) {
acc->connect();
}
}
emit changeAccount(name, map);
}
@ -339,6 +488,10 @@ void Core::Squawk::onAccountError(const QString& text)
{
Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), {{"error", text}});
if (acc->getLastError() == Account::Error::authentication) {
emit requestPassword(acc->getName(), true);
}
}
void Core::Squawk::removeAccountRequest(const QString& name)
@ -350,7 +503,7 @@ void Core::Squawk::removeAccountRequest(const QString& name)
}
Account* acc = itr->second;
if (acc->getState() != Shared::disconnected) {
if (acc->getState() != Shared::ConnectionState::disconnected) {
acc->disconnect();
}
@ -372,7 +525,6 @@ void Core::Squawk::removeAccountRequest(const QString& name)
acc->deleteLater();
}
void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason)
{
AccountsMap::const_iterator itr = amap.find(account);
@ -473,6 +625,12 @@ void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString
emit removeRoomParticipant(acc->getName(), jid, nick);
}
void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
{
Account* acc = static_cast<Account*>(sender());
emit changeMessage(acc->getName(), jid, id, data);
}
void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid)
{
AccountsMap::const_iterator itr = amap.find(account);
@ -493,21 +651,16 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
itr->second->addRoomRequest(jid, nick, password, autoJoin);
}
void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url)
void Core::Squawk::fileDownloadRequest(const QString& url)
{
network.fileLocalPathRequest(messageId, url);
}
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
{
network.downladFileRequest(messageId, url);
network.downladFile(url);
}
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping";
qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
return;
}
itr->second->addContactToGroupRequest(jid, groupName);
@ -517,7 +670,7 @@ void Core::Squawk::removeContactFromGroupRequest(const QString& account, const Q
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping";
qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
return;
}
itr->second->removeContactFromGroupRequest(jid, groupName);
@ -527,8 +680,136 @@ void Core::Squawk::renameContactRequest(const QString& account, const QString& j
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to rename contact" << jid << "of existing account" << account << ", skipping";
qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping";
return;
}
itr->second->renameContactRequest(jid, newName);
}
void Core::Squawk::requestVCard(const QString& account, const QString& jid)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to request" << jid << "vcard of non existing account" << account << ", skipping";
return;
}
itr->second->requestVCard(jid);
}
void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to upload vcard to non existing account" << account << ", skipping";
return;
}
itr->second->uploadVCard(card);
}
void Core::Squawk::readSettings()
{
QSettings settings;
settings.beginGroup("core");
int size = settings.beginReadArray("accounts");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
Shared::AccountPassword passwordType =
Shared::Global::fromInt<Shared::AccountPassword>(
settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt()
);
QString password = settings.value("password", "").toString();
if (passwordType == Shared::AccountPassword::jammed) {
SimpleCrypt crypto(passwordHash);
password = crypto.decryptToString(password);
}
addAccount(
settings.value("login").toString(),
settings.value("server").toString(),
password,
settings.value("name").toString(),
settings.value("resource").toString(),
settings.value("active").toBool(),
passwordType
);
}
settings.endArray();
settings.endGroup();
qDebug() << "Squawk core is ready";
emit ready();
}
void Core::Squawk::onAccountNeedPassword()
{
Account* acc = static_cast<Account*>(sender());
switch (acc->getPasswordType()) {
case Shared::AccountPassword::alwaysAsk:
emit requestPassword(acc->getName(), false);
break;
case Shared::AccountPassword::kwallet: {
#ifdef WITH_KWALLET
if (kwallet.supportState() == PSE::KWallet::success) {
kwallet.requestReadPassword(acc->getName());
} else {
#endif
emit requestPassword(acc->getName(), false);
#ifdef WITH_KWALLET
}
#endif
break;
}
default:
break; //should never happen;
}
}
void Core::Squawk::onWalletRejectPassword(const QString& login)
{
emit requestPassword(login, false);
}
void Core::Squawk::responsePassword(const QString& account, const QString& password)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
return;
}
Account* acc = itr->second;
acc->setPassword(password);
emit changeAccount(account, {{"password", password}});
if (state != Shared::Availability::offline && acc->getActive()) {
acc->connect();
}
}
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
{
Account* acc = static_cast<Account*>(sender());
emit fileError({{acc->getName(), jid, id}}, errorText, true);
}
void Core::Squawk::onLocalPathInvalid(const QString& path)
{
std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
QMap<QString, QVariant> data({
{"attachPath", ""}
});
for (const Shared::MessageInfo& info : list) {
AccountsMap::const_iterator itr = amap.find(info.account);
if (itr != amap.end()) {
itr->second->requestChangeMessage(info.jid, info.messageId, data);
} else {
qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
}
}
}
void Core::Squawk::changeDownloadsPath(const QString& path)
{
network.moveFilesDirectory(path);
}

View File

@ -23,12 +23,20 @@
#include <QString>
#include <QVariant>
#include <QMap>
#include <deque>
#include <QtGlobal>
#include <deque>
#include "account.h"
#include "../global.h"
#include "shared/enums.h"
#include "shared/message.h"
#include "shared/global.h"
#include "networkaccess.h"
#include "external/simpleCrypt/simplecrypt.h"
#ifdef WITH_KWALLET
#include "passwordStorageEngines/kwallet.h"
#endif
namespace Core
{
@ -42,41 +50,61 @@ public:
signals:
void quit();
void ready();
void newAccount(const QMap<QString, QVariant>&);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account);
void addGroup(const QString& account, const QString& name);
void removeGroup(const QString& account, const QString& name);
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
void removeContact(const QString& account, const QString& jid);
void removeContact(const QString& account, const QString& jid, const QString& group);
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removePresence(const QString& account, const QString& jid, const QString& name);
void stateChanged(int state);
void stateChanged(Shared::Availability state);
void accountMessage(const QString& account, const Shared::Message& data);
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
void removeRoom(const QString& account, const QString jid);
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
void fileLocalPathResponse(const QString& messageId, const QString& path);
void downloadFileError(const QString& messageId, const QString& error);
void downloadFileProgress(const QString& messageId, qreal value);
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
void responseVCard(const QString& jid, const Shared::VCard& card);
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void requestPassword(const QString& account, bool authernticationError);
public slots:
void start();
void stop();
void newAccountRequest(const QMap<QString, QVariant>& map);
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
void removeAccountRequest(const QString& name);
void connectAccount(const QString& account);
void disconnectAccount(const QString& account);
void changeState(int state);
void changeState(Shared::Availability state);
void sendMessage(const QString& account, const Shared::Message& data);
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
void resendMessage(const QString& account, const QString& jid, const QString& id);
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
@ -84,12 +112,19 @@ public slots:
void removeContactRequest(const QString& account, const QString& jid);
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
void setRoomJoined(const QString& account, const QString& jid, bool joined);
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void removeRoomRequest(const QString& account, const QString& jid);
void fileLocalPathRequest(const QString& messageId, const QString& url);
void downloadFileRequest(const QString& messageId, const QString& url);
void fileDownloadRequest(const QString& url);
void requestVCard(const QString& account, const QString& jid);
void uploadVCard(const QString& account, const Shared::VCard& card);
void responsePassword(const QString& account, const QString& password);
void onLocalPathInvalid(const QString& path);
void changeDownloadsPath(const QString& path);
private:
typedef std::deque<Account*> Accounts;
@ -99,13 +134,26 @@ private:
AccountsMap amap;
Shared::Availability state;
NetworkAccess network;
private:
void addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource);
bool isInitialized;
#ifdef WITH_KWALLET
PSE::KWallet kwallet;
#endif
private slots:
void onAccountConnectionStateChanged(int state);
void onAccountAvailabilityChanged(int state);
void addAccount(
const QString& login,
const QString& server,
const QString& password,
const QString& name,
const QString& resource,
bool active,
Shared::AccountPassword passwordType
);
void onAccountConnectionStateChanged(Shared::ConnectionState state);
void onAccountAvailabilityChanged(Shared::Availability state);
void onAccountChanged(const QMap<QString, QVariant>& data);
void onAccountAddGroup(const QString& name);
void onAccountError(const QString& text);
void onAccountRemoveGroup(const QString& name);
@ -116,13 +164,34 @@ private slots:
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void onAccountRemovePresence(const QString& jid, const QString& name);
void onAccountMessage(const Shared::Message& data);
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list);
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
void onAccountRemoveRoom(const QString jid);
void onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void onAccountNeedPassword();
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
void onWalletOpened(bool success);
void onWalletRejectPassword(const QString& login);
private:
void readSettings();
void parseAccount(
const QString& login,
const QString& server,
const QString& password,
const QString& name,
const QString& resource,
bool active,
Shared::AccountPassword passwordType
);
static const quint64 passwordHash = 0x08d054225ac4871d;
};
}

View File

@ -0,0 +1,8 @@
target_sources(squawk PRIVATE
archive.cpp
archive.h
storage.cpp
storage.h
urlstorage.cpp
urlstorage.h
)

1007
core/storage/archive.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -20,9 +20,13 @@
#define CORE_ARCHIVE_H
#include <QObject>
#include "../global.h"
#include <QCryptographicHash>
#include <QMimeDatabase>
#include <QMimeType>
#include "shared/message.h"
#include "shared/exception.h"
#include <lmdb.h>
#include "../exception.h"
#include <list>
namespace Core {
@ -31,6 +35,8 @@ class Archive : public QObject
{
Q_OBJECT
public:
class AvatarInfo;
Archive(const QString& jid, QObject* parent = 0);
~Archive();
@ -39,7 +45,9 @@ public:
bool addElement(const Shared::Message& message);
unsigned int addElements(const std::list<Shared::Message>& messages);
Shared::Message getElement(const QString& id);
Shared::Message getElement(const QString& id) const;
bool hasElement(const QString& id) const;
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
Shared::Message oldest();
QString oldestId();
Shared::Message newest();
@ -49,6 +57,12 @@ public:
std::list<Shared::Message> getBefore(int count, const QString& id);
bool isFromTheBeginning();
void setFromTheBeginning(bool is);
bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
AvatarInfo getAvatarInfo(const QString& resource = "") const;
bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
QString idByStanzaId(const QString& stanzaId) const;
QString stanzaIdById(const QString& id) const;
public:
const QString jid;
@ -112,6 +126,22 @@ public:
std::string key;
};
class NoAvatar:
public Utils::Exception
{
public:
NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){
if (resource.size() == 0) {
resource = "for himself";
}
}
std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;}
private:
std::string element;
std::string resource;
};
class Unknown:
public Utils::Exception
{
@ -124,17 +154,42 @@ public:
std::string msg;
};
class AvatarInfo {
public:
AvatarInfo();
AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
void deserialize(char* pointer, uint32_t size);
void serialize(QByteArray* ba) const;
QString type;
QByteArray hash;
bool autogenerated;
};
private:
bool opened;
bool fromTheBeginning;
MDB_env* environment;
MDB_dbi main;
MDB_dbi order;
MDB_dbi main; //id to message
MDB_dbi order; //time to id
MDB_dbi stats;
MDB_dbi avatars;
MDB_dbi sid; //stanzaId to id
bool _isFromTheBeginning();
bool getStatBoolValue(const std::string& id, MDB_txn* txn);
std::string getStatStringValue(const std::string& id, MDB_txn* txn);
bool setStatValue(const std::string& id, bool value, MDB_txn* txn);
bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
bool readAvatarInfo(AvatarInfo& target, const std::string& res, MDB_txn* txn) const;
void printOrder();
void printKeys();
bool dropAvatar(const std::string& resource);
Shared::Message getMessage(const std::string& id, MDB_txn* txn) const;
Shared::Message getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc);
Shared::Message edge(bool end);
};
}

View File

@ -73,7 +73,7 @@ void Core::Storage::close()
void Core::Storage::addRecord(const QString& key, const QString& value)
{
if (!opened) {
throw Archive::Closed("addElement", name.toStdString());
throw Archive::Closed("addRecord", name.toStdString());
}
const std::string& id = key.toStdString();
const std::string& val = value.toStdString();
@ -99,6 +99,33 @@ void Core::Storage::addRecord(const QString& key, const QString& value)
}
}
void Core::Storage::changeRecord(const QString& key, const QString& value)
{
if (!opened) {
throw Archive::Closed("changeRecord", name.toStdString());
}
const std::string& id = key.toStdString();
const std::string& val = value.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = val.size();
lmdbData.mv_data = (char*)val.c_str();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
int rc;
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0);
if (rc != 0) {
mdb_txn_abort(txn);
if (rc) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
} else {
mdb_txn_commit(txn);
}
}
QString Core::Storage::getRecord(const QString& key) const
{
if (!opened) {

View File

@ -39,6 +39,7 @@ public:
void close();
void addRecord(const QString& key, const QString& value);
void changeRecord(const QString& key, const QString& value);
void removeRecord(const QString& key);
QString getRecord(const QString& key) const;

491
core/storage/urlstorage.cpp Normal file
View File

@ -0,0 +1,491 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include "urlstorage.h"
Core::UrlStorage::UrlStorage(const QString& p_name):
name(p_name),
opened(false),
environment(),
base(),
map()
{
}
Core::UrlStorage::~UrlStorage()
{
close();
}
void Core::UrlStorage::open()
{
if (!opened) {
mdb_env_create(&environment);
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + name;
QDir cache(path);
if (!cache.exists()) {
bool res = cache.mkpath(path);
if (!res) {
throw Archive::Directory(path.toStdString());
}
}
mdb_env_set_maxdbs(environment, 2);
mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
mdb_dbi_open(txn, "base", MDB_CREATE, &base);
mdb_dbi_open(txn, "map", MDB_CREATE, &map);
mdb_txn_commit(txn);
opened = true;
}
}
void Core::UrlStorage::close()
{
if (opened) {
mdb_dbi_close(environment, map);
mdb_dbi_close(environment, base);
mdb_env_close(environment);
opened = false;
}
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite)
{
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
try {
writeInfo(key, info, txn, overwrite);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite)
{
QByteArray ba;
QDataStream ds(&ba, QIODevice::WriteOnly);
info.serialize(ds);
const std::string& id = key.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
lmdbData.mv_size = ba.size();
lmdbData.mv_data = (uint8_t*)ba.data();
int rc;
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
if (rc != 0) {
if (rc == MDB_KEYEXIST) {
if (!overwrite) {
throw Archive::Exist(name.toStdString(), id);
}
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
if (info.hasPath()) {
std::string sp = info.getPath().toStdString();
lmdbData.mv_size = sp.size();
lmdbData.mv_data = (char*)sp.c_str();
rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
}
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn)
{
const std::string& id = key.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
if (rc == 0) {
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
QDataStream ds(&ba, QIODevice::ReadOnly);
info.deserialize(ds);
} else if (rc == MDB_NOTFOUND) {
throw Archive::NotFound(id, name.toStdString());
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info)
{
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
try {
readInfo(key, info, txn);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
}
void Core::UrlStorage::addFile(const QString& url)
{
if (!opened) {
throw Archive::Closed("addFile(no message, no path)", name.toStdString());
}
addToInfo(url, "", "", "");
}
void Core::UrlStorage::addFile(const QString& url, const QString& path)
{
if (!opened) {
throw Archive::Closed("addFile(no message, with path)", name.toStdString());
}
addToInfo(url, "", "", "", path);
}
void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id)
{
if (!opened) {
throw Archive::Closed("addFile(with message, no path)", name.toStdString());
}
addToInfo(url, account, jid, id);
}
void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
{
if (!opened) {
throw Archive::Closed("addFile(with message, with path)", name.toStdString());
}
addToInfo(url, account, jid, id, path);
}
void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
{
if (!opened) {
throw Archive::Closed("addFile(with list)", name.toStdString());
}
UrlInfo info (path, msgs);
writeInfo(url, info, true);;
}
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
{
if (!opened) {
throw Archive::Closed("addMessageAndCheckForPath", name.toStdString());
}
return addToInfo(url, account, jid, id).getPath();
}
Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path)
{
UrlInfo info;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
try {
readInfo(url, info, txn);
} catch (const Archive::NotFound& e) {
} catch (...) {
mdb_txn_abort(txn);
throw;
}
bool pathChange = false;
bool listChange = false;
if (path != "-s") {
if (info.getPath() != path) {
info.setPath(path);
pathChange = true;
}
}
if (account.size() > 0 && jid.size() > 0 && id.size() > 0) {
listChange = info.addMessage(account, jid, id);
}
if (pathChange || listChange) {
try {
writeInfo(url, info, txn, true);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
} else {
mdb_txn_abort(txn);
}
return info;
}
std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
UrlInfo info;
try {
readInfo(url, info, txn);
info.getMessages(list);
} catch (const Archive::NotFound& e) {
} catch (...) {
mdb_txn_abort(txn);
throw;
}
info.setPath(path);
try {
writeInfo(url, info, txn, true);
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
UrlInfo info;
try {
std::string id = url.toStdString();
readInfo(url, info, txn);
info.getMessages(list);
MDB_val lmdbKey;
lmdbKey.mv_size = id.size();
lmdbKey.mv_data = (char*)id.c_str();
int rc = mdb_del(txn, base, &lmdbKey, NULL);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
if (info.hasPath()) {
std::string path = info.getPath().toStdString();
lmdbKey.mv_size = path.size();
lmdbKey.mv_data = (char*)path.c_str();
int rc = mdb_del(txn, map, &lmdbKey, NULL);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
try {
std::string spath = path.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = spath.size();
lmdbKey.mv_data = (char*)spath.c_str();
QString url;
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
if (rc == 0) {
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
url = QString(surl.c_str());
} else if (rc == MDB_NOTFOUND) {
qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
mdb_txn_abort(txn);
return list;
} else {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
UrlInfo info;
std::string id = url.toStdString();
readInfo(url, info, txn);
info.getMessages(list);
info.setPath(QString());
writeInfo(url, info, txn, true);
rc = mdb_del(txn, map, &lmdbKey, NULL);
if (rc != 0) {
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
mdb_txn_commit(txn);
} catch (...) {
mdb_txn_abort(txn);
throw;
}
return list;
}
QString Core::UrlStorage::getUrl(const QString& path)
{
std::list<Shared::MessageInfo> list;
MDB_txn *txn;
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
std::string spath = path.toStdString();
MDB_val lmdbKey, lmdbData;
lmdbKey.mv_size = spath.size();
lmdbKey.mv_data = (char*)spath.c_str();
QString url;
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
if (rc == 0) {
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
url = QString(surl.c_str());
mdb_txn_abort(txn);
return url;
} else if (rc == MDB_NOTFOUND) {
mdb_txn_abort(txn);
throw Archive::NotFound(spath, name.toStdString());
} else {
mdb_txn_abort(txn);
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
}
}
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url)
{
UrlInfo info;
readInfo(url, info);
std::list<Shared::MessageInfo> container;
info.getMessages(container);
return std::make_pair(info.getPath(), container);
}
Core::UrlStorage::UrlInfo::UrlInfo():
localPath(),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
localPath(path),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
localPath(path),
messages(msgs) {}
Core::UrlStorage::UrlInfo::~UrlInfo() {}
bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id)
{
for (const Shared::MessageInfo& info : messages) {
if (info.account == acc && info.jid == jid && info.messageId == id) {
return false;
}
}
messages.emplace_back(acc, jid, id);
return true;
}
void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const
{
data << localPath;
std::list<Shared::MessageInfo>::size_type size = messages.size();
data << quint32(size);
for (const Shared::MessageInfo& info : messages) {
data << info.account;
data << info.jid;
data << info.messageId;
}
}
void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data)
{
data >> localPath;
quint32 size;
data >> size;
for (quint32 i = 0; i < size; ++i) {
messages.emplace_back();
Shared::MessageInfo& info = messages.back();
data >> info.account;
data >> info.jid;
data >> info.messageId;
}
}
void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const
{
for (const Shared::MessageInfo& info : messages) {
container.emplace_back(info);
}
}
QString Core::UrlStorage::UrlInfo::getPath() const
{
return localPath;
}
bool Core::UrlStorage::UrlInfo::hasPath() const
{
return localPath.size() > 0;
}
void Core::UrlStorage::UrlInfo::setPath(const QString& path)
{
localPath = path;
}

99
core/storage/urlstorage.h Normal file
View File

@ -0,0 +1,99 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_URLSTORAGE_H
#define CORE_URLSTORAGE_H
#include <QString>
#include <QDataStream>
#include <lmdb.h>
#include <list>
#include "archive.h"
#include <shared/messageinfo.h>
namespace Core {
/**
* @todo write docs
*/
class UrlStorage
{
class UrlInfo;
public:
UrlStorage(const QString& name);
~UrlStorage();
void open();
void close();
void addFile(const QString& url);
void addFile(const QString& url, const QString& path);
void addFile(const QString& url, const QString& account, const QString& jid, const QString& id);
void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
void addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path); //this one overwrites all that was
std::list<Shared::MessageInfo> removeFile(const QString& url); //removes entry like it never was in the database, returns affected message infos
std::list<Shared::MessageInfo> deletedFile(const QString& path); //empties the localPath of the entry, returns affected message infos
std::list<Shared::MessageInfo> setPath(const QString& url, const QString& path);
QString getUrl(const QString& path);
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
private:
QString name;
bool opened;
MDB_env* environment;
MDB_dbi base;
MDB_dbi map;
private:
void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
void readInfo(const QString& key, UrlInfo& info);
void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn);
UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
private:
class UrlInfo {
public:
UrlInfo(const QString& path);
UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs);
UrlInfo();
~UrlInfo();
void serialize(QDataStream& data) const;
void deserialize(QDataStream& data);
QString getPath() const;
bool hasPath() const;
void setPath(const QString& path);
bool addMessage(const QString& acc, const QString& jid, const QString& id);
void getMessages(std::list<Shared::MessageInfo>& container) const;
private:
QString localPath;
std::list<Shared::MessageInfo> messages;
};
};
}
#endif // CORE_URLSTORAGE_H

2
external/qxmpp vendored

@ -1 +1 @@
Subproject commit e6eb0b78d0cb17fccd5ddb60966ba2a0a2d2b593
Subproject commit fe83e9c3d42c3becf682e2b5ecfc9d77b24c614f

10
external/simpleCrypt/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.0)
project(simplecrypt LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
find_package(Qt5 COMPONENTS Core REQUIRED)
add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
target_link_libraries(simpleCrypt Qt5::Core)

248
external/simpleCrypt/simplecrypt.cpp vendored Normal file
View File

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

226
external/simpleCrypt/simplecrypt.h vendored Normal file
View File

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

View File

@ -1,355 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "global.h"
#include <uuid/uuid.h>
#include <QApplication>
#include <QPalette>
#include <QIcon>
#include <QDebug>
Shared::Message::Message(Shared::Message::Type p_type):
jFrom(),
rFrom(),
jTo(),
rTo(),
id(),
body(),
time(),
thread(),
type(p_type),
outgoing(false),
forwarded(false)
{
}
Shared::Message::Message():
jFrom(),
rFrom(),
jTo(),
rTo(),
id(),
body(),
time(),
thread(),
type(Message::normal),
outgoing(false),
forwarded(false)
{
}
QString Shared::Message::getBody() const
{
return body;
}
QString Shared::Message::getFrom() const
{
QString from = jFrom;
if (rFrom.size() > 0) {
from += "/" + rFrom;
}
return from;
}
QString Shared::Message::getTo() const
{
QString to = jTo;
if (rTo.size() > 0) {
to += "/" + rTo;
}
return to;
}
QString Shared::Message::getId() const
{
return id;
}
QDateTime Shared::Message::getTime() const
{
return time;
}
void Shared::Message::setBody(const QString& p_body)
{
body = p_body;
}
void Shared::Message::setFrom(const QString& from)
{
QStringList list = from.split("/");
if (list.size() == 1) {
jFrom = from;
} else {
jFrom = list.front();
rFrom = list.back();
}
}
void Shared::Message::setTo(const QString& to)
{
QStringList list = to.split("/");
if (list.size() == 1) {
jTo = to;
} else {
jTo = list.front();
rTo = list.back();
}
}
void Shared::Message::setId(const QString& p_id)
{
id = p_id;
}
void Shared::Message::setTime(const QDateTime& p_time)
{
time = p_time;
}
QString Shared::Message::getFromJid() const
{
return jFrom;
}
QString Shared::Message::getFromResource() const
{
return rFrom;
}
QString Shared::Message::getToJid() const
{
return jTo;
}
QString Shared::Message::getToResource() const
{
return rTo;
}
QString Shared::Message::getPenPalJid() const
{
if (outgoing) {
return jTo;
} else {
return jFrom;
}
}
QString Shared::Message::getPenPalResource() const
{
if (outgoing) {
return rTo;
} else {
return rFrom;
}
}
void Shared::Message::setFromJid(const QString& from)
{
jFrom = from;
}
void Shared::Message::setFromResource(const QString& from)
{
rFrom = from;
}
void Shared::Message::setToJid(const QString& to)
{
jTo = to;
}
void Shared::Message::setToResource(const QString& to)
{
rTo = to;
}
bool Shared::Message::getOutgoing() const
{
return outgoing;
}
void Shared::Message::setOutgoing(bool og)
{
outgoing = og;
}
bool Shared::Message::getForwarded() const
{
return forwarded;
}
void Shared::Message::generateRandomId()
{
id = generateUUID();
}
QString Shared::Message::getThread() const
{
return thread;
}
void Shared::Message::setForwarded(bool fwd)
{
forwarded = fwd;
}
void Shared::Message::setThread(const QString& p_body)
{
thread = p_body;
}
Shared::Message::Type Shared::Message::getType() const
{
return type;
}
void Shared::Message::setType(Shared::Message::Type t)
{
type = t;
}
void Shared::Message::serialize(QDataStream& data) const
{
data << jFrom;
data << rFrom;
data << jTo;
data << rTo;
data << id;
data << body;
data << time;
data << thread;
quint8 t = type;
data << t;
data << outgoing;
data << forwarded;
data << oob;
}
void Shared::Message::deserialize(QDataStream& data)
{
data >> jFrom;
data >> rFrom;
data >> jTo;
data >> rTo;
data >> id;
data >> body;
data >> time;
data >> thread;
quint8 t;
data >> t;
type = static_cast<Type>(t);
data >> outgoing;
data >> forwarded;
data >> oob;
}
QString Shared::generateUUID()
{
uuid_t uuid;
uuid_generate(uuid);
char uuid_str[36];
uuid_unparse_lower(uuid, uuid_str);
return uuid_str;
}
void Shared::Message::setCurrentTime()
{
time = QDateTime::currentDateTime();
}
QString Shared::Message::getOutOfBandUrl() const
{
return oob;
}
bool Shared::Message::hasOutOfBandUrl() const
{
return oob.size() > 0;
}
void Shared::Message::setOutOfBandUrl(const QString& url)
{
oob = url;
}
bool Shared::Message::storable() const
{
return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
}
QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
{
const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ?
big ?
Shared::fallbackAvailabilityThemeIconsDarkBig:
Shared::fallbackAvailabilityThemeIconsDarkSmall:
big ?
Shared::fallbackAvailabilityThemeIconsLightBig:
Shared::fallbackAvailabilityThemeIconsLightSmall;
return QIcon::fromTheme(availabilityThemeIcons[av], QIcon(fallback[av]));
}
QIcon Shared::subscriptionStateIcon(Shared::SubscriptionState ss, bool big)
{
const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ?
big ?
Shared::fallbackSubscriptionStateThemeIconsDarkBig:
Shared::fallbackSubscriptionStateThemeIconsDarkSmall:
big ?
Shared::fallbackSubscriptionStateThemeIconsLightBig:
Shared::fallbackSubscriptionStateThemeIconsLightSmall;
return QIcon::fromTheme(subscriptionStateThemeIcons[ss], QIcon(fallback[ss]));
}
QIcon Shared::connectionStateIcon(Shared::ConnectionState cs, bool big)
{
const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ?
big ?
Shared::fallbackConnectionStateThemeIconsDarkBig:
Shared::fallbackConnectionStateThemeIconsDarkSmall:
big ?
Shared::fallbackConnectionStateThemeIconsLightBig:
Shared::fallbackConnectionStateThemeIconsLightSmall;
return QIcon::fromTheme(connectionStateThemeIcons[cs], QIcon(fallback[cs]));
}
static const QString ds = ":images/fallback/dark/small/";
static const QString db = ":images/fallback/dark/big/";
static const QString ls = ":images/fallback/light/small/";
static const QString lb = ":images/fallback/light/big/";
QIcon Shared::icon(const QString& name, bool big)
{
std::map<QString, std::pair<QString, QString>>::const_iterator itr = icons.find(name);
if (itr != icons.end()) {
const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ?
big ? db : ds:
big ? lb : ls;
return QIcon::fromTheme(itr->second.first, QIcon(prefix + itr->second.second));
} else {
qDebug() << "Icon" << name << "not found";
return QIcon::fromTheme(name);
}
}

163
main.cpp
View File

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

7
main/CMakeLists.txt Normal file
View File

@ -0,0 +1,7 @@
target_sources(squawk PRIVATE
main.cpp
application.cpp
application.h
dialogqueue.cpp
dialogqueue.h
)

566
main/application.cpp Normal file
View File

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

118
main/application.h Normal file
View File

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

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

140
main/main.cpp Normal file
View File

@ -0,0 +1,140 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "shared/global.h"
#include "shared/messageinfo.h"
#include "shared/pathcheck.h"
#include "main/application.h"
#include "core/signalcatcher.h"
#include "core/squawk.h"
#include <QLibraryInfo>
#include <QSettings>
#include <QStandardPaths>
#include <QTranslator>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtWidgets/QApplication>
#include <QDir>
int main(int argc, char *argv[])
{
qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
qRegisterMetaType<Shared::VCard>("Shared::VCard");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
qRegisterMetaType<QSet<QString>>("QSet<QString>");
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
qRegisterMetaType<Shared::Availability>("Shared::Availability");
QApplication app(argc, argv);
SignalCatcher sc(&app);
QApplication::setApplicationName("squawk");
QApplication::setOrganizationName("macaw.me");
QApplication::setApplicationDisplayName("Squawk");
QApplication::setApplicationVersion("0.2.2");
app.setDesktopFileName("squawk");
QTranslator qtTranslator;
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&qtTranslator);
QTranslator myappTranslator;
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
bool found = false;
for (QString share : shares) {
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
if (found) {
break;
}
}
if (!found) {
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
}
app.installTranslator(&myappTranslator);
QIcon icon;
icon.addFile(":images/logo.svg", QSize(16, 16));
icon.addFile(":images/logo.svg", QSize(24, 24));
icon.addFile(":images/logo.svg", QSize(32, 32));
icon.addFile(":images/logo.svg", QSize(48, 48));
icon.addFile(":images/logo.svg", QSize(64, 64));
icon.addFile(":images/logo.svg", QSize(96, 96));
icon.addFile(":images/logo.svg", QSize(128, 128));
icon.addFile(":images/logo.svg", QSize(256, 256));
icon.addFile(":images/logo.svg", QSize(512, 512));
QApplication::setWindowIcon(icon);
new Shared::Global(); //translates enums
QSettings settings;
QVariant vs = settings.value("style");
if (vs.isValid()) {
QString style = vs.toString().toLower();
if (style != "system") {
Shared::Global::setStyle(style);
}
}
if (Shared::Global::supported("colorSchemeTools")) {
QVariant vt = settings.value("theme");
if (vt.isValid()) {
QString theme = vt.toString();
if (theme.toLower() != "system") {
Shared::Global::setTheme(theme);
}
}
}
QString path = Shared::downloadsPathCheck();
if (path.size() > 0) {
settings.setValue("downloadsPath", path);
} else {
qDebug() << "couldn't initialize directory for downloads, quitting";
return -1;
}
Core::Squawk* squawk = new Core::Squawk();
QThread* coreThread = new QThread();
squawk->moveToThread(coreThread);
Application application(squawk);
QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit);
QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop);
//QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
coreThread->start();
int result = app.exec();
if (coreThread->isRunning()) {
//coreThread->wait();
//todo if I uncomment that, the app will not quit if it has reconnected at least once
//it feels like a symptom of something badly desinged in the core thread
//need to investigate;
}
return result;
}

View File

@ -1,21 +1,26 @@
# Maintainer: Yury Gubich <blue@macaw.me>
pkgname=squawk
pkgver=0.0.5
pkgver=0.2.2
pkgrel=1
pkgdesc="An XMPP desktop messenger, written on qt"
pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
arch=('i686' 'x86_64')
url="https://git.macaw.me/blue/squawk"
license=('GPLv3')
depends=('qt5-base' 'qt5-svg' 'lmdb' 'qxmpp>=1.0.0' 'libutil-linux')
makedepends=('cmake>=3.3' 'imagemagick')
source=("https://git.macaw.me/blue/squawk/archive/master.tar.gz")
md5sums=('SKIP')
license=('GPL3')
depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
optdepends=('kwallet: secure password storage (requires rebuild)'
'kconfig: system themes support (requires rebuild)'
'kconfigwidgets: system themes support (requires rebuild)'
'kio: better show in folder action (requires rebuild)')
source=("$pkgname-$pkgver.tar.gz")
sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
build() {
cd "$srcdir/squawk"
cmake . -D SYSTEM_QXMPP:BOOL=True -D CMAKE_INSTALL_PREFIX=/usr -G Ninja
cmake --build .
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
cmake --build . -j $nproc
}
package() {
cd "$srcdir/squawk"
DESTDIR="$pkgdir/" cmake --build . --target install
DESTDIR="$pkgdir/" cmake --build . --target install
}

3
packaging/CMakeLists.txt Normal file
View File

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

View File

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

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 KF5::KIOWidgets)
install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif ()
if (WITH_KCONFIG)
add_library(colorSchemeTools SHARED colorschemetools.cpp)
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()

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 <KConfigCore/KSharedConfig>
#include <KConfigCore/KConfigGroup>
#include <KConfigWidgets/KColorScheme>
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection);
extern "C" QIcon* createPreview(const QString& path) {
KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path);
QIcon* result = new QIcon();
KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig);
KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig);
KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig);
KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig);
result->addPixmap(createPixmap(16, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
result->addPixmap(createPixmap(24, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
return result;
}
extern "C" void deletePreview(QIcon* icon) {
delete icon;
}
extern "C" void colorSchemeName(const QString& path, QString& answer) {
KSharedConfigPtr config = KSharedConfig::openConfig(path);
KConfigGroup group(config, QStringLiteral("General"));
answer = group.readEntry("Name", QFileInfo(path).baseName());
}
extern "C" void createPalette(const QString& path, QPalette& answer) {
KSharedConfigPtr config = KSharedConfig::openConfig(path);
answer = KColorScheme::createApplicationPalette(config);
}
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) {
QPixmap pix(size, size);
pix.fill(Qt::black);
QPainter p;
p.begin(&pix);
const int itemSize = size / 2 - 1;
p.fillRect(1, 1, itemSize, itemSize, window);
p.fillRect(1 + itemSize, 1, itemSize, itemSize, button);
p.fillRect(1, 1 + itemSize, itemSize, itemSize, view);
p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, selection);
p.end();
return pix;
}

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

59
resources/CMakeLists.txt Normal file
View File

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

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10 4 L 10 11 L 3 11 L 3 12 L 10 12 L 10 19 L 11 19 L 11 12 L 18 12 L 18 11 L 11 11 L 11 4 L 10 4 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m4 3v1 13h1 2 1v1 1h6l4-4v-1-7-1h-2v-3h-1-10-1m1 1h10v2h-7v1 9h-1-2v-12m4 3h8v7h-3-1v1 3h-4v-11"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 432 B

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,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m14.996094 3l-11.992188 11.992188h-.003906v4.00781h1 2 1.00781v-.003906l11.992188-11.992188-.001953-.001953.001953-.001953-4-4-.001953.001953-.001953-.001953m-1.998047 3.412109l2.589844 2.589844-7.587891 7.587891v-1.589844h-1-1v-1-.589844l6.998047-6.998047m-7.998047 7.998047v1.589844h1 1v1 .589844l-.410156.410156h-1.589844l-1-1v-1.589844l1-1"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 681 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 439 B

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<defs id="defs5455">
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
<stop style="stop-color:#3daee9" id="stop4174-6"/>
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
</defs>
<metadata id="metadata5458"/>
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.3380003 6 5 7.3380003 5 9 C 5 10.662 6.3380003 12 8 12 C 9.6619997 12 11 10.662 11 9 C 11 7.3380003 9.6619997 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 15.482985 11 16.758385 11.807292 17.449219 13 L 18.580078 13 C 17.810617 11.232833 16.056835 10 14 10 z M 8 13 C 5.2299834 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.7839834 14 8 14 z M 16 14 L 16 16 L 14 16 L 14 17 L 16 17 L 16 19 L 17 19 L 17 17 L 19 17 L 19 16 L 17 16 L 17 14 L 16 14 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 16.21602 11 18 12.78398 18 15 L 14.5 15 L 15 16 L 19 16 L 19 15 C 19 12.22998 16.77002 10 14 10 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 953 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10 3 L 10 4 L 19 4 L 19 3 L 10 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 12 8 L 12 9 L 19 9 L 19 8 L 12 8 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 13 13 L 13 14 L 19 14 L 19 13 L 13 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 734 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 7 3.0058594 L 7 8 L 2 8 L 2 8.9980469 L 7 8.9980469 L 7 14.007812 L 8 14.007812 L 8 8.9980469 L 13 8.9980469 L 13 8 L 8 8 L 8 3.0058594 L 7 3.0058594 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 490 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 3 2 L 3 13 L 6 13 L 6 14 L 11 14 L 14 11 L 14 4 L 13 4 L 13 2 L 3.7851562 2 L 3 2 z M 4 3 L 12 3 L 12 4 L 6 4 L 6 12 L 4 12 L 4 3 z M 7 5 L 13 5 L 13 10 L 10 10 L 10 13 L 7 13 L 7 5 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 522 B

View File

@ -0,0 +1,12 @@
<!DOCTYPE svg>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1010 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 883 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 439 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 609 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11 8.0722656 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.0917969 11 L 9.2929688 11 A 2.5 3 0 0 1 11 9.1074219 L 11 8.0722656 z M 12 9 L 12 11 L 10 11 L 10 12 L 12 12 L 12 14 L 13 14 L 13 12 L 15 12 L 15 11 L 13 11 L 13 9 L 12 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 938 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11.646484 8 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.6171875 12 L 14.146484 12 L 15.146484 12 A 3.4999979 4 0 0 0 11.646484 8 z M 11.646484 9 A 2.5 3 0 0 1 14 11 L 9.2929688 11 A 2.5 3 0 0 1 11.646484 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 916 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 9 2 L 9 3 L 14 3 L 14 2 L 9 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 9 5 L 9 6 L 14 6 L 14 5 L 9 5 z M 9 8 L 9 9 L 14 9 L 14 8 L 9 8 z M 5.5 10 A 3.499998 4 0 0 0 2 14 L 9 14 A 3.499998 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
class="ColorScheme-Text"/>
</svg>

After

Width:  |  Height:  |  Size: 678 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10 4 L 10 11 L 3 11 L 3 12 L 10 12 L 10 19 L 11 19 L 11 12 L 18 12 L 18 11 L 11 11 L 11 4 L 10 4 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m4 3v1 13h1 2 1v1 1h6l4-4v-1-7-1h-2v-3h-1-10-1m1 1h10v2h-7v1 9h-1-2v-12m4 3h8v7h-3-1v1 3h-4v-11"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 432 B

View File

@ -0,0 +1,11 @@
<!DOCTYPE svg>
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="m14.996094 3l-11.992188 11.992188h-.003906v4.00781h1 2 1.00781v-.003906l11.992188-11.992188-.001953-.001953.001953-.001953-4-4-.001953.001953-.001953-.001953m-1.998047 3.412109l2.589844 2.589844-7.587891 7.587891v-1.589844h-1-1v-1-.589844l6.998047-6.998047m-7.998047 7.998047v1.589844h1 1v1 .589844l-.410156.410156h-1.589844l-1-1v-1.589844l1-1"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 681 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 439 B

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<defs id="defs5455">
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
<stop style="stop-color:#3daee9" id="stop4174-6"/>
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
</defs>
<metadata id="metadata5458"/>
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.3380003 6 5 7.3380003 5 9 C 5 10.662 6.3380003 12 8 12 C 9.6619997 12 11 10.662 11 9 C 11 7.3380003 9.6619997 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 15.482985 11 16.758385 11.807292 17.449219 13 L 18.580078 13 C 17.810617 11.232833 16.056835 10 14 10 z M 8 13 C 5.2299834 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.7839834 14 8 14 z M 16 14 L 16 16 L 14 16 L 14 17 L 16 17 L 16 19 L 17 19 L 17 17 L 19 17 L 19 16 L 17 16 L 17 14 L 16 14 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 16.21602 11 18 12.78398 18 15 L 14.5 15 L 15 16 L 19 16 L 19 15 C 19 12.22998 16.77002 10 14 10 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 953 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10 3 L 10 4 L 19 4 L 19 3 L 10 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 12 8 L 12 9 L 19 9 L 19 8 L 12 8 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 13 13 L 13 14 L 19 14 L 19 13 L 13 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 734 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 7 3.0058594 L 7 8 L 2 8 L 2 8.9980469 L 7 8.9980469 L 7 14.007812 L 8 14.007812 L 8 8.9980469 L 13 8.9980469 L 13 8 L 8 8 L 8 3.0058594 L 7 3.0058594 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 490 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 3 2 L 3 13 L 6 13 L 6 14 L 11 14 L 14 11 L 14 4 L 13 4 L 13 2 L 3.7851562 2 L 3 2 z M 4 3 L 12 3 L 12 4 L 6 4 L 6 12 L 4 12 L 4 3 z M 7 5 L 13 5 L 13 10 L 10 10 L 10 13 L 7 13 L 7 5 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 522 B

View File

@ -0,0 +1,12 @@
<!DOCTYPE svg>
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1010 B

View File

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 883 B

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#eff0f1;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 439 B

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