mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge pull request #5932
Mick Sayson (6): feat(messages): Multipacket message support feat(messages): History and offline message support for extended messages feat(extensions): UI updates for extension support fix(extensions): Add toxext to CI scripts feat(extensions): Update documentation feat(extensions): Split messages on extended messages
This commit is contained in:
commit
59038088cb
|
@ -126,16 +126,18 @@ CC="ccache $CC" CXX="ccache $CXX" make -j$(nproc)
|
|||
sudo checkinstall --install --pkgname libsodium --pkgversion 1.0.8 --nodoc -y
|
||||
sudo ldconfig
|
||||
cd ..
|
||||
|
||||
# toxcore
|
||||
git clone --branch v0.2.12 --depth=1 https://github.com/toktok/c-toxcore.git toxcore
|
||||
cd toxcore
|
||||
autoreconf -if
|
||||
CC="ccache $CC" CXX="ccache $CXX" ./configure
|
||||
CC="ccache $CC" CXX="ccache $CXX" make -j$(nproc) > /dev/null
|
||||
mkdir build-cmake
|
||||
cd build-cmake
|
||||
CC="ccache $CC" CXX="ccache $CXX" cmake ..
|
||||
make -j$(nproc) > /dev/null
|
||||
sudo make install
|
||||
echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
|
||||
sudo ldconfig
|
||||
cd ..
|
||||
cd ../..
|
||||
|
||||
# filteraudio
|
||||
git clone --branch v0.0.1 --depth=1 https://github.com/irungentoo/filter_audio filteraudio
|
||||
|
@ -144,12 +146,32 @@ CC="ccache $CC" CXX="ccache $CXX" sudo make install -j$(nproc)
|
|||
sudo ldconfig
|
||||
cd ..
|
||||
|
||||
$CC --version
|
||||
$CXX --version
|
||||
# toxext
|
||||
git clone --branch v0.0.2 --depth=1 https://github.com/toxext/toxext toxext
|
||||
cd toxext
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ../../
|
||||
|
||||
# toxext_messages
|
||||
git clone --branch v0.0.2 --depth=1 https://github.com/toxext/tox_extension_messages tox_extension_messages
|
||||
cd tox_extension_messages
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ../../
|
||||
|
||||
# needed, otherwise ffmpeg doesn't get detected
|
||||
export PKG_CONFIG_PATH="$PWD/libs/lib/pkgconfig"
|
||||
|
||||
$CC --version
|
||||
$CXX --version
|
||||
|
||||
build_qtox() {
|
||||
bdir() {
|
||||
cd $BUILDDIR
|
||||
|
|
|
@ -218,6 +218,8 @@ set(${PROJECT_NAME}_SOURCES
|
|||
src/chatlog/textformatter.h
|
||||
src/core/coreav.cpp
|
||||
src/core/coreav.h
|
||||
src/core/coreext.cpp
|
||||
src/core/coreext.h
|
||||
src/core/core.cpp
|
||||
src/core/corefile.cpp
|
||||
src/core/corefile.h
|
||||
|
@ -360,6 +362,8 @@ set(${PROJECT_NAME}_SOURCES
|
|||
src/widget/contentlayout.h
|
||||
src/widget/emoticonswidget.cpp
|
||||
src/widget/emoticonswidget.h
|
||||
src/widget/extensionstatus.cpp
|
||||
src/widget/extensionstatus.h
|
||||
src/widget/flowlayout.cpp
|
||||
src/widget/flowlayout.h
|
||||
src/widget/searchform.cpp
|
||||
|
|
52
INSTALL.md
52
INSTALL.md
|
@ -34,18 +34,20 @@
|
|||
|
||||
## Dependencies
|
||||
|
||||
| Name | Version | Modules |
|
||||
|---------------|-------------|----------------------------------------------------------|
|
||||
| [Qt] | >= 5.5.0 | concurrent, core, gui, network, opengl, svg, widget, xml |
|
||||
| [GCC]/[MinGW] | >= 4.8 | C++11 enabled |
|
||||
| [toxcore] | >= 0.2.10 | core, av |
|
||||
| [FFmpeg] | >= 2.6.0 | avformat, avdevice, avcodec, avutil, swscale |
|
||||
| [CMake] | >= 2.8.11 | |
|
||||
| [OpenAL Soft] | >= 1.16.0 | |
|
||||
| [qrencode] | >= 3.0.3 | |
|
||||
| [sqlcipher] | >= 3.2.0 | |
|
||||
| [pkg-config] | >= 0.28 | |
|
||||
| [snorenotify] | >= 0.7.0 | optional dependency |
|
||||
| Name | Version | Modules |
|
||||
|--------------------------|-------------|----------------------------------------------------------|
|
||||
| [Qt] | >= 5.5.0 | concurrent, core, gui, network, opengl, svg, widget, xml |
|
||||
| [GCC]/[MinGW] | >= 4.8 | C++11 enabled |
|
||||
| [toxcore] | >= 0.2.10 | core, av |
|
||||
| [FFmpeg] | >= 2.6.0 | avformat, avdevice, avcodec, avutil, swscale |
|
||||
| [CMake] | >= 2.8.11 | |
|
||||
| [OpenAL Soft] | >= 1.16.0 | |
|
||||
| [qrencode] | >= 3.0.3 | |
|
||||
| [sqlcipher] | >= 3.2.0 | |
|
||||
| [pkg-config] | >= 0.28 | |
|
||||
| [snorenotify] | >= 0.7.0 | optional dependency |
|
||||
| [toxext] | >= 0.0.1 | |
|
||||
| [tox_extension_messages] | >= 0.0.1 | |
|
||||
|
||||
## Optional dependencies
|
||||
|
||||
|
@ -424,6 +426,30 @@ echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
|
|||
sudo ldconfig
|
||||
```
|
||||
|
||||
### Compile extensions
|
||||
|
||||
qTox uses the toxext library and some of the extensions that go with it.
|
||||
|
||||
You will likely have to compile these yourself
|
||||
|
||||
```bash
|
||||
git clone https://github.com/toxext/toxext.git toxext
|
||||
cd toxext
|
||||
git checkout v0.0.2
|
||||
cmake .
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
```bash
|
||||
git clone https://github.com/toxext/tox_extension_messages.git tox_extension_messages
|
||||
cd tox_extension_messages
|
||||
git checkout v0.0.2
|
||||
cmake .
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Compile qTox
|
||||
|
||||
**Make sure that all the dependencies are installed.** If you experience
|
||||
|
@ -756,3 +782,5 @@ Switches:
|
|||
[toxcore]: https://github.com/TokTok/c-toxcore/
|
||||
[sonnet]: https://github.com/KDE/sonnet
|
||||
[snorenotify]: https://techbase.kde.org/Projects/Snorenotify
|
||||
[toxext]: https://github.com/toxext/toxext
|
||||
[tox_extension_messages]: https://github.com/toxext/tox_extension_messages
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
readonly DEBUG="$1"
|
||||
|
||||
# Set this to True to upload the PR version of the
|
||||
# Set this to True to upload the PR version of the
|
||||
# AppImage to transfer.sh for testing.
|
||||
readonly UPLOAD_PR_APPIMAGE="False"
|
||||
|
||||
|
@ -80,7 +80,7 @@ then
|
|||
# the .zsync meta file as the given name below with .zsync
|
||||
# extension.
|
||||
readonly OUTFILE=./output/qTox-"$TRAVIS_TAG".x86_64.AppImage
|
||||
|
||||
|
||||
# just check if the files are in the right place
|
||||
eval "ls $OUTFILE"
|
||||
eval "ls $OUTFILE.zsync"
|
||||
|
|
|
@ -52,7 +52,7 @@ copy_libs() {
|
|||
echo Copying libraries…
|
||||
for lib in "${libs[@]}"
|
||||
do
|
||||
cp -v "$lib" "$dest"
|
||||
cp -v -r "$lib" "$dest"
|
||||
done
|
||||
}
|
||||
|
||||
|
|
75
bootstrap.sh
75
bootstrap.sh
|
@ -49,14 +49,20 @@ readonly BASE_DIR="${SCRIPT_DIR}/${INSTALL_DIR}"
|
|||
|
||||
# versions of libs to checkout
|
||||
readonly TOXCORE_VERSION="v0.2.12"
|
||||
readonly TOXEXT_VERSION="v0.0.2"
|
||||
readonly TOX_EXT_MESSAGES_VERSION="v0.0.2"
|
||||
readonly SQLCIPHER_VERSION="v4.3.0"
|
||||
|
||||
# directory names of cloned repositories
|
||||
readonly TOXCORE_DIR="libtoxcore-$TOXCORE_VERSION"
|
||||
readonly TOXEXT_DIR="toxext-$TOXEXT_VERSION"
|
||||
readonly TOX_EXT_MESSAGES_DIR="tox_ext_messages-$TOXEXT_VERSION"
|
||||
readonly SQLCIPHER_DIR="sqlcipher-$SQLCIPHER_VERSION"
|
||||
|
||||
# default values for user given parameters
|
||||
INSTALL_TOX=true
|
||||
INSTALL_TOXEXT=true
|
||||
INSTALL_TOX_EXT_MESSAGES=true
|
||||
INSTALL_SQLCIPHER=false
|
||||
SYSTEM_WIDE=true
|
||||
KEEP_BUILD_FILES=false
|
||||
|
@ -128,6 +134,57 @@ install_toxcore() {
|
|||
fi
|
||||
}
|
||||
|
||||
install_toxext() {
|
||||
if [[ $INSTALL_TOXEXT = "true" ]]
|
||||
then
|
||||
git clone https://github.com/toxext/toxext.git \
|
||||
--branch $TOXEXT_VERSION \
|
||||
"${BASE_DIR}/${TOXEXT_DIR}"
|
||||
|
||||
pushd ${BASE_DIR}/${TOXEXT_DIR}
|
||||
|
||||
# compile and install
|
||||
if [[ $SYSTEM_WIDE = "false" ]]
|
||||
then
|
||||
cmake . -DCMAKE_INSTALL_PREFIX=${BASE_DIR}
|
||||
make -j $(nproc)
|
||||
make install
|
||||
else
|
||||
cmake .
|
||||
make -j $(nproc)
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
fi
|
||||
|
||||
popd
|
||||
fi
|
||||
}
|
||||
|
||||
install_tox_ext_messages() {
|
||||
if [[ $INSTALL_TOX_EXT_MESSAGES = "true" ]]
|
||||
then
|
||||
git clone https://github.com/toxext/tox_extension_messages.git \
|
||||
--branch $TOX_EXT_MESSAGES_VERSION \
|
||||
"${BASE_DIR}/${TOX_EXT_MESSAGES_DIR}"
|
||||
|
||||
pushd ${BASE_DIR}/${TOX_EXT_MESSAGES_DIR}
|
||||
|
||||
# compile and install
|
||||
if [[ $SYSTEM_WIDE = "false" ]]
|
||||
then
|
||||
cmake . -DCMAKE_INSTALL_PREFIX=${BASE_DIR}
|
||||
make -j $(nproc)
|
||||
make install
|
||||
else
|
||||
cmake .
|
||||
make -j $(nproc)
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
fi
|
||||
|
||||
popd
|
||||
fi
|
||||
}
|
||||
|
||||
install_sqlcipher() {
|
||||
if [[ $INSTALL_SQLCIPHER = "true" ]]
|
||||
|
@ -178,6 +235,22 @@ main() {
|
|||
then
|
||||
INSTALL_TOX=false
|
||||
shift
|
||||
elif [ ${1} = "--with-toxext" ]
|
||||
then
|
||||
INSTALL_TOXEXT=true
|
||||
shift
|
||||
elif [ ${1} = "--without-toxext" ]
|
||||
then
|
||||
INSTALL_TOXEXT=false
|
||||
shift
|
||||
elif [ ${1} = "--with-toxext-messages" ]
|
||||
then
|
||||
INSTALL_TOX_EXT_MESSAGES=true
|
||||
shift
|
||||
elif [ ${1} = "--without-toxext-messages" ]
|
||||
then
|
||||
INSTALL_TOX_EXT_MESSAGES=false
|
||||
shift
|
||||
elif [ ${1} = "--with-sqlcipher" ]
|
||||
then
|
||||
INSTALL_SQLCIPHER=true
|
||||
|
@ -221,6 +294,8 @@ main() {
|
|||
|
||||
############### install step ###############
|
||||
install_toxcore
|
||||
install_toxext
|
||||
install_tox_ext_messages
|
||||
install_sqlcipher
|
||||
|
||||
############### cleanup step ###############
|
||||
|
|
|
@ -33,6 +33,9 @@ find_package(Qt5Test REQUIRED)
|
|||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5Xml REQUIRED)
|
||||
|
||||
find_package(ToxExt REQUIRED)
|
||||
find_package(ToxExtensionMessages REQUIRED)
|
||||
|
||||
function(add_dependency)
|
||||
set(ALL_LIBRARIES ${ALL_LIBRARIES} ${ARGN} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
@ -47,6 +50,10 @@ add_dependency(
|
|||
Qt5::Widgets
|
||||
Qt5::Xml)
|
||||
|
||||
add_dependency(
|
||||
ToxExt::ToxExt
|
||||
ToxExtensionMessages::ToxExtensionMessages)
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
function(search_dependency pkg)
|
||||
|
|
|
@ -489,3 +489,11 @@ public key, and which changes on every start of a client, so it's best to use a
|
|||
[ToxMe service]: #register-on-toxme
|
||||
[user profile]: #user-profile
|
||||
[profile corner]: #profile-corner
|
||||
|
||||
# Extensions
|
||||
|
||||
qTox supports extra features through the use of extensions to the tox protocol. Not all contacts are going to support these extensions.
|
||||
|
||||
For most cases you won't have to do anything, but you may wonder why behavior of chats is different for some friends. There is a puzzle piece icon to the left of your contact's name in the top of a chat. If it's green that means that they support all the features qTox cares about. If it's yellow it means some of the features are supported. If it's red it means that they don't support any extensions.
|
||||
|
||||
You can hover over the icon to see which extensions they support. qTox should dynamically enable/disable features based on the extension set of your friend.
|
||||
|
|
|
@ -117,6 +117,30 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "toxext",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/toxext/toxext",
|
||||
"tag": "v0.0.2",
|
||||
"commit": "0280357a0dded4dd46d0ff29f52875687136472d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tox_extension_messages",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/toxext/tox_extension_messages",
|
||||
"tag": "v0.0.2",
|
||||
"commit": "f1f4539cf1aeed0bcc0ad476fbae74cb5bd0cf66"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "qTox",
|
||||
"buildsystem": "cmake-ninja",
|
||||
|
|
1
img/status/extensions_available.svg
Normal file
1
img/status/extensions_available.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg height="23px" version="1.1" viewBox="0 0 23 23" width="23px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><desc/><defs/><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#6FC062" id="Core" transform="translate(-253.000000, -211.000000)"><g id="extension" transform="translate(253.500000, 211.500000)"><path d="M18.5,10 L17,10 L17,6 C17,4.9 16.1,4 15,4 L11,4 L11,2.5 C11,1.1 9.9,0 8.5,0 C7.1,0 6,1.1 6,2.5 L6,4 L2,4 C0.9,4 0,4.9 0,6 L0,9.8 L1.5,9.8 C3,9.8 4.2,11 4.2,12.5 C4.2,14 3,15.2 1.5,15.2 L0,15.2 L0,19 C0,20.1 0.9,21 2,21 L5.8,21 L5.8,19.5 C5.8,18 7,16.8 8.5,16.8 C10,16.8 11.2,18 11.2,19.5 L11.2,21 L15,21 C16.1,21 17,20.1 17,19 L17,15 L18.5,15 C19.9,15 21,13.9 21,12.5 C21,11.1 19.9,10 18.5,10 L18.5,10 Z" id="Shape"/></g></g></g></svg>
|
After Width: | Height: | Size: 906 B |
1
img/status/extensions_partial.svg
Normal file
1
img/status/extensions_partial.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg height="23px" version="1.1" viewBox="0 0 23 23" width="23px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><desc/><defs/><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#CDBE41" id="Core" transform="translate(-253.000000, -211.000000)"><g id="extension" transform="translate(253.500000, 211.500000)"><path d="M18.5,10 L17,10 L17,6 C17,4.9 16.1,4 15,4 L11,4 L11,2.5 C11,1.1 9.9,0 8.5,0 C7.1,0 6,1.1 6,2.5 L6,4 L2,4 C0.9,4 0,4.9 0,6 L0,9.8 L1.5,9.8 C3,9.8 4.2,11 4.2,12.5 C4.2,14 3,15.2 1.5,15.2 L0,15.2 L0,19 C0,20.1 0.9,21 2,21 L5.8,21 L5.8,19.5 C5.8,18 7,16.8 8.5,16.8 C10,16.8 11.2,18 11.2,19.5 L11.2,21 L15,21 C16.1,21 17,20.1 17,19 L17,15 L18.5,15 C19.9,15 21,13.9 21,12.5 C21,11.1 19.9,10 18.5,10 L18.5,10 Z" id="Shape"/></g></g></g></svg>
|
After Width: | Height: | Size: 906 B |
1
img/status/extensions_unavailable.svg
Normal file
1
img/status/extensions_unavailable.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg height="23px" version="1.1" viewBox="0 0 23 23" width="23px" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" xmlns:xlink="http://www.w3.org/1999/xlink"><title/><desc/><defs/><g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1"><g fill="#C94F50" id="Core" transform="translate(-253.000000, -211.000000)"><g id="extension" transform="translate(253.500000, 211.500000)"><path d="M18.5,10 L17,10 L17,6 C17,4.9 16.1,4 15,4 L11,4 L11,2.5 C11,1.1 9.9,0 8.5,0 C7.1,0 6,1.1 6,2.5 L6,4 L2,4 C0.9,4 0,4.9 0,6 L0,9.8 L1.5,9.8 C3,9.8 4.2,11 4.2,12.5 C4.2,14 3,15.2 1.5,15.2 L0,15.2 L0,19 C0,20.1 0.9,21 2,21 L5.8,21 L5.8,19.5 C5.8,18 7,16.8 8.5,16.8 C10,16.8 11.2,18 11.2,19.5 L11.2,21 L15,21 C16.1,21 17,20.1 17,19 L17,15 L18.5,15 C19.9,15 21,13.9 21,12.5 C21,11.1 19.9,10 18.5,10 L18.5,10 Z" id="Shape"/></g></g></g></svg>
|
After Width: | Height: | Size: 906 B |
10
img/status/negotiating.svg
Normal file
10
img/status/negotiating.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 10 10" enable-background="new 0 0 10 10" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#C94F50" d="M5,1.5c1.9,0,3.5,1.6,3.5,3.5S6.9,8.5,5,8.5C3.1,8.5,1.5,6.9,1.5,5S3.1,1.5,5,1.5 M5,0C2.2,0,0,2.2,0,5
|
||||
s2.2,5,5,5c2.8,0,5-2.2,5-5S7.8,0,5,0z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 621 B |
32
img/status/negotiating_notification.svg
Normal file
32
img/status/negotiating_notification.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="14"
|
||||
width="14"
|
||||
xml:space="preserve"
|
||||
enable-background="new 0 0 10 10"
|
||||
viewBox="0 0 14 14"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"><metadata
|
||||
id="metadata9"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs7" /><g
|
||||
style="stroke-width:0.36135802;stroke-miterlimit:4;stroke-dasharray:none;stroke:#c94f50"
|
||||
id="layer1"
|
||||
transform="matrix(3.0289233,0,0,3.0289233,0.18380903,-887.48895)"><path
|
||||
style="fill:#000000;stroke-width:0.36135802;stroke-miterlimit:4;stroke-dasharray:none;stroke:#c94f50"
|
||||
d=""
|
||||
id="path4500" /><path
|
||||
style="fill:#000000;stroke-width:0.36135802;stroke-miterlimit:4;stroke-dasharray:none;stroke:#c94f50"
|
||||
d=""
|
||||
id="path4498" /><path
|
||||
id="path4528"
|
||||
style="fill:none;stroke:#c94f50;stroke-width:0.36135802;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 0.10133195,293.90792 1.88406635,1.94794 H 2.5570778 L 4.3764717,293.9374 M 0.13990242,293.77165 H 4.375461 v 2.99779 H 0.13310726 l 9.0535e-4,-3.17789" /></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -41,6 +41,8 @@ QT_VER=($(ls ${QT_DIR} | sed -n -e 's/^\([0-9]*\.([0-9]*\.([0-9]*\).*/\1/' -e '1
|
|||
QT_DIR_VER="${QT_DIR}/${QT_VER[1]}"
|
||||
|
||||
TOXCORE_DIR="${MAIN_DIR}/toxcore" # Change to Git location
|
||||
TOX_EXT_DIR="${MAIN_DIR}/toxext"
|
||||
TOX_EXT_MESSAGES_DIR="${MAIN_DIR}/tox_extension_messages"
|
||||
|
||||
LIB_INSTALL_PREFIX="${QTOX_DIR}/libs"
|
||||
|
||||
|
@ -80,6 +82,43 @@ build_toxcore() {
|
|||
make install > /dev/null || exit 1
|
||||
}
|
||||
|
||||
build_toxext() {
|
||||
echo "Starting Toxext build and install"
|
||||
cd $TOX_EXT_DIR
|
||||
echo "Now working in: ${PWD}"
|
||||
|
||||
[[ $TRAVIS != true ]] \
|
||||
&& sleep 3
|
||||
|
||||
mkdir _build && cd _build
|
||||
fcho "Starting cmake ..."
|
||||
PKG_CONFIG_PATH="${LIB_INSTALL_PREFIX}"/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX="${LIB_INSTALL_PREFIX}" ..
|
||||
make clean &> /dev/null
|
||||
fcho "Compiling toxext."
|
||||
make > /dev/null || exit 1
|
||||
fcho "Installing toxext."
|
||||
make install > /dev/null || exit 1
|
||||
}
|
||||
|
||||
build_tox_extension_messages() {
|
||||
echo "Starting tox_extension_messages build and install"
|
||||
cd $TOX_EXT_MESSAGES_DIR
|
||||
echo "Now working in: ${PWD}"
|
||||
|
||||
[[ $TRAVIS != true ]] \
|
||||
&& sleep 3
|
||||
|
||||
mkdir _build && cd _build
|
||||
fcho "Starting cmake ..."
|
||||
PKG_CONFIG_PATH="${LIB_INSTALL_PREFIX}"/lib/pkgconfig cmake -DCMAKE_INSTALL_PREFIX="${LIB_INSTALL_PREFIX}" ..
|
||||
make clean &> /dev/null
|
||||
fcho "Compiling tox_extension_messages."
|
||||
make > /dev/null || exit 1
|
||||
fcho "Installing tox_extension_messages."
|
||||
make install > /dev/null || exit 1
|
||||
}
|
||||
|
||||
|
||||
install() {
|
||||
fcho "=============================="
|
||||
fcho "This script will install the necessary applications and libraries needed to compile qTox properly."
|
||||
|
@ -145,6 +184,26 @@ install() {
|
|||
fcho "Cloning Toxcore git ... "
|
||||
git clone --branch v0.2.12 --depth=1 https://github.com/toktok/c-toxcore "$TOXCORE_DIR"
|
||||
fi
|
||||
# toxext
|
||||
if [[ -e $TOX_EXT_DIR/.git/index ]]
|
||||
then
|
||||
fcho "ToxExt git repo already in place !"
|
||||
cd $TOX_EXT_DIR
|
||||
git pull
|
||||
else
|
||||
fcho "Cloning ToxExt git ... "
|
||||
git clone --branch v0.0.2 --depth=1 https://github.com/toxext/toxext "$TOX_EXT_DIR"
|
||||
fi
|
||||
# tox_extension_messages
|
||||
if [[ -e $TOX_EXT_MESSAGES_DIR/.git/index ]]
|
||||
then
|
||||
fcho "ToxExt git repo already in place !"
|
||||
cd $TOX_EXT_MESSAGES_DIR
|
||||
git pul
|
||||
else
|
||||
fcho "Cloning tox_extension_messages git ... "
|
||||
git clone --branch v0.0.2 --depth=1 https://github.com/toxext/tox_extension_messages "$TOX_EXT_MESSAGES_DIR"
|
||||
fi
|
||||
# qTox
|
||||
if [[ $TRAVIS = true ]]
|
||||
then
|
||||
|
@ -161,12 +220,16 @@ install() {
|
|||
fi
|
||||
fi
|
||||
|
||||
if [[ $TRAVIS != true ]]
|
||||
then
|
||||
fcho "If all went well you should now have all the tools needed to compile qTox!"
|
||||
fi
|
||||
|
||||
# toxcore build
|
||||
if [[ $TRAVIS = true ]]
|
||||
then
|
||||
build_toxcore
|
||||
else
|
||||
fcho "If all went well you should now have all the tools needed to compile qTox!"
|
||||
read -r -p "Would you like to install toxcore now? [y/N] " response
|
||||
if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]]
|
||||
then
|
||||
|
@ -176,6 +239,34 @@ install() {
|
|||
fi
|
||||
fi
|
||||
|
||||
# toxext build
|
||||
if [[ $TRAVIS = true ]]
|
||||
then
|
||||
build_toxext
|
||||
else
|
||||
read -r -p "Would you like to install toxext now? [y/N] " response
|
||||
if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]]
|
||||
then
|
||||
build_toxext
|
||||
else
|
||||
fcho "You can simply use the -u command and say [Yes/n] when prompted"
|
||||
fi
|
||||
fi
|
||||
|
||||
# tox_extension_messages build
|
||||
if [[ $TRAVIS = true ]]
|
||||
then
|
||||
build_tox_extension_messages
|
||||
else
|
||||
read -r -p "Would you like to install tox_extension_messages now? [y/N] " response
|
||||
if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]]
|
||||
then
|
||||
build_tox_extension_messages
|
||||
else
|
||||
fcho "You can simply use the -u command and say [Yes/n] when prompted"
|
||||
fi
|
||||
fi
|
||||
|
||||
QT_VER=($(ls ${QT_DIR} | sed -n -e 's/^\([0-9]*\.([0-9]*\.([0-9]*\).*/\1/' -e '1p;$p'))
|
||||
QT_DIR_VER="${QT_DIR}/${QT_VER[1]}"
|
||||
|
||||
|
@ -230,7 +321,6 @@ build() {
|
|||
cd $BUILD_DIR
|
||||
fcho "Now working in ${PWD}"
|
||||
fcho "Starting cmake ..."
|
||||
export CMAKE_PREFIX_PATH=$(brew --prefix qt5)
|
||||
|
||||
if [[ $TRAVIS = true ]]
|
||||
then
|
||||
|
@ -238,7 +328,11 @@ build() {
|
|||
else
|
||||
STRICT_OPTIONS="OFF"
|
||||
fi
|
||||
cmake -H$QTOX_DIR -B. -DUPDATE_CHECK=ON -DSPELL_CHECK=OFF -DSTRICT_OPTIONS="${STRICT_OPTIONS}"
|
||||
cmake -H$QTOX_DIR -B. \
|
||||
-DUPDATE_CHECK=ON \
|
||||
-DSPELL_CHECK=OFF \
|
||||
-DSTRICT_OPTIONS="${STRICT_OPTIONS}" \
|
||||
-DCMAKE_PREFIX_PATH="$(brew --prefix qt5);${LIB_INSTALL_PREFIX}"
|
||||
make -j$(sysctl -n hw.ncpu)
|
||||
}
|
||||
|
||||
|
@ -262,6 +356,8 @@ bootstrap() {
|
|||
|
||||
#Toxcore
|
||||
build_toxcore
|
||||
build_toxext
|
||||
build_tox_extension_messages
|
||||
|
||||
#Boot Strap
|
||||
fcho "Running: sudo ${QTOX_DIR}/bootstrap-osx.sh"
|
||||
|
|
5
res.qrc
5
res.qrc
|
@ -22,10 +22,15 @@
|
|||
<file>img/status/away_notification.svg</file>
|
||||
<file>img/status/busy.svg</file>
|
||||
<file>img/status/busy_notification.svg</file>
|
||||
<file>img/status/negotiating.svg</file>
|
||||
<file>img/status/negotiating_notification.svg</file>
|
||||
<file>img/status/offline.svg</file>
|
||||
<file>img/status/offline_notification.svg</file>
|
||||
<file>img/status/online.svg</file>
|
||||
<file>img/status/online_notification.svg</file>
|
||||
<file>img/status/extensions_available.svg</file>
|
||||
<file>img/status/extensions_partial.svg</file>
|
||||
<file>img/status/extensions_unavailable.svg</file>
|
||||
<file>img/taskbar/dark/taskbar_online.svg</file>
|
||||
<file>img/taskbar/dark/taskbar_online_event.svg</file>
|
||||
<file>img/taskbar/dark/taskbar_away.svg</file>
|
||||
|
|
|
@ -228,6 +228,11 @@ void ChatMessage::markAsDelivered(const QDateTime& time)
|
|||
replaceContent(2, new Timestamp(time, Settings::getInstance().getTimestampFormat(), baseFont));
|
||||
}
|
||||
|
||||
void ChatMessage::markAsBroken()
|
||||
{
|
||||
replaceContent(2, new Broken(Style::getImagePath("chatArea/error.svg"), QSize(16, 16)));
|
||||
}
|
||||
|
||||
QString ChatMessage::toString() const
|
||||
{
|
||||
ChatLineContent* c = getContent(1);
|
||||
|
|
|
@ -60,6 +60,7 @@ public:
|
|||
static ChatMessage::Ptr createBusyNotification();
|
||||
|
||||
void markAsDelivered(const QDateTime& time);
|
||||
void markAsBroken();
|
||||
QString toString() const;
|
||||
bool isAction() const;
|
||||
void setAsAction();
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include "core.h"
|
||||
#include "coreav.h"
|
||||
#include "corefile.h"
|
||||
|
||||
#include "src/core/coreext.h"
|
||||
#include "src/core/dhtserver.h"
|
||||
#include "src/core/icoresettings.h"
|
||||
#include "src/core/toxlogger.h"
|
||||
|
@ -515,6 +517,7 @@ void Core::registerCallbacks(Tox* tox)
|
|||
tox_callback_conference_peer_list_changed(tox, onGroupPeerListChange);
|
||||
tox_callback_conference_peer_name(tox, onGroupPeerNameChange);
|
||||
tox_callback_conference_title(tox, onGroupTitleChange);
|
||||
tox_callback_friend_lossless_packet(tox, onLosslessPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -639,6 +642,9 @@ ToxCorePtr Core::makeToxCore(const QByteArray& savedata, const ICoreSettings* co
|
|||
return {};
|
||||
}
|
||||
|
||||
core->ext = CoreExt::makeCoreExt(core->tox.get());
|
||||
connect(core.get(), &Core::friendStatusChanged, core->ext.get(), &CoreExt::onFriendStatusChanged);
|
||||
|
||||
registerCallbacks(core->tox.get());
|
||||
|
||||
// connect the thread with the Core
|
||||
|
@ -714,6 +720,16 @@ QMutex &Core::getCoreLoopLock() const
|
|||
return coreLoopLock;
|
||||
}
|
||||
|
||||
const CoreExt* Core::getExt() const
|
||||
{
|
||||
return ext.get();
|
||||
}
|
||||
|
||||
CoreExt* Core::getExt()
|
||||
{
|
||||
return ext.get();
|
||||
}
|
||||
|
||||
/* Using the now commented out statements in checkConnection(), I watched how
|
||||
* many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials,
|
||||
* 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks.
|
||||
|
@ -734,6 +750,7 @@ void Core::process()
|
|||
|
||||
static int tolerance = CORE_DISCONNECT_TOLERANCE;
|
||||
tox_iterate(tox.get(), this);
|
||||
ext->process();
|
||||
|
||||
#ifdef DEBUG
|
||||
// we want to see the debug messages immediately
|
||||
|
@ -988,6 +1005,16 @@ void Core::onGroupTitleChange(Tox*, uint32_t groupId, uint32_t peerId, const uin
|
|||
emit core->groupTitleChanged(groupId, author, ToxString(cTitle, length).getQString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handling of custom lossless packets received by toxcore. Currently only used to forward toxext packets to CoreExt
|
||||
*/
|
||||
void Core::onLosslessPacket(Tox*, uint32_t friendId,
|
||||
const uint8_t* data, size_t length, void* vCore)
|
||||
{
|
||||
Core* core = static_cast<Core*>(vCore);
|
||||
core->ext->onLosslessPacket(friendId, data, length);
|
||||
}
|
||||
|
||||
void Core::onReadReceiptCallback(Tox*, uint32_t friendId, uint32_t receipt, void* core)
|
||||
{
|
||||
emit static_cast<Core*>(core)->receiptRecieved(friendId, ReceiptNum{receipt});
|
||||
|
@ -1067,8 +1094,9 @@ bool Core::sendMessageWithType(uint32_t friendId, const QString& message, Tox_Me
|
|||
ReceiptNum& receipt)
|
||||
{
|
||||
int size = message.toUtf8().size();
|
||||
auto maxSize = static_cast<int>(tox_max_message_length());
|
||||
auto maxSize = static_cast<int>(getMaxMessageSize());
|
||||
if (size > maxSize) {
|
||||
assert(false);
|
||||
qCritical() << "Core::sendMessageWithType called with message of size:" << size
|
||||
<< "when max is:" << maxSize << ". Ignoring.";
|
||||
return false;
|
||||
|
@ -1112,7 +1140,7 @@ void Core::sendGroupMessageWithType(int groupId, const QString& message, Tox_Mes
|
|||
QMutexLocker ml{&coreLoopLock};
|
||||
|
||||
int size = message.toUtf8().size();
|
||||
auto maxSize = static_cast<int>(tox_max_message_length());
|
||||
auto maxSize = static_cast<int>(getMaxMessageSize());
|
||||
if (size > maxSize) {
|
||||
qCritical() << "Core::sendMessageWithType called with message of size:" << size
|
||||
<< "when max is:" << maxSize << ". Ignoring.";
|
||||
|
@ -1734,11 +1762,8 @@ QString Core::getFriendUsername(uint32_t friendnumber) const
|
|||
return ToxString(nameBuf.data(), nameSize).getQString();
|
||||
}
|
||||
|
||||
QStringList Core::splitMessage(const QString& message)
|
||||
uint64_t Core::getMaxMessageSize() const
|
||||
{
|
||||
QStringList splittedMsgs;
|
||||
QByteArray ba_message{message.toUtf8()};
|
||||
|
||||
/*
|
||||
* TODO: Remove this hack; the reported max message length we receive from c-toxcore
|
||||
* as of 08-02-2019 is inaccurate, causing us to generate too large messages when splitting
|
||||
|
@ -1749,33 +1774,7 @@ QStringList Core::splitMessage(const QString& message)
|
|||
*
|
||||
* (uint32_t tox_max_message_length(void); declared in tox.h, unable to see explicit definition)
|
||||
*/
|
||||
const auto maxLen = static_cast<int>(tox_max_message_length()) - 50;
|
||||
|
||||
while (ba_message.size() > maxLen) {
|
||||
int splitPos = ba_message.lastIndexOf('\n', maxLen - 1);
|
||||
|
||||
if (splitPos <= 0) {
|
||||
splitPos = ba_message.lastIndexOf(' ', maxLen - 1);
|
||||
}
|
||||
|
||||
if (splitPos <= 0) {
|
||||
constexpr uint8_t firstOfMultiByteMask = 0xC0;
|
||||
constexpr uint8_t multiByteMask = 0x80;
|
||||
splitPos = maxLen;
|
||||
// don't split a utf8 character
|
||||
if ((ba_message[splitPos] & multiByteMask) == multiByteMask) {
|
||||
while ((ba_message[splitPos] & firstOfMultiByteMask) != firstOfMultiByteMask) {
|
||||
--splitPos;
|
||||
}
|
||||
}
|
||||
--splitPos;
|
||||
}
|
||||
splittedMsgs.append(QString{ba_message.left(splitPos + 1)});
|
||||
ba_message = ba_message.mid(splitPos + 1);
|
||||
}
|
||||
|
||||
splittedMsgs.append(QString{ba_message});
|
||||
return splittedMsgs;
|
||||
return tox_max_message_length() - 50;
|
||||
}
|
||||
|
||||
QString Core::getPeerName(const ToxPk& id) const
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
|
||||
class CoreAV;
|
||||
class CoreFile;
|
||||
class CoreExt;
|
||||
class IAudioControl;
|
||||
class ICoreSettings;
|
||||
class GroupInvite;
|
||||
|
@ -79,10 +80,12 @@ public:
|
|||
Tox* getTox() const;
|
||||
QMutex& getCoreLoopLock() const;
|
||||
|
||||
const CoreExt* getExt() const;
|
||||
CoreExt* getExt();
|
||||
~Core();
|
||||
|
||||
static const QString TOX_EXT;
|
||||
static QStringList splitMessage(const QString& message);
|
||||
uint64_t getMaxMessageSize() const;
|
||||
QString getPeerName(const ToxPk& id) const;
|
||||
QVector<uint32_t> getFriendList() const;
|
||||
GroupId getGroupPersistentId(uint32_t groupNumber) const override;
|
||||
|
@ -215,6 +218,9 @@ private:
|
|||
size_t length, void* core);
|
||||
static void onGroupTitleChange(Tox* tox, uint32_t groupId, uint32_t peerId,
|
||||
const uint8_t* cTitle, size_t length, void* vCore);
|
||||
|
||||
static void onLosslessPacket(Tox* tox, uint32_t friendId,
|
||||
const uint8_t* data, size_t length, void* core);
|
||||
static void onReadReceiptCallback(Tox* tox, uint32_t friendId, uint32_t receipt, void* core);
|
||||
|
||||
void sendGroupMessageWithType(int groupId, const QString& message, Tox_Message_Type type);
|
||||
|
@ -249,6 +255,7 @@ private:
|
|||
|
||||
std::unique_ptr<CoreFile> file;
|
||||
CoreAV* av = nullptr;
|
||||
std::unique_ptr<CoreExt> ext;
|
||||
QTimer* toxTimer = nullptr;
|
||||
// recursive, since we might call our own functions
|
||||
mutable QMutex coreLoopLock{QMutex::Recursive};
|
||||
|
|
191
src/core/coreext.cpp
Normal file
191
src/core/coreext.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
Copyright © 2019-2020 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "coreext.h"
|
||||
#include "toxstring.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QTimeZone>
|
||||
#include <QtCore>
|
||||
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
|
||||
extern "C" {
|
||||
#include <toxext/toxext.h>
|
||||
#include <tox_extension_messages.h>
|
||||
}
|
||||
|
||||
std::unique_ptr<CoreExt> CoreExt::makeCoreExt(Tox* core) {
|
||||
auto toxExtPtr = toxext_init(core);
|
||||
if (!toxExtPtr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto toxExt = ExtensionPtr<ToxExt>(toxExtPtr, toxext_free);
|
||||
return std::unique_ptr<CoreExt>(new CoreExt(std::move(toxExt)));
|
||||
}
|
||||
|
||||
CoreExt::CoreExt(ExtensionPtr<ToxExt> toxExt_)
|
||||
: toxExt(std::move(toxExt_))
|
||||
, toxExtMessages(nullptr, nullptr)
|
||||
{
|
||||
toxExtMessages = ExtensionPtr<ToxExtensionMessages>(
|
||||
tox_extension_messages_register(
|
||||
toxExt.get(),
|
||||
CoreExt::onExtendedMessageReceived,
|
||||
CoreExt::onExtendedMessageReceipt,
|
||||
CoreExt::onExtendedMessageNegotiation,
|
||||
this,
|
||||
TOX_EXTENSION_MESSAGES_DEFAULT_MAX_RECEIVING_MESSAGE_SIZE),
|
||||
tox_extension_messages_free);
|
||||
}
|
||||
|
||||
void CoreExt::process()
|
||||
{
|
||||
toxext_iterate(toxExt.get());
|
||||
}
|
||||
|
||||
void CoreExt::onLosslessPacket(uint32_t friendId, const uint8_t* data, size_t length)
|
||||
{
|
||||
if (is_toxext_packet(data, length)) {
|
||||
toxext_handle_lossless_custom_packet(toxExt.get(), friendId, data, length);
|
||||
}
|
||||
}
|
||||
|
||||
CoreExt::Packet::Packet(
|
||||
ToxExtPacketList* packetList,
|
||||
ToxExtensionMessages* toxExtMessages,
|
||||
uint32_t friendId,
|
||||
PacketPassKey)
|
||||
: toxExtMessages(toxExtMessages)
|
||||
, packetList(packetList)
|
||||
, friendId(friendId)
|
||||
{}
|
||||
|
||||
std::unique_ptr<ICoreExtPacket> CoreExt::getPacket(uint32_t friendId)
|
||||
{
|
||||
return std::unique_ptr<Packet>(new Packet(
|
||||
toxext_packet_list_create(toxExt.get(), friendId),
|
||||
toxExtMessages.get(),
|
||||
friendId,
|
||||
PacketPassKey{}));
|
||||
}
|
||||
|
||||
uint64_t CoreExt::Packet::addExtendedMessage(QString message)
|
||||
{
|
||||
if (hasBeenSent) {
|
||||
assert(false);
|
||||
qWarning() << "Invalid use of CoreExt::Packet";
|
||||
// Hope that UINT64_MAX will never collide with an actual receipt num
|
||||
// that we care about
|
||||
return UINT64_MAX;
|
||||
}
|
||||
|
||||
int size = message.toUtf8().size();
|
||||
enum Tox_Extension_Messages_Error err;
|
||||
auto maxSize = static_cast<int>(tox_extension_messages_get_max_sending_size(
|
||||
toxExtMessages,
|
||||
friendId,
|
||||
&err));
|
||||
|
||||
if (size > maxSize) {
|
||||
assert(false);
|
||||
qCritical() << "addExtendedMessage called with message of size:" << size
|
||||
<< "when max is:" << maxSize << ". Ignoring.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ToxString toxString(message);
|
||||
const auto receipt = tox_extension_messages_append(
|
||||
toxExtMessages,
|
||||
packetList,
|
||||
toxString.data(),
|
||||
toxString.size(),
|
||||
friendId,
|
||||
&err);
|
||||
|
||||
if (err != TOX_EXTENSION_MESSAGES_SUCCESS) {
|
||||
qWarning() << "Error sending extension message";
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
|
||||
bool CoreExt::Packet::send()
|
||||
{
|
||||
auto ret = toxext_send(packetList);
|
||||
if (ret != TOXEXT_SUCCESS) {
|
||||
qWarning() << "Failed to send packet";
|
||||
}
|
||||
// Indicate we've sent the packet even on failure since our packetlist will
|
||||
// be invalid no matter what
|
||||
hasBeenSent = true;
|
||||
return ret == TOXEXT_SUCCESS;
|
||||
}
|
||||
|
||||
uint64_t CoreExt::getMaxExtendedMessageSize()
|
||||
{
|
||||
return TOX_EXTENSION_MESSAGES_DEFAULT_MAX_RECEIVING_MESSAGE_SIZE;
|
||||
}
|
||||
|
||||
void CoreExt::onFriendStatusChanged(uint32_t friendId, Status::Status status)
|
||||
{
|
||||
const auto prevStatusIt = currentStatuses.find(friendId);
|
||||
const auto prevStatus = prevStatusIt == currentStatuses.end()
|
||||
? Status::Status::Offline : prevStatusIt->second;
|
||||
|
||||
currentStatuses[friendId] = status;
|
||||
|
||||
// Does not depend on prevStatus since prevStatus could be newly
|
||||
// constructed. In this case we should still ensure the rest of the system
|
||||
// knows there is no extension support
|
||||
if (status == Status::Status::Offline) {
|
||||
emit extendedMessageSupport(friendId, false);
|
||||
} else if (prevStatus == Status::Status::Offline) {
|
||||
tox_extension_messages_negotiate(toxExtMessages.get(), friendId);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreExt::onExtendedMessageReceived(uint32_t friendId, const uint8_t* data, size_t size, void* userData)
|
||||
{
|
||||
QString msg = ToxString(data, size).getQString();
|
||||
emit static_cast<CoreExt*>(userData)->extendedMessageReceived(friendId, msg);
|
||||
}
|
||||
|
||||
void CoreExt::onExtendedMessageReceipt(uint32_t friendId, uint64_t receiptId, void* userData)
|
||||
{
|
||||
emit static_cast<CoreExt*>(userData)->extendedReceiptReceived(friendId, receiptId);
|
||||
}
|
||||
|
||||
void CoreExt::onExtendedMessageNegotiation(uint32_t friendId, bool compatible, uint64_t maxMessageSize, void* userData)
|
||||
{
|
||||
auto coreExt = static_cast<CoreExt*>(userData);
|
||||
|
||||
// HACK: handling configurable max message size per-friend is not trivial.
|
||||
// For now the upper layers just assume that the max size for extended
|
||||
// messages is the same for all friends. If a friend has a max message size
|
||||
// lower than this value we just pretend they do not have the extension since
|
||||
// we will not split correctly for this friend.
|
||||
if (maxMessageSize < coreExt->getMaxExtendedMessageSize())
|
||||
compatible = false;
|
||||
|
||||
emit coreExt->extendedMessageSupport(friendId, compatible);
|
||||
}
|
||||
|
147
src/core/coreext.h
Normal file
147
src/core/coreext.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
Copyright © 2019-2020 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "src/model/status.h"
|
||||
#include "icoreextpacket.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
struct Tox;
|
||||
struct ToxExt;
|
||||
struct ToxExtensionMessages;
|
||||
struct ToxExtPacketList;
|
||||
|
||||
/**
|
||||
* Bridge between the toxext library and the rest of qTox.
|
||||
*/
|
||||
class CoreExt : public QObject, public ICoreExtPacketAllocator
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
// PassKey idiom to prevent others from making PacketBuilders
|
||||
struct PacketPassKey {};
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Creates a CoreExt instance. Using a pointer here makes our
|
||||
* registrations with extensions significantly easier to manage
|
||||
*
|
||||
* @param[in] pointer to core tox instance
|
||||
* @return CoreExt on success, nullptr on failure
|
||||
*/
|
||||
static std::unique_ptr<CoreExt> makeCoreExt(Tox* core);
|
||||
|
||||
// We do registration with our own pointer, need to ensure we're in a stable location
|
||||
CoreExt(CoreExt const& other) = delete;
|
||||
CoreExt(CoreExt&& other) = delete;
|
||||
CoreExt& operator=(CoreExt const& other) = delete;
|
||||
CoreExt& operator=(CoreExt&& other) = delete;
|
||||
|
||||
/**
|
||||
* @brief Periodic service function
|
||||
*/
|
||||
void process();
|
||||
|
||||
/**
|
||||
* @brief Handles extension related lossless packets
|
||||
* @param[in] friendId Core id of friend
|
||||
* @param[in] data Packet data
|
||||
* @param[in] length Length of packet data
|
||||
*/
|
||||
void onLosslessPacket(uint32_t friendId, const uint8_t* data, size_t length);
|
||||
|
||||
/**
|
||||
* See documentation of ICoreExtPacket
|
||||
*/
|
||||
class Packet : public ICoreExtPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Internal constructor for a packet.
|
||||
*/
|
||||
Packet(
|
||||
ToxExtPacketList* packetList,
|
||||
ToxExtensionMessages* toxExtMessages,
|
||||
uint32_t friendId,
|
||||
PacketPassKey);
|
||||
|
||||
// Delete copy constructor, we shouldn't be able to copy
|
||||
Packet(Packet const& other) = delete;
|
||||
|
||||
Packet(Packet&& other)
|
||||
{
|
||||
toxExtMessages = other.toxExtMessages;
|
||||
packetList = other.packetList;
|
||||
friendId = other.friendId;
|
||||
hasBeenSent = other.hasBeenSent;
|
||||
other.toxExtMessages = nullptr;
|
||||
other.packetList = nullptr;
|
||||
other.friendId = 0;
|
||||
other.hasBeenSent = false;
|
||||
}
|
||||
|
||||
uint64_t addExtendedMessage(QString message) override;
|
||||
|
||||
bool send() override;
|
||||
private:
|
||||
bool hasBeenSent = false;
|
||||
// Note: non-owning pointer
|
||||
ToxExtensionMessages* toxExtMessages;
|
||||
// Note: packetList is freed on send() call
|
||||
ToxExtPacketList* packetList;
|
||||
uint32_t friendId;
|
||||
};
|
||||
|
||||
std::unique_ptr<ICoreExtPacket> getPacket(uint32_t friendId) override;
|
||||
|
||||
uint64_t getMaxExtendedMessageSize();
|
||||
|
||||
signals:
|
||||
void extendedMessageReceived(uint32_t friendId, const QString& message);
|
||||
void extendedReceiptReceived(uint32_t friendId, uint64_t receiptId);
|
||||
void extendedMessageSupport(uint32_t friendId, bool supported);
|
||||
|
||||
public slots:
|
||||
void onFriendStatusChanged(uint32_t friendId, Status::Status status);
|
||||
|
||||
private:
|
||||
|
||||
static void onExtendedMessageReceived(uint32_t friendId, const uint8_t* data, size_t size, void* userData);
|
||||
static void onExtendedMessageReceipt(uint32_t friendId, uint64_t receiptId, void* userData);
|
||||
static void onExtendedMessageNegotiation(uint32_t friendId, bool compatible, uint64_t maxMessageSize, void* userData);
|
||||
|
||||
// A little extra cost to hide the deleters, but this lets us fwd declare
|
||||
// and prevent any extension headers from leaking out to the rest of the
|
||||
// system
|
||||
template <class T>
|
||||
using ExtensionPtr = std::unique_ptr<T, void(*)(T*)>;
|
||||
|
||||
CoreExt(ExtensionPtr<ToxExt> toxExt);
|
||||
|
||||
std::unordered_map<uint32_t, Status::Status> currentStatuses;
|
||||
ExtensionPtr<ToxExt> toxExt;
|
||||
ExtensionPtr<ToxExtensionMessages> toxExtMessages;
|
||||
};
|
32
src/core/extension.h
Normal file
32
src/core/extension.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright © 2019 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
|
||||
// Do not use enum class because we use these as indexes frequently (see ExtensionSet)
|
||||
struct ExtensionType
|
||||
{
|
||||
enum {
|
||||
messages,
|
||||
max
|
||||
};
|
||||
};
|
||||
using ExtensionSet = std::bitset<ExtensionType::max>;
|
68
src/core/icoreextpacket.h
Normal file
68
src/core/icoreextpacket.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
Copyright © 2019 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* Abstraction around the toxext packet. The toxext flow is to allow several extensions
|
||||
* to tack onto the same packet before sending it to avoid needing the toxext overhead
|
||||
* for every single extension. This abstraction models a toxext packet list.
|
||||
*
|
||||
* Intent is to retrieve a ICoreExtPacket from an ICoreExtPacketAllocator, append all
|
||||
* relevant extension data, and then finally send the packet. After sending the packet
|
||||
* is no longer guaranteed to be valid.
|
||||
*/
|
||||
class ICoreExtPacket
|
||||
{
|
||||
public:
|
||||
virtual ~ICoreExtPacket() = default;
|
||||
|
||||
/**
|
||||
* @brief Adds message to packet
|
||||
* @return Extended message receipt, UINT64_MAX on failure
|
||||
* @note Any other extensions related to this message have to be added
|
||||
* _before_ the message itself
|
||||
*/
|
||||
virtual uint64_t addExtendedMessage(QString message) = 0;
|
||||
|
||||
/**
|
||||
* @brief Consumes the packet constructed with PacketBuilder packet and
|
||||
* sends it to toxext
|
||||
*/
|
||||
virtual bool send() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider of toxext packets
|
||||
*/
|
||||
class ICoreExtPacketAllocator
|
||||
{
|
||||
public:
|
||||
virtual ~ICoreExtPacketAllocator() = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a new packet builder for friend with core friend id friendId
|
||||
*/
|
||||
virtual std::unique_ptr<ICoreExtPacket> getPacket(uint32_t friendId) = 0;
|
||||
};
|
|
@ -26,3 +26,6 @@
|
|||
|
||||
using ReceiptNum = NamedType<uint32_t, struct ReceiptNumTag, Orderable>;
|
||||
Q_DECLARE_METATYPE(ReceiptNum)
|
||||
|
||||
using ExtendedReceiptNum = NamedType<uint32_t, struct ExtendedReceiptNumTag, Orderable>;
|
||||
Q_DECLARE_METATYPE(ExtendedReceiptNum);
|
||||
|
|
27
src/model/brokenmessagereason.h
Normal file
27
src/model/brokenmessagereason.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Copyright © 2015-2019 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// NOTE: Numbers are important here as this is cast to an int and persisted in the DB
|
||||
enum class BrokenMessageReason : int
|
||||
{
|
||||
unknown = 0,
|
||||
unsupportedExtensions = 1
|
||||
};
|
|
@ -82,6 +82,8 @@ ChatHistory::ChatHistory(Friend& f_, History* history_, const ICoreIdHandler& co
|
|||
&ChatHistory::onMessageComplete);
|
||||
connect(&messageDispatcher, &IMessageDispatcher::messageReceived, this,
|
||||
&ChatHistory::onMessageReceived);
|
||||
connect(&messageDispatcher, &IMessageDispatcher::messageBroken, this,
|
||||
&ChatHistory::onMessageBroken);
|
||||
|
||||
if (canUseHistory()) {
|
||||
// Defer messageSent callback until we finish firing off all our unsent messages.
|
||||
|
@ -266,7 +268,7 @@ void ChatHistory::onMessageReceived(const ToxPk& sender, const Message& message)
|
|||
content = ChatForm::ACTION_PREFIX + content;
|
||||
}
|
||||
|
||||
history->addNewMessage(friendPk, content, friendPk, message.timestamp, true, displayName);
|
||||
history->addNewMessage(friendPk, content, friendPk, message.timestamp, true, message.extensionSet, displayName);
|
||||
}
|
||||
|
||||
sessionChatLog.onMessageReceived(sender, message);
|
||||
|
@ -287,7 +289,7 @@ void ChatHistory::onMessageSent(DispatchedMessageId id, const Message& message)
|
|||
|
||||
auto onInsertion = [this, id](RowId historyId) { handleDispatchedMessage(id, historyId); };
|
||||
|
||||
history->addNewMessage(friendPk, content, selfPk, message.timestamp, false, username,
|
||||
history->addNewMessage(friendPk, content, selfPk, message.timestamp, false, message.extensionSet, username,
|
||||
onInsertion);
|
||||
}
|
||||
|
||||
|
@ -303,6 +305,15 @@ void ChatHistory::onMessageComplete(DispatchedMessageId id)
|
|||
sessionChatLog.onMessageComplete(id);
|
||||
}
|
||||
|
||||
void ChatHistory::onMessageBroken(DispatchedMessageId id, BrokenMessageReason reason)
|
||||
{
|
||||
if (canUseHistory()) {
|
||||
breakMessage(id, reason);
|
||||
}
|
||||
|
||||
sessionChatLog.onMessageBroken(id, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forces the given index and all future indexes to be in the chatlog
|
||||
* @param[in] idx
|
||||
|
@ -405,6 +416,13 @@ void ChatHistory::loadHistoryIntoSessionChatLog(ChatLogIdx start) const
|
|||
void ChatHistory::dispatchUnsentMessages(IMessageDispatcher& messageDispatcher)
|
||||
{
|
||||
auto unsentMessages = history->getUndeliveredMessagesForFriend(f.getPublicKey());
|
||||
|
||||
auto requiredExtensions = std::accumulate(
|
||||
unsentMessages.begin(), unsentMessages.end(),
|
||||
ExtensionSet(), [] (const ExtensionSet& a, const History::HistMessage& b) {
|
||||
return a | b.extensionSet;
|
||||
});
|
||||
|
||||
for (auto& message : unsentMessages) {
|
||||
// We should only store messages as unsent, if this changes in the
|
||||
// future we need to extend this logic
|
||||
|
@ -418,12 +436,14 @@ void ChatHistory::dispatchUnsentMessages(IMessageDispatcher& messageDispatcher)
|
|||
// with the new timestamp. This is intentional as everywhere else we use
|
||||
// attempted send time (which is whenever the it was initially inserted
|
||||
// into history
|
||||
auto dispatchIds = messageDispatcher.sendMessage(isAction, messageContent);
|
||||
auto dispatchId = requiredExtensions.none()
|
||||
// We should only send a single message, but in the odd case where we end
|
||||
// up having to split more than when we added the message to history we'll
|
||||
// just associate the last dispatched id with the history message
|
||||
? messageDispatcher.sendMessage(isAction, messageContent).second
|
||||
: messageDispatcher.sendExtendedMessage(messageContent, requiredExtensions).second;
|
||||
|
||||
// We should only send a single message, but in the odd case where we end
|
||||
// up having to split more than when we added the message to history we'll
|
||||
// just associate the last dispatched id with the history message
|
||||
handleDispatchedMessage(dispatchIds.second, message.id);
|
||||
handleDispatchedMessage(dispatchId, message.id);
|
||||
|
||||
// We don't add the messages to the underlying chatlog since
|
||||
// 1. We don't even know the ChatLogIdx of this message
|
||||
|
@ -435,11 +455,20 @@ void ChatHistory::dispatchUnsentMessages(IMessageDispatcher& messageDispatcher)
|
|||
void ChatHistory::handleDispatchedMessage(DispatchedMessageId dispatchId, RowId historyId)
|
||||
{
|
||||
auto completedMessageIt = completedMessages.find(dispatchId);
|
||||
if (completedMessageIt == completedMessages.end()) {
|
||||
dispatchedMessageRowIdMap.insert(dispatchId, historyId);
|
||||
} else {
|
||||
auto brokenMessageIt = brokenMessages.find(dispatchId);
|
||||
|
||||
const auto isCompleted = completedMessageIt != completedMessages.end();
|
||||
const auto isBroken = brokenMessageIt != brokenMessages.end();
|
||||
assert(!(isCompleted && isBroken));
|
||||
|
||||
if (isCompleted) {
|
||||
history->markAsDelivered(historyId);
|
||||
completedMessages.erase(completedMessageIt);
|
||||
} else if (isBroken) {
|
||||
history->markAsBroken(historyId, brokenMessageIt.value());
|
||||
brokenMessages.erase(brokenMessageIt);
|
||||
} else {
|
||||
dispatchedMessageRowIdMap.insert(dispatchId, historyId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,6 +484,18 @@ void ChatHistory::completeMessage(DispatchedMessageId id)
|
|||
}
|
||||
}
|
||||
|
||||
void ChatHistory::breakMessage(DispatchedMessageId id, BrokenMessageReason reason)
|
||||
{
|
||||
auto dispatchedMessageIt = dispatchedMessageRowIdMap.find(id);
|
||||
|
||||
if (dispatchedMessageIt == dispatchedMessageRowIdMap.end()) {
|
||||
brokenMessages.insert(id, reason);
|
||||
} else {
|
||||
history->markAsBroken(*dispatchedMessageIt, reason);
|
||||
dispatchedMessageRowIdMap.erase(dispatchedMessageIt);
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatHistory::canUseHistory() const
|
||||
{
|
||||
return history && settings.getEnableLogging();
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "ichatlog.h"
|
||||
#include "sessionchatlog.h"
|
||||
#include "src/model/brokenmessagereason.h"
|
||||
#include "src/persistence/history.h"
|
||||
|
||||
#include <QSet>
|
||||
|
@ -51,6 +52,7 @@ private slots:
|
|||
void onMessageReceived(const ToxPk& sender, const Message& message);
|
||||
void onMessageSent(DispatchedMessageId id, const Message& message);
|
||||
void onMessageComplete(DispatchedMessageId id);
|
||||
void onMessageBroken(DispatchedMessageId id, BrokenMessageReason reason);
|
||||
|
||||
private:
|
||||
void ensureIdxInSessionChatLog(ChatLogIdx idx) const;
|
||||
|
@ -58,6 +60,7 @@ private:
|
|||
void dispatchUnsentMessages(IMessageDispatcher& messageDispatcher);
|
||||
void handleDispatchedMessage(DispatchedMessageId dispatchId, RowId historyId);
|
||||
void completeMessage(DispatchedMessageId id);
|
||||
void breakMessage(DispatchedMessageId id, BrokenMessageReason reason);
|
||||
bool canUseHistory() const;
|
||||
ChatLogIdx getInitialChatLogIdx() const;
|
||||
|
||||
|
@ -70,6 +73,9 @@ private:
|
|||
// If a message completes before it's inserted into history it will end up
|
||||
// in this set
|
||||
QSet<DispatchedMessageId> completedMessages;
|
||||
// If a message breaks before it's inserted into history it will end up
|
||||
// in this set
|
||||
QMap<DispatchedMessageId, BrokenMessageReason> brokenMessages;
|
||||
|
||||
// If a message is inserted into history before it gets a completion
|
||||
// callback it will end up in this map
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
#include "src/persistence/profile.h"
|
||||
#include "src/widget/form/chatform.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <memory>
|
||||
|
||||
Friend::Friend(uint32_t friendId, const ToxPk& friendPk, const QString& userAlias, const QString& userName)
|
||||
: userName{userName}
|
||||
, userAlias{userAlias}
|
||||
|
@ -30,6 +33,7 @@ Friend::Friend(uint32_t friendId, const ToxPk& friendPk, const QString& userAlia
|
|||
, friendId{friendId}
|
||||
, hasNewEvents{false}
|
||||
, friendStatus{Status::Status::Offline}
|
||||
, isNegotiating{false}
|
||||
{
|
||||
if (userName.isEmpty()) {
|
||||
this->userName = friendPk.toString();
|
||||
|
@ -151,25 +155,75 @@ bool Friend::getEventFlag() const
|
|||
|
||||
void Friend::setStatus(Status::Status s)
|
||||
{
|
||||
if (friendStatus != s) {
|
||||
auto oldStatus = friendStatus;
|
||||
friendStatus = s;
|
||||
emit statusChanged(friendPk, friendStatus);
|
||||
if (!Status::isOnline(oldStatus) && Status::isOnline(friendStatus)) {
|
||||
emit onlineOfflineChanged(friendPk, true);
|
||||
} else if (Status::isOnline(oldStatus) && !Status::isOnline(friendStatus)) {
|
||||
emit onlineOfflineChanged(friendPk, false);
|
||||
}
|
||||
// Internal status should never be negotiating. We only expose this externally through the use of isNegotiating
|
||||
assert(s != Status::Status::Negotiating);
|
||||
|
||||
const bool wasOnline = Status::isOnline(getStatus());
|
||||
if (friendStatus == s) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When a friend goes online we want to give them some time to negotiate
|
||||
// extension support
|
||||
const auto startNegotating = friendStatus == Status::Status::Offline;
|
||||
|
||||
if (startNegotating) {
|
||||
qDebug() << "Starting negotiation with friend " << friendId;
|
||||
isNegotiating = true;
|
||||
}
|
||||
|
||||
friendStatus = s;
|
||||
const bool nowOnline = Status::isOnline(getStatus());
|
||||
|
||||
const auto emitStatusChange = startNegotating || !isNegotiating;
|
||||
if (emitStatusChange) {
|
||||
const auto statusToEmit = isNegotiating ? Status::Status::Negotiating : friendStatus;
|
||||
emit statusChanged(friendPk, statusToEmit);
|
||||
if (wasOnline && !nowOnline) {
|
||||
emit onlineOfflineChanged(friendPk, false);
|
||||
} else if (!wasOnline && nowOnline) {
|
||||
emit onlineOfflineChanged(friendPk, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status::Status Friend::getStatus() const
|
||||
{
|
||||
return friendStatus;
|
||||
return isNegotiating ? Status::Status::Negotiating : friendStatus;
|
||||
}
|
||||
|
||||
bool Friend::useHistory() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Friend::setExtendedMessageSupport(bool supported)
|
||||
{
|
||||
supportedExtensions[ExtensionType::messages] = supported;
|
||||
emit extensionSupportChanged(supportedExtensions);
|
||||
|
||||
// If all extensions are supported we can exit early
|
||||
if (supportedExtensions.all()) {
|
||||
onNegotiationComplete();
|
||||
}
|
||||
}
|
||||
|
||||
ExtensionSet Friend::getSupportedExtensions() const
|
||||
{
|
||||
return supportedExtensions;
|
||||
}
|
||||
|
||||
void Friend::onNegotiationComplete() {
|
||||
if (!isNegotiating) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Negotiation complete for friend " << friendId;
|
||||
|
||||
isNegotiating = false;
|
||||
emit statusChanged(friendPk, friendStatus);
|
||||
|
||||
if (Status::isOnline(getStatus())) {
|
||||
emit onlineOfflineChanged(friendPk, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "contact.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/extension.h"
|
||||
#include "src/core/toxid.h"
|
||||
#include "src/core/contactid.h"
|
||||
#include "src/model/status.h"
|
||||
|
@ -50,20 +51,25 @@ public:
|
|||
uint32_t getId() const override;
|
||||
const ContactId& getPersistentId() const override;
|
||||
|
||||
void finishNegotiation();
|
||||
void setStatus(Status::Status s);
|
||||
Status::Status getStatus() const;
|
||||
bool useHistory() const final;
|
||||
|
||||
void setExtendedMessageSupport(bool supported);
|
||||
ExtensionSet getSupportedExtensions() const;
|
||||
|
||||
signals:
|
||||
void nameChanged(const ToxPk& friendId, const QString& name);
|
||||
void aliasChanged(const ToxPk& friendId, QString alias);
|
||||
void statusChanged(const ToxPk& friendId, Status::Status status);
|
||||
void onlineOfflineChanged(const ToxPk& friendId, bool isOnline);
|
||||
void statusMessageChanged(const ToxPk& friendId, const QString& message);
|
||||
void extensionSupportChanged(ExtensionSet extensions);
|
||||
void loadChatHistory();
|
||||
|
||||
public slots:
|
||||
|
||||
void onNegotiationComplete();
|
||||
private:
|
||||
QString userName;
|
||||
QString userAlias;
|
||||
|
@ -72,4 +78,6 @@ private:
|
|||
uint32_t friendId;
|
||||
bool hasNewEvents;
|
||||
Status::Status friendStatus;
|
||||
bool isNegotiating;
|
||||
ExtensionSet supportedExtensions;
|
||||
};
|
||||
|
|
|
@ -21,64 +21,52 @@
|
|||
#include "src/persistence/settings.h"
|
||||
#include "src/model/status.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief Sends message to friend using messageSender
|
||||
* @param[in] messageSender
|
||||
* @param[in] f
|
||||
* @param[in] message
|
||||
* @param[out] receipt
|
||||
*/
|
||||
bool sendMessageToCore(ICoreFriendMessageSender& messageSender, const Friend& f,
|
||||
const Message& message, ReceiptNum& receipt)
|
||||
{
|
||||
uint32_t friendId = f.getId();
|
||||
|
||||
auto sendFn = message.isAction ? std::mem_fn(&ICoreFriendMessageSender::sendAction)
|
||||
: std::mem_fn(&ICoreFriendMessageSender::sendMessage);
|
||||
|
||||
return sendFn(messageSender, friendId, message.content, receipt);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
FriendMessageDispatcher::FriendMessageDispatcher(Friend& f_, MessageProcessor processor_,
|
||||
ICoreFriendMessageSender& messageSender_)
|
||||
ICoreFriendMessageSender& messageSender_,
|
||||
ICoreExtPacketAllocator& coreExtPacketAllocator_)
|
||||
: f(f_)
|
||||
, messageSender(messageSender_)
|
||||
, offlineMsgEngine(&f_, &messageSender_)
|
||||
, processor(std::move(processor_))
|
||||
, coreExtPacketAllocator(coreExtPacketAllocator_)
|
||||
{
|
||||
connect(&f, &Friend::onlineOfflineChanged, this, &FriendMessageDispatcher::onFriendOnlineOfflineChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see IMessageSender::sendMessage
|
||||
* @see IMessageDispatcher::sendMessage
|
||||
*/
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId>
|
||||
FriendMessageDispatcher::sendMessage(bool isAction, const QString& content)
|
||||
{
|
||||
const auto firstId = nextMessageId;
|
||||
auto lastId = nextMessageId;
|
||||
for (const auto& message : processor.processOutgoingMessage(isAction, content)) {
|
||||
for (const auto& message : processor.processOutgoingMessage(isAction, content, f.getSupportedExtensions())) {
|
||||
auto messageId = nextMessageId++;
|
||||
lastId = messageId;
|
||||
auto onOfflineMsgComplete = [this, messageId] { emit this->messageComplete(messageId); };
|
||||
|
||||
ReceiptNum receipt;
|
||||
auto onOfflineMsgComplete = getCompletionFn(messageId);
|
||||
sendProcessedMessage(message, onOfflineMsgComplete);
|
||||
|
||||
bool messageSent = false;
|
||||
emit this->messageSent(messageId, message);
|
||||
}
|
||||
return std::make_pair(firstId, lastId);
|
||||
}
|
||||
|
||||
if (Status::isOnline(f.getStatus())) {
|
||||
messageSent = sendMessageToCore(messageSender, f, message, receipt);
|
||||
}
|
||||
/**
|
||||
* @see IMessageDispatcher::sendExtendedMessage
|
||||
*/
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId>
|
||||
FriendMessageDispatcher::sendExtendedMessage(const QString& content, ExtensionSet extensions)
|
||||
{
|
||||
const auto firstId = nextMessageId;
|
||||
auto lastId = nextMessageId;
|
||||
|
||||
if (!messageSent) {
|
||||
offlineMsgEngine.addUnsentMessage(message, onOfflineMsgComplete);
|
||||
} else {
|
||||
offlineMsgEngine.addSentMessage(receipt, message, onOfflineMsgComplete);
|
||||
}
|
||||
for (const auto& message : processor.processOutgoingMessage(false, content, extensions)) {
|
||||
auto messageId = nextMessageId++;
|
||||
lastId = messageId;
|
||||
|
||||
auto onOfflineMsgComplete = getCompletionFn(messageId);
|
||||
sendProcessedMessage(message, onOfflineMsgComplete);
|
||||
|
||||
emit this->messageSent(messageId, message);
|
||||
}
|
||||
|
@ -92,7 +80,7 @@ FriendMessageDispatcher::sendMessage(bool isAction, const QString& content)
|
|||
*/
|
||||
void FriendMessageDispatcher::onMessageReceived(bool isAction, const QString& content)
|
||||
{
|
||||
emit this->messageReceived(f.getPublicKey(), processor.processIncomingMessage(isAction, content));
|
||||
emit this->messageReceived(f.getPublicKey(), processor.processIncomingCoreMessage(isAction, content));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,6 +92,17 @@ void FriendMessageDispatcher::onReceiptReceived(ReceiptNum receipt)
|
|||
offlineMsgEngine.onReceiptReceived(receipt);
|
||||
}
|
||||
|
||||
void FriendMessageDispatcher::onExtMessageReceived(const QString& content)
|
||||
{
|
||||
auto message = processor.processIncomingExtMessage(content);
|
||||
emit this->messageReceived(f.getPublicKey(), message);
|
||||
}
|
||||
|
||||
void FriendMessageDispatcher::onExtReceiptReceived(uint64_t receiptId)
|
||||
{
|
||||
offlineMsgEngine.onExtendedReceiptReceived(ExtendedReceiptNum(receiptId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles status change for friend
|
||||
* @note Parameters just to fit slot api
|
||||
|
@ -111,7 +110,10 @@ void FriendMessageDispatcher::onReceiptReceived(ReceiptNum receipt)
|
|||
void FriendMessageDispatcher::onFriendOnlineOfflineChanged(const ToxPk&, bool isOnline)
|
||||
{
|
||||
if (isOnline) {
|
||||
offlineMsgEngine.deliverOfflineMsgs();
|
||||
auto messagesToResend = offlineMsgEngine.removeAllMessages();
|
||||
for (auto const& message : messagesToResend) {
|
||||
sendProcessedMessage(message.message, message.callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,3 +124,78 @@ void FriendMessageDispatcher::clearOutgoingMessages()
|
|||
{
|
||||
offlineMsgEngine.removeAllMessages();
|
||||
}
|
||||
|
||||
|
||||
void FriendMessageDispatcher::sendProcessedMessage(Message const& message, OfflineMsgEngine::CompletionFn onOfflineMsgComplete)
|
||||
{
|
||||
if (!Status::isOnline(f.getStatus())) {
|
||||
offlineMsgEngine.addUnsentMessage(message, onOfflineMsgComplete);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.extensionSet[ExtensionType::messages] && !message.isAction) {
|
||||
sendExtendedProcessedMessage(message, onOfflineMsgComplete);
|
||||
} else {
|
||||
sendCoreProcessedMessage(message, onOfflineMsgComplete);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FriendMessageDispatcher::sendExtendedProcessedMessage(Message const& message, OfflineMsgEngine::CompletionFn onOfflineMsgComplete)
|
||||
{
|
||||
assert(!message.isAction); // Actions not supported with extensions
|
||||
|
||||
if ((f.getSupportedExtensions() & message.extensionSet) != message.extensionSet) {
|
||||
onOfflineMsgComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto receipt = ExtendedReceiptNum();
|
||||
|
||||
const auto friendId = f.getId();
|
||||
auto packet = coreExtPacketAllocator.getPacket(friendId);
|
||||
|
||||
if (message.extensionSet[ExtensionType::messages]) {
|
||||
receipt.get() = packet->addExtendedMessage(message.content);
|
||||
}
|
||||
|
||||
const auto messageSent = packet->send();
|
||||
|
||||
if (messageSent) {
|
||||
offlineMsgEngine.addSentExtendedMessage(receipt, message, onOfflineMsgComplete);
|
||||
} else {
|
||||
offlineMsgEngine.addUnsentMessage(message, onOfflineMsgComplete);
|
||||
}
|
||||
}
|
||||
|
||||
void FriendMessageDispatcher::sendCoreProcessedMessage(Message const& message, OfflineMsgEngine::CompletionFn onOfflineMsgComplete)
|
||||
{
|
||||
auto receipt = ReceiptNum();
|
||||
|
||||
uint32_t friendId = f.getId();
|
||||
|
||||
auto sendFn = message.isAction ? std::mem_fn(&ICoreFriendMessageSender::sendAction)
|
||||
: std::mem_fn(&ICoreFriendMessageSender::sendMessage);
|
||||
|
||||
const auto messageSent = sendFn(messageSender, friendId, message.content, receipt);
|
||||
|
||||
if (messageSent) {
|
||||
offlineMsgEngine.addSentCoreMessage(receipt, message, onOfflineMsgComplete);
|
||||
} else {
|
||||
offlineMsgEngine.addUnsentMessage(message, onOfflineMsgComplete);
|
||||
}
|
||||
}
|
||||
|
||||
OfflineMsgEngine::CompletionFn FriendMessageDispatcher::getCompletionFn(DispatchedMessageId messageId)
|
||||
{
|
||||
return [this, messageId] (bool success) {
|
||||
if (success) {
|
||||
emit this->messageComplete(messageId);
|
||||
} else {
|
||||
// For now we know the only reason we can fail after giving to the
|
||||
// offline message engine is due to a reduced extension set
|
||||
emit this->messageBroken(messageId, BrokenMessageReason::unsupportedExtensions);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,18 +35,29 @@ class FriendMessageDispatcher : public IMessageDispatcher
|
|||
Q_OBJECT
|
||||
public:
|
||||
FriendMessageDispatcher(Friend& f, MessageProcessor processor,
|
||||
ICoreFriendMessageSender& messageSender);
|
||||
ICoreFriendMessageSender& messageSender,
|
||||
ICoreExtPacketAllocator& coreExt);
|
||||
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId> sendMessage(bool isAction,
|
||||
const QString& content) override;
|
||||
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId> sendExtendedMessage(const QString& content, ExtensionSet extensions) override;
|
||||
void onMessageReceived(bool isAction, const QString& content);
|
||||
void onReceiptReceived(ReceiptNum receipt);
|
||||
void onExtMessageReceived(const QString& message);
|
||||
void onExtReceiptReceived(uint64_t receiptId);
|
||||
void clearOutgoingMessages();
|
||||
private slots:
|
||||
void onFriendOnlineOfflineChanged(const ToxPk& key, bool isOnline);
|
||||
|
||||
private:
|
||||
void sendProcessedMessage(Message const& msg, OfflineMsgEngine::CompletionFn fn);
|
||||
void sendExtendedProcessedMessage(Message const& msg, OfflineMsgEngine::CompletionFn fn);
|
||||
void sendCoreProcessedMessage(Message const& msg, OfflineMsgEngine::CompletionFn fn);
|
||||
OfflineMsgEngine::CompletionFn getCompletionFn(DispatchedMessageId messageId);
|
||||
|
||||
Friend& f;
|
||||
ICoreExtPacketAllocator& coreExtPacketAllocator;
|
||||
DispatchedMessageId nextMessageId = DispatchedMessageId(0);
|
||||
|
||||
ICoreFriendMessageSender& messageSender;
|
||||
|
|
|
@ -41,7 +41,7 @@ GroupMessageDispatcher::sendMessage(bool isAction, QString const& content)
|
|||
const auto firstMessageId = nextMessageId;
|
||||
auto lastMessageId = firstMessageId;
|
||||
|
||||
for (auto const& message : processor.processOutgoingMessage(isAction, content)) {
|
||||
for (auto const& message : processor.processOutgoingMessage(isAction, content, ExtensionSet())) {
|
||||
auto messageId = nextMessageId++;
|
||||
lastMessageId = messageId;
|
||||
if (group.getPeersCount() != 1) {
|
||||
|
@ -65,6 +65,17 @@ GroupMessageDispatcher::sendMessage(bool isAction, QString const& content)
|
|||
return std::make_pair(firstMessageId, lastMessageId);
|
||||
}
|
||||
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId>
|
||||
GroupMessageDispatcher::sendExtendedMessage(const QString& content, ExtensionSet extensions)
|
||||
{
|
||||
// Stub this api to immediately fail
|
||||
auto messageId = nextMessageId++;
|
||||
auto messages = processor.processOutgoingMessage(false, content, ExtensionSet());
|
||||
emit this->messageSent(messageId, messages[0]);
|
||||
emit this->messageBroken(messageId, BrokenMessageReason::unsupportedExtensions);
|
||||
return {messageId, messageId};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes and dispatches received message from toxcore
|
||||
* @param[in] sender
|
||||
|
@ -84,5 +95,5 @@ void GroupMessageDispatcher::onMessageReceived(const ToxPk& sender, bool isActio
|
|||
return;
|
||||
}
|
||||
|
||||
emit messageReceived(sender, processor.processIncomingMessage(isAction, content));
|
||||
emit messageReceived(sender, processor.processIncomingCoreMessage(isAction, content));
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ public:
|
|||
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId> sendMessage(bool isAction,
|
||||
QString const& content) override;
|
||||
|
||||
std::pair<DispatchedMessageId, DispatchedMessageId> sendExtendedMessage(const QString& content,
|
||||
ExtensionSet extensions) override;
|
||||
void onMessageReceived(ToxPk const& sender, bool isAction, QString const& content);
|
||||
|
||||
private:
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "src/model/friend.h"
|
||||
#include "src/model/message.h"
|
||||
#include "src/model/brokenmessagereason.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
@ -44,6 +45,18 @@ public:
|
|||
*/
|
||||
virtual std::pair<DispatchedMessageId, DispatchedMessageId>
|
||||
sendMessage(bool isAction, const QString& content) = 0;
|
||||
|
||||
/**
|
||||
* @brief Sends message to associated chat ensuring that extensions are available
|
||||
* @param[in] content Message content
|
||||
* @param[in] extensions extensions required for given message
|
||||
* @return Pair of first and last dispatched message IDs
|
||||
* @note If the provided extensions are not supported the message will be flagged
|
||||
* as broken
|
||||
*/
|
||||
virtual std::pair<DispatchedMessageId, DispatchedMessageId>
|
||||
sendExtendedMessage(const QString& content, ExtensionSet extensions) = 0;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted when a message is received and processed
|
||||
|
@ -62,4 +75,6 @@ signals:
|
|||
* @param id Id of message that is completed
|
||||
*/
|
||||
void messageComplete(DispatchedMessageId id);
|
||||
|
||||
void messageBroken(DispatchedMessageId id, BrokenMessageReason reason);
|
||||
};
|
||||
|
|
|
@ -21,6 +21,40 @@
|
|||
#include "friend.h"
|
||||
#include "src/core/core.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
QStringList splitMessage(const QString& message, uint64_t maxLength)
|
||||
{
|
||||
QStringList splittedMsgs;
|
||||
QByteArray ba_message{message.toUtf8()};
|
||||
while (static_cast<uint64_t>(ba_message.size()) > maxLength) {
|
||||
int splitPos = ba_message.lastIndexOf('\n', maxLength - 1);
|
||||
|
||||
if (splitPos <= 0) {
|
||||
splitPos = ba_message.lastIndexOf(' ', maxLength - 1);
|
||||
}
|
||||
|
||||
if (splitPos <= 0) {
|
||||
constexpr uint8_t firstOfMultiByteMask = 0xC0;
|
||||
constexpr uint8_t multiByteMask = 0x80;
|
||||
splitPos = maxLength;
|
||||
// don't split a utf8 character
|
||||
if ((ba_message[splitPos] & multiByteMask) == multiByteMask) {
|
||||
while ((ba_message[splitPos] & firstOfMultiByteMask) != firstOfMultiByteMask) {
|
||||
--splitPos;
|
||||
}
|
||||
}
|
||||
--splitPos;
|
||||
}
|
||||
splittedMsgs.append(QString{ba_message.left(splitPos + 1)});
|
||||
ba_message = ba_message.mid(splitPos + 1);
|
||||
}
|
||||
|
||||
splittedMsgs.append(QString{ba_message});
|
||||
return splittedMsgs;
|
||||
}
|
||||
}
|
||||
void MessageProcessor::SharedParams::onUserNameSet(const QString& username)
|
||||
{
|
||||
QString sanename = username;
|
||||
|
@ -49,11 +83,16 @@ MessageProcessor::MessageProcessor(const MessageProcessor::SharedParams& sharedP
|
|||
/**
|
||||
* @brief Converts an outgoing message into one (or many) sanitized Message(s)
|
||||
*/
|
||||
std::vector<Message> MessageProcessor::processOutgoingMessage(bool isAction, QString const& content)
|
||||
std::vector<Message> MessageProcessor::processOutgoingMessage(bool isAction, QString const& content, ExtensionSet extensions)
|
||||
{
|
||||
std::vector<Message> ret;
|
||||
|
||||
QStringList splitMsgs = Core::splitMessage(content);
|
||||
const auto maxSendingSize = extensions[ExtensionType::messages]
|
||||
? sharedParams.getMaxExtendedMessageSize()
|
||||
: sharedParams.getMaxCoreMessageSize();
|
||||
|
||||
const auto splitMsgs = splitMessage(content, maxSendingSize);
|
||||
|
||||
ret.reserve(splitMsgs.size());
|
||||
|
||||
QDateTime timestamp = QDateTime::currentDateTime();
|
||||
|
@ -63,17 +102,20 @@ std::vector<Message> MessageProcessor::processOutgoingMessage(bool isAction, QSt
|
|||
message.isAction = isAction;
|
||||
message.content = part;
|
||||
message.timestamp = timestamp;
|
||||
// In theory we could limit this only to the extensions
|
||||
// required but since Core owns the splitting logic it
|
||||
// isn't trivial to do that now
|
||||
message.extensionSet = extensions;
|
||||
return message;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Converts an incoming message into a sanitized Message
|
||||
*/
|
||||
Message MessageProcessor::processIncomingMessage(bool isAction, QString const& message)
|
||||
Message MessageProcessor::processIncomingCoreMessage(bool isAction, QString const& message)
|
||||
{
|
||||
QDateTime timestamp = QDateTime::currentDateTime();
|
||||
auto ret = Message{};
|
||||
|
@ -82,9 +124,9 @@ Message MessageProcessor::processIncomingMessage(bool isAction, QString const& m
|
|||
ret.timestamp = timestamp;
|
||||
|
||||
if (detectingMentions) {
|
||||
auto nameMention = sharedParams.GetNameMention();
|
||||
auto sanitizedNameMention = sharedParams.GetSanitizedNameMention();
|
||||
auto pubKeyMention = sharedParams.GetPublicKeyMention();
|
||||
auto nameMention = sharedParams.getNameMention();
|
||||
auto sanitizedNameMention = sharedParams.getSanitizedNameMention();
|
||||
auto pubKeyMention = sharedParams.getPublicKeyMention();
|
||||
|
||||
for (auto const& mention : {nameMention, sanitizedNameMention, pubKeyMention}) {
|
||||
auto matchIt = mention.globalMatch(ret.content);
|
||||
|
@ -109,3 +151,18 @@ Message MessageProcessor::processIncomingMessage(bool isAction, QString const& m
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Message MessageProcessor::processIncomingExtMessage(const QString& content)
|
||||
{
|
||||
// Note: detectingMentions not implemented here since mentions are only
|
||||
// currently useful in group messages which do not support extensions. If we
|
||||
// were to support mentions we would probably want to do something more
|
||||
// intelligent anyways
|
||||
assert(detectingMentions == false);
|
||||
auto message = Message();
|
||||
message.timestamp = QDateTime::currentDateTime();
|
||||
message.content = content;
|
||||
message.extensionSet |= ExtensionType::messages;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "src/core/coreext.h"
|
||||
#include "src/core/extension.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
@ -26,6 +29,7 @@
|
|||
#include <vector>
|
||||
|
||||
class Friend;
|
||||
class CoreExt;
|
||||
|
||||
// NOTE: This could be extended in the future to handle all text processing (see
|
||||
// ChatMessage::createChatMessage)
|
||||
|
@ -50,6 +54,7 @@ struct Message
|
|||
bool isAction;
|
||||
QString content;
|
||||
QDateTime timestamp;
|
||||
ExtensionSet extensionSet;
|
||||
std::vector<MessageMetadata> metadata;
|
||||
};
|
||||
|
||||
|
@ -66,22 +71,39 @@ public:
|
|||
{
|
||||
|
||||
public:
|
||||
QRegularExpression GetNameMention() const
|
||||
SharedParams(uint64_t maxCoreMessageSize_, uint64_t maxExtendedMessageSize_)
|
||||
: maxCoreMessageSize(maxCoreMessageSize_)
|
||||
, maxExtendedMessageSize(maxExtendedMessageSize_)
|
||||
{}
|
||||
|
||||
QRegularExpression getNameMention() const
|
||||
{
|
||||
return nameMention;
|
||||
}
|
||||
QRegularExpression GetSanitizedNameMention() const
|
||||
QRegularExpression getSanitizedNameMention() const
|
||||
{
|
||||
return sanitizedNameMention;
|
||||
}
|
||||
QRegularExpression GetPublicKeyMention() const
|
||||
QRegularExpression getPublicKeyMention() const
|
||||
{
|
||||
return pubKeyMention;
|
||||
}
|
||||
void onUserNameSet(const QString& username);
|
||||
void setPublicKey(const QString& pk);
|
||||
|
||||
uint64_t getMaxCoreMessageSize() const
|
||||
{
|
||||
return maxCoreMessageSize;
|
||||
}
|
||||
|
||||
uint64_t getMaxExtendedMessageSize() const
|
||||
{
|
||||
return maxExtendedMessageSize;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t maxCoreMessageSize;
|
||||
uint64_t maxExtendedMessageSize;
|
||||
QRegularExpression nameMention;
|
||||
QRegularExpression sanitizedNameMention;
|
||||
QRegularExpression pubKeyMention;
|
||||
|
@ -89,9 +111,9 @@ public:
|
|||
|
||||
MessageProcessor(const SharedParams& sharedParams);
|
||||
|
||||
std::vector<Message> processOutgoingMessage(bool isAction, QString const& content);
|
||||
|
||||
Message processIncomingMessage(bool isAction, QString const& message);
|
||||
std::vector<Message> processOutgoingMessage(bool isAction, const QString& content, ExtensionSet extensions);
|
||||
Message processIncomingCoreMessage(bool isAction, const QString& content);
|
||||
Message processIncomingExtMessage(const QString& content);
|
||||
|
||||
/**
|
||||
* @brief Enables mention detection in the processor
|
||||
|
|
|
@ -420,6 +420,29 @@ void SessionChatLog::onMessageComplete(DispatchedMessageId id)
|
|||
emit this->itemUpdated(messageIt->first);
|
||||
}
|
||||
|
||||
void SessionChatLog::onMessageBroken(DispatchedMessageId id, BrokenMessageReason)
|
||||
{
|
||||
auto chatLogIdxIt = outgoingMessages.find(id);
|
||||
|
||||
if (chatLogIdxIt == outgoingMessages.end()) {
|
||||
qWarning() << "Failed to find outgoing message";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& chatLogIdx = *chatLogIdxIt;
|
||||
auto messageIt = items.find(chatLogIdx);
|
||||
|
||||
if (messageIt == items.end()) {
|
||||
qWarning() << "Failed to look up message in chat log";
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: Reason for broken message not currently shown in UI, but it could be
|
||||
messageIt->second.getContentAsMessage().state = MessageState::broken;
|
||||
|
||||
emit this->itemUpdated(messageIt->first);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates file state in the chatlog
|
||||
* @note The files need to be pre-filtered for the current chat since we do no validation
|
||||
|
|
|
@ -57,6 +57,7 @@ public slots:
|
|||
void onMessageReceived(const ToxPk& sender, const Message& message);
|
||||
void onMessageSent(DispatchedMessageId id, const Message& message);
|
||||
void onMessageComplete(DispatchedMessageId id);
|
||||
void onMessageBroken(DispatchedMessageId id, BrokenMessageReason reason);
|
||||
|
||||
void onFileUpdated(const ToxPk& sender, const ToxFile& file);
|
||||
void onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool paused);
|
||||
|
|
|
@ -41,6 +41,8 @@ namespace Status
|
|||
return QObject::tr("offline", "contact status");
|
||||
case Status::Blocked:
|
||||
return QObject::tr("blocked", "contact status");
|
||||
case Status::Negotiating:
|
||||
return QObject::tr("negotitating", "contact status");
|
||||
}
|
||||
|
||||
assert(false);
|
||||
|
@ -60,6 +62,8 @@ namespace Status
|
|||
return "offline";
|
||||
case Status::Blocked:
|
||||
return "blocked";
|
||||
case Status::Negotiating:
|
||||
return "negotiating";
|
||||
}
|
||||
assert(false);
|
||||
return QStringLiteral("");
|
||||
|
@ -78,6 +82,9 @@ namespace Status
|
|||
|
||||
bool isOnline(Status status)
|
||||
{
|
||||
return status != Status::Offline && status != Status::Blocked;
|
||||
return status != Status::Offline
|
||||
&& status != Status::Blocked
|
||||
// We don't want to treat a friend as online unless we know their feature set
|
||||
&& status != Status::Negotiating;
|
||||
}
|
||||
} // namespace Status
|
||||
|
|
|
@ -31,7 +31,8 @@ namespace Status
|
|||
Away,
|
||||
Busy,
|
||||
Offline,
|
||||
Blocked
|
||||
Blocked,
|
||||
Negotiating,
|
||||
};
|
||||
|
||||
QString getIconPath(Status status, bool event = false);
|
||||
|
|
|
@ -108,6 +108,8 @@ void Nexus::start()
|
|||
qRegisterMetaType<GroupInvite>("GroupInvite");
|
||||
qRegisterMetaType<ReceiptNum>("ReceiptNum");
|
||||
qRegisterMetaType<RowId>("RowId");
|
||||
qRegisterMetaType<uint64_t>("uint64_t");
|
||||
qRegisterMetaType<ExtensionSet>("ExtensionSet");
|
||||
|
||||
qApp->setQuitOnLastWindowClosed(false);
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
#include "src/core/toxpk.h"
|
||||
|
||||
namespace {
|
||||
static constexpr int SCHEMA_VERSION = 5;
|
||||
static constexpr int SCHEMA_VERSION = 6;
|
||||
|
||||
bool createCurrentSchema(RawDatabase& db)
|
||||
{
|
||||
|
@ -67,8 +67,10 @@ bool createCurrentSchema(RawDatabase& db)
|
|||
"direction INTEGER NOT NULL, "
|
||||
"file_state INTEGER NOT NULL);"
|
||||
"CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY, "
|
||||
"required_extensions INTEGER NOT NULL DEFAULT 0, "
|
||||
"FOREIGN KEY (id) REFERENCES history(id));"
|
||||
"CREATE TABLE broken_messages (id INTEGER PRIMARY KEY, "
|
||||
"reason INTEGER NOT NULL DEFAULT 0, "
|
||||
"FOREIGN KEY (id) REFERENCES history(id));"));
|
||||
// sqlite doesn't support including the index as part of the CREATE TABLE statement, so add a second query
|
||||
queries += RawDatabase::Query(
|
||||
|
@ -95,20 +97,17 @@ bool isNewDb(std::shared_ptr<RawDatabase>& db, bool& success)
|
|||
bool dbSchema0to1(RawDatabase& db)
|
||||
{
|
||||
QVector<RawDatabase::Query> queries;
|
||||
queries +=
|
||||
RawDatabase::Query(QStringLiteral(
|
||||
"CREATE TABLE file_transfers "
|
||||
"(id INTEGER PRIMARY KEY, "
|
||||
"chat_id INTEGER NOT NULL, "
|
||||
"file_restart_id BLOB NOT NULL, "
|
||||
"file_name BLOB NOT NULL, "
|
||||
"file_path BLOB NOT NULL, "
|
||||
"file_hash BLOB NOT NULL, "
|
||||
"file_size INTEGER NOT NULL, "
|
||||
"direction INTEGER NOT NULL, "
|
||||
"file_state INTEGER NOT NULL);"));
|
||||
queries +=
|
||||
RawDatabase::Query(QStringLiteral("ALTER TABLE history ADD file_id INTEGER;"));
|
||||
queries += RawDatabase::Query(QStringLiteral("CREATE TABLE file_transfers "
|
||||
"(id INTEGER PRIMARY KEY, "
|
||||
"chat_id INTEGER NOT NULL, "
|
||||
"file_restart_id BLOB NOT NULL, "
|
||||
"file_name BLOB NOT NULL, "
|
||||
"file_path BLOB NOT NULL, "
|
||||
"file_hash BLOB NOT NULL, "
|
||||
"file_size INTEGER NOT NULL, "
|
||||
"direction INTEGER NOT NULL, "
|
||||
"file_state INTEGER NOT NULL);"));
|
||||
queries += RawDatabase::Query(QStringLiteral("ALTER TABLE history ADD file_id INTEGER;"));
|
||||
queries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 1;"));
|
||||
return db.execNow(queries);
|
||||
}
|
||||
|
@ -120,29 +119,29 @@ bool dbSchema1to2(RawDatabase& db)
|
|||
// faux_offline_pending to broken_messages
|
||||
|
||||
// the last non-pending message in each chat
|
||||
QString lastDeliveredQuery = QString(
|
||||
"SELECT chat_id, MAX(history.id) FROM "
|
||||
"history JOIN peers chat ON chat_id = chat.id "
|
||||
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
||||
"WHERE faux_offline_pending.id IS NULL "
|
||||
"GROUP BY chat_id;");
|
||||
QString lastDeliveredQuery =
|
||||
QString("SELECT chat_id, MAX(history.id) FROM "
|
||||
"history JOIN peers chat ON chat_id = chat.id "
|
||||
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
||||
"WHERE faux_offline_pending.id IS NULL "
|
||||
"GROUP BY chat_id;");
|
||||
|
||||
QVector<RawDatabase::Query> upgradeQueries;
|
||||
upgradeQueries +=
|
||||
RawDatabase::Query(QStringLiteral(
|
||||
"CREATE TABLE broken_messages "
|
||||
"(id INTEGER PRIMARY KEY);"));
|
||||
upgradeQueries += RawDatabase::Query(QStringLiteral("CREATE TABLE broken_messages "
|
||||
"(id INTEGER PRIMARY KEY);"));
|
||||
|
||||
auto rowCallback = [&upgradeQueries](const QVector<QVariant>& row) {
|
||||
auto chatId = row[0].toLongLong();
|
||||
auto lastDeliveredHistoryId = row[1].toLongLong();
|
||||
|
||||
upgradeQueries += QString("INSERT INTO broken_messages "
|
||||
"SELECT faux_offline_pending.id FROM "
|
||||
"history JOIN faux_offline_pending "
|
||||
"ON faux_offline_pending.id = history.id "
|
||||
"WHERE history.chat_id=%1 "
|
||||
"AND history.id < %2;").arg(chatId).arg(lastDeliveredHistoryId);
|
||||
"SELECT faux_offline_pending.id FROM "
|
||||
"history JOIN faux_offline_pending "
|
||||
"ON faux_offline_pending.id = history.id "
|
||||
"WHERE history.chat_id=%1 "
|
||||
"AND history.id < %2;")
|
||||
.arg(chatId)
|
||||
.arg(lastDeliveredHistoryId);
|
||||
};
|
||||
// note this doesn't modify the db, just generate new queries, so is safe
|
||||
// to run outside of our upgrade transaction
|
||||
|
@ -150,10 +149,9 @@ bool dbSchema1to2(RawDatabase& db)
|
|||
return false;
|
||||
}
|
||||
|
||||
upgradeQueries += QString(
|
||||
"DELETE FROM faux_offline_pending "
|
||||
"WHERE id in ("
|
||||
"SELECT id FROM broken_messages);");
|
||||
upgradeQueries += QString("DELETE FROM faux_offline_pending "
|
||||
"WHERE id in ("
|
||||
"SELECT id FROM broken_messages);");
|
||||
|
||||
upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 2;"));
|
||||
|
||||
|
@ -172,16 +170,15 @@ bool dbSchema2to3(RawDatabase& db)
|
|||
|
||||
QVector<RawDatabase::Query> upgradeQueries;
|
||||
upgradeQueries += RawDatabase::Query{QString("INSERT INTO broken_messages "
|
||||
"SELECT faux_offline_pending.id FROM "
|
||||
"history JOIN faux_offline_pending "
|
||||
"ON faux_offline_pending.id = history.id "
|
||||
"WHERE history.message = ?;"),
|
||||
{emptyActionMessageString.toUtf8()}};
|
||||
"SELECT faux_offline_pending.id FROM "
|
||||
"history JOIN faux_offline_pending "
|
||||
"ON faux_offline_pending.id = history.id "
|
||||
"WHERE history.message = ?;"),
|
||||
{emptyActionMessageString.toUtf8()}};
|
||||
|
||||
upgradeQueries += QString(
|
||||
"DELETE FROM faux_offline_pending "
|
||||
"WHERE id in ("
|
||||
"SELECT id FROM broken_messages);");
|
||||
upgradeQueries += QString("DELETE FROM faux_offline_pending "
|
||||
"WHERE id in ("
|
||||
"SELECT id FROM broken_messages);");
|
||||
|
||||
upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 3;"));
|
||||
|
||||
|
@ -277,14 +274,48 @@ bool dbSchema4to5(RawDatabase& db)
|
|||
return transactionPass;
|
||||
}
|
||||
|
||||
bool dbSchema5to6(RawDatabase& db)
|
||||
{
|
||||
QVector<RawDatabase::Query> upgradeQueries;
|
||||
|
||||
upgradeQueries += RawDatabase::Query{QString("ALTER TABLE faux_offline_pending "
|
||||
"ADD COLUMN required_extensions INTEGER NOT NULL "
|
||||
"DEFAULT 0;")};
|
||||
|
||||
upgradeQueries += RawDatabase::Query{QString("ALTER TABLE broken_messages "
|
||||
"ADD COLUMN reason INTEGER NOT NULL "
|
||||
"DEFAULT 0;")};
|
||||
|
||||
upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 6;"));
|
||||
return db.execNow(upgradeQueries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Upgrade the db schema
|
||||
* @return True if the schema upgrade succeded, false otherwise
|
||||
* @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION
|
||||
* variable and add another case to the switch statement below. Make sure to fall through on each case.
|
||||
*/
|
||||
* @brief Upgrade the db schema
|
||||
* @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION
|
||||
* variable and add another case to the switch statement below. Make sure to fall through on each case.
|
||||
*/
|
||||
bool dbSchemaUpgrade(std::shared_ptr<RawDatabase>& db)
|
||||
{
|
||||
// If we're a new dB we can just make a new one and call it a day
|
||||
bool success = false;
|
||||
const bool newDb = isNewDb(db, success);
|
||||
if (!success) {
|
||||
qCritical() << "Failed to create current db schema";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newDb) {
|
||||
if (!createCurrentSchema(*db)) {
|
||||
qCritical() << "Failed to create current db schema";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Database created at schema version" << SCHEMA_VERSION;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise we have to do upgrades from our current version to the latest version
|
||||
|
||||
int64_t databaseSchemaVersion;
|
||||
|
||||
if (!db->execNow(RawDatabase::Query("PRAGMA user_version", [&](const QVector<QVariant>& row) {
|
||||
|
@ -295,8 +326,9 @@ bool dbSchemaUpgrade(std::shared_ptr<RawDatabase>& db)
|
|||
}
|
||||
|
||||
if (databaseSchemaVersion > SCHEMA_VERSION) {
|
||||
qWarning().nospace() << "Database version (" << databaseSchemaVersion <<
|
||||
") is newer than we currently support (" << SCHEMA_VERSION << "). Please upgrade qTox";
|
||||
qWarning().nospace() << "Database version (" << databaseSchemaVersion
|
||||
<< ") is newer than we currently support (" << SCHEMA_VERSION
|
||||
<< "). Please upgrade qTox";
|
||||
// We don't know what future versions have done, we have to disable db access until we re-upgrade
|
||||
return false;
|
||||
} else if (databaseSchemaVersion == SCHEMA_VERSION) {
|
||||
|
@ -304,66 +336,24 @@ bool dbSchemaUpgrade(std::shared_ptr<RawDatabase>& db)
|
|||
return true;
|
||||
}
|
||||
|
||||
switch (databaseSchemaVersion) {
|
||||
case 0: {
|
||||
// Note: 0 is a special version that is actually two versions.
|
||||
// possibility 1) it is a newly created database and it neesds the current schema to be created.
|
||||
// possibility 2) it is a old existing database, before version 1 and before we saved schema version,
|
||||
// and needs to be updated.
|
||||
bool success = false;
|
||||
const bool newDb = isNewDb(db, success);
|
||||
if (!success) {
|
||||
qCritical() << "Failed to create current db schema";
|
||||
using DbSchemaUpgradeFn = bool (*)(RawDatabase&);
|
||||
std::vector<DbSchemaUpgradeFn> upgradeFns = {dbSchema0to1, dbSchema1to2, dbSchema2to3,
|
||||
dbSchema3to4, dbSchema4to5, dbSchema5to6};
|
||||
|
||||
assert(databaseSchemaVersion < static_cast<int>(upgradeFns.size()));
|
||||
assert(upgradeFns.size() == SCHEMA_VERSION);
|
||||
|
||||
for (int64_t i = databaseSchemaVersion; i < static_cast<int>(upgradeFns.size()); ++i) {
|
||||
auto const newDbVersion = i + 1;
|
||||
if (!upgradeFns[i](*db)) {
|
||||
qCritical() << "Failed to upgrade db to schema version " << newDbVersion << " aborting";
|
||||
return false;
|
||||
}
|
||||
if (newDb) {
|
||||
if (!createCurrentSchema(*db)) {
|
||||
qCritical() << "Failed to create current db schema";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Database created at schema version" << SCHEMA_VERSION;
|
||||
break; // new db is the only case where we don't incrementally upgrade through each version
|
||||
} else {
|
||||
if (!dbSchema0to1(*db)) {
|
||||
qCritical() << "Failed to upgrade db to schema version 1, aborting";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Database upgraded incrementally to schema version 1";
|
||||
}
|
||||
}
|
||||
// fallthrough
|
||||
case 1:
|
||||
if (!dbSchema1to2(*db)) {
|
||||
qCritical() << "Failed to upgrade db to schema version 2, aborting";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Database upgraded incrementally to schema version 2";
|
||||
//fallthrough
|
||||
case 2:
|
||||
if (!dbSchema2to3(*db)) {
|
||||
qCritical() << "Failed to upgrade db to schema version 3, aborting";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Database upgraded incrementally to schema version 3";
|
||||
case 3:
|
||||
if (!dbSchema3to4(*db)) {
|
||||
qCritical() << "Failed to upgrade db to schema version 4, aborting";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Database upgraded incrementally to schema version 4";
|
||||
//fallthrough
|
||||
case 4:
|
||||
if (!dbSchema4to5(*db)) {
|
||||
qCritical() << "Failed to upgrade db to schema version 5, aborting";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Database upgraded incrementally to schema version 5";
|
||||
// etc.
|
||||
default:
|
||||
qInfo() << "Database upgrade finished (databaseSchemaVersion" << databaseSchemaVersion
|
||||
<< "->" << SCHEMA_VERSION << ")";
|
||||
qDebug() << "Database upgraded incrementally to schema version " << newDbVersion;
|
||||
}
|
||||
|
||||
qInfo() << "Database upgrade finished (databaseSchemaVersion" << databaseSchemaVersion << "->"
|
||||
<< SCHEMA_VERSION << ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -371,6 +361,7 @@ MessageState getMessageState(bool isPending, bool isBroken)
|
|||
{
|
||||
assert(!(isPending && isBroken));
|
||||
MessageState messageState;
|
||||
|
||||
if (isPending) {
|
||||
messageState = MessageState::pending;
|
||||
} else if (isBroken) {
|
||||
|
@ -544,7 +535,8 @@ void History::removeFriendHistory(const ToxPk& friendPk)
|
|||
QVector<RawDatabase::Query>
|
||||
History::generateNewMessageQueries(const ToxPk& friendPk, const QString& message,
|
||||
const ToxPk& sender, const QDateTime& time, bool isDelivered,
|
||||
QString dispName, std::function<void(RowId)> insertIdCallback)
|
||||
ExtensionSet extensionSet, QString dispName,
|
||||
std::function<void(RowId)> insertIdCallback)
|
||||
{
|
||||
QVector<RawDatabase::Query> queries;
|
||||
|
||||
|
@ -565,9 +557,10 @@ History::generateNewMessageQueries(const ToxPk& friendPk, const QString& message
|
|||
{message.toUtf8(), dispName.toUtf8()}, insertIdCallback);
|
||||
|
||||
if (!isDelivered) {
|
||||
queries += RawDatabase::Query{"INSERT INTO faux_offline_pending (id) VALUES ("
|
||||
" last_insert_rowid()"
|
||||
");"};
|
||||
queries += RawDatabase::Query{QString("INSERT INTO faux_offline_pending (id, required_extensions) VALUES ("
|
||||
" last_insert_rowid(), %1"
|
||||
");")
|
||||
.arg(extensionSet.to_ulong())};
|
||||
}
|
||||
|
||||
return queries;
|
||||
|
@ -590,7 +583,8 @@ void History::onFileInsertionReady(FileDbInsertionData data)
|
|||
.arg(data.size)
|
||||
.arg(static_cast<int>(data.direction))
|
||||
.arg(ToxFile::CANCELED),
|
||||
{data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName.toUtf8(), QByteArray()},
|
||||
{data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName.toUtf8(),
|
||||
QByteArray()},
|
||||
[weakThis, fileId](RowId id) {
|
||||
auto pThis = weakThis.lock();
|
||||
if (pThis) {
|
||||
|
@ -687,7 +681,7 @@ void History::addNewFileMessage(const ToxPk& friendPk, const QString& fileId,
|
|||
emit thisPtr->fileInsertionReady(std::move(insertionDataRw));
|
||||
};
|
||||
|
||||
addNewMessage(friendPk, "", sender, time, true, dispName, insertFileTransferFn);
|
||||
addNewMessage(friendPk, "", sender, time, true, ExtensionSet(), dispName, insertFileTransferFn);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -701,15 +695,15 @@ void History::addNewFileMessage(const ToxPk& friendPk, const QString& fileId,
|
|||
* @param insertIdCallback Function, called after query execution.
|
||||
*/
|
||||
void History::addNewMessage(const ToxPk& friendPk, const QString& message, const ToxPk& sender,
|
||||
const QDateTime& time, bool isDelivered, QString dispName,
|
||||
const std::function<void(RowId)>& insertIdCallback)
|
||||
const QDateTime& time, bool isDelivered, ExtensionSet extensionSet,
|
||||
QString dispName, const std::function<void(RowId)>& insertIdCallback)
|
||||
{
|
||||
if (historyAccessBlocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
db->execLater(generateNewMessageQueries(friendPk, message, sender, time, isDelivered, dispName,
|
||||
insertIdCallback));
|
||||
db->execLater(generateNewMessageQueries(friendPk, message, sender, time, isDelivered,
|
||||
extensionSet, dispName, insertIdCallback));
|
||||
}
|
||||
|
||||
void History::setFileFinished(const QString& fileId, bool success, const QString& filePath,
|
||||
|
@ -785,7 +779,8 @@ QList<History::HistMessage> History::getMessagesForFriend(const ToxPk& friendPk,
|
|||
"message, file_transfers.file_restart_id, "
|
||||
"file_transfers.file_path, file_transfers.file_name, "
|
||||
"file_transfers.file_size, file_transfers.direction, "
|
||||
"file_transfers.file_state, broken_messages.id FROM history "
|
||||
"file_transfers.file_state, broken_messages.id, "
|
||||
"faux_offline_pending.required_extensions FROM history "
|
||||
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
||||
"JOIN peers chat ON history.chat_id = chat.id "
|
||||
"JOIN aliases ON sender_alias = aliases.id "
|
||||
|
@ -808,12 +803,13 @@ QList<History::HistMessage> History::getMessagesForFriend(const ToxPk& friendPk,
|
|||
auto display_name = QString::fromUtf8(row[4].toByteArray().replace('\0', ""));
|
||||
auto sender_key = row[5].toString();
|
||||
auto isBroken = !row[13].isNull();
|
||||
auto requiredExtensions = ExtensionSet(row[14].toLongLong());
|
||||
|
||||
MessageState messageState = getMessageState(isPending, isBroken);
|
||||
|
||||
if (row[7].isNull()) {
|
||||
messages += {id, messageState, timestamp, friend_key,
|
||||
display_name, sender_key, row[6].toString()};
|
||||
messages += {id, messageState, requiredExtensions, timestamp, friend_key,
|
||||
display_name, sender_key, row[6].toString()};
|
||||
} else {
|
||||
ToxFile file;
|
||||
file.fileKind = TOX_FILE_KIND_DATA;
|
||||
|
@ -823,8 +819,7 @@ QList<History::HistMessage> History::getMessagesForFriend(const ToxPk& friendPk,
|
|||
file.filesize = row[10].toLongLong();
|
||||
file.direction = static_cast<ToxFile::FileDirection>(row[11].toLongLong());
|
||||
file.status = static_cast<ToxFile::FileStatus>(row[12].toInt());
|
||||
messages +=
|
||||
{id, messageState, timestamp, friend_key, display_name, sender_key, file};
|
||||
messages += {id, messageState, timestamp, friend_key, display_name, sender_key, file};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -841,7 +836,8 @@ QList<History::HistMessage> History::getUndeliveredMessagesForFriend(const ToxPk
|
|||
|
||||
auto queryText =
|
||||
QString("SELECT history.id, faux_offline_pending.id, timestamp, chat.public_key, "
|
||||
"aliases.display_name, sender.public_key, message, broken_messages.id "
|
||||
"aliases.display_name, sender.public_key, message, broken_messages.id, "
|
||||
"faux_offline_pending.required_extensions "
|
||||
"FROM history "
|
||||
"JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
||||
"JOIN peers chat on history.chat_id = chat.id "
|
||||
|
@ -862,11 +858,12 @@ QList<History::HistMessage> History::getUndeliveredMessagesForFriend(const ToxPk
|
|||
auto display_name = QString::fromUtf8(row[4].toByteArray().replace('\0', ""));
|
||||
auto sender_key = row[5].toString();
|
||||
auto isBroken = !row[7].isNull();
|
||||
auto extensionSet = ExtensionSet(row[8].toLongLong());
|
||||
|
||||
MessageState messageState = getMessageState(isPending, isBroken);
|
||||
|
||||
ret += {id, messageState, timestamp, friend_key,
|
||||
display_name, sender_key, row[6].toString()};
|
||||
ret +=
|
||||
{id, messageState, extensionSet, timestamp, friend_key, display_name, sender_key, row[6].toString()};
|
||||
};
|
||||
|
||||
db->execNow({queryText, rowCallback});
|
||||
|
@ -1066,5 +1063,20 @@ bool History::historyAccessBlocked()
|
|||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void History::markAsBroken(RowId messageId, BrokenMessageReason reason)
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<RawDatabase::Query> queries;
|
||||
queries += RawDatabase::Query(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(messageId.get()));
|
||||
queries += RawDatabase::Query(QString("INSERT INTO broken_messages (id, reason) "
|
||||
"VALUES (%1, %2);")
|
||||
.arg(messageId.get())
|
||||
.arg(static_cast<int64_t>(reason)));
|
||||
|
||||
db->execLater(queries);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
#include "src/core/toxfile.h"
|
||||
#include "src/core/toxpk.h"
|
||||
#include "src/core/extension.h"
|
||||
#include "src/model/brokenmessagereason.h"
|
||||
#include "src/persistence/db/rawdatabase.h"
|
||||
#include "src/widget/searchtypes.h"
|
||||
|
||||
|
@ -117,7 +119,7 @@ class History : public QObject, public std::enable_shared_from_this<History>
|
|||
public:
|
||||
struct HistMessage
|
||||
{
|
||||
HistMessage(RowId id, MessageState state, QDateTime timestamp, QString chat, QString dispName,
|
||||
HistMessage(RowId id, MessageState state, ExtensionSet extensionSet, QDateTime timestamp, QString chat, QString dispName,
|
||||
QString sender, QString message)
|
||||
: chat{chat}
|
||||
, sender{sender}
|
||||
|
@ -125,6 +127,7 @@ public:
|
|||
, timestamp{timestamp}
|
||||
, id{id}
|
||||
, state{state}
|
||||
, extensionSet(extensionSet)
|
||||
, content(std::move(message))
|
||||
{}
|
||||
|
||||
|
@ -146,6 +149,7 @@ public:
|
|||
QDateTime timestamp;
|
||||
RowId id;
|
||||
MessageState state;
|
||||
ExtensionSet extensionSet;
|
||||
HistMessageContent content;
|
||||
};
|
||||
|
||||
|
@ -166,8 +170,8 @@ public:
|
|||
void eraseHistory();
|
||||
void removeFriendHistory(const ToxPk& friendPk);
|
||||
void addNewMessage(const ToxPk& friendPk, const QString& message, const ToxPk& sender,
|
||||
const QDateTime& time, bool isDelivered, QString dispName,
|
||||
const std::function<void(RowId)>& insertIdCallback = {});
|
||||
const QDateTime& time, bool isDelivered, ExtensionSet extensions,
|
||||
QString dispName, const std::function<void(RowId)>& insertIdCallback = {});
|
||||
|
||||
void addNewFileMessage(const ToxPk& friendPk, const QString& fileId,
|
||||
const QString& fileName, const QString& filePath, int64_t size,
|
||||
|
@ -184,12 +188,13 @@ public:
|
|||
const QDate& from, size_t maxNum);
|
||||
|
||||
void markAsDelivered(RowId messageId);
|
||||
void markAsBroken(RowId messageId, BrokenMessageReason reason);
|
||||
|
||||
protected:
|
||||
QVector<RawDatabase::Query>
|
||||
generateNewMessageQueries(const ToxPk& friendPk, const QString& message,
|
||||
const ToxPk& sender, const QDateTime& time, bool isDelivered,
|
||||
QString dispName, std::function<void(RowId)> insertIdCallback = {});
|
||||
ExtensionSet extensionSet, QString dispName, std::function<void(RowId)> insertIdCallback = {});
|
||||
|
||||
signals:
|
||||
void fileInsertionReady(FileDbInsertionData data);
|
||||
|
|
|
@ -29,10 +29,8 @@
|
|||
#include <QCoreApplication>
|
||||
#include <chrono>
|
||||
|
||||
OfflineMsgEngine::OfflineMsgEngine(Friend* frnd, ICoreFriendMessageSender* messageSender)
|
||||
OfflineMsgEngine::OfflineMsgEngine()
|
||||
: mutex(QMutex::Recursive)
|
||||
, f(frnd)
|
||||
, messageSender(messageSender)
|
||||
{}
|
||||
|
||||
/**
|
||||
|
@ -43,12 +41,13 @@ OfflineMsgEngine::OfflineMsgEngine(Friend* frnd, ICoreFriendMessageSender* messa
|
|||
void OfflineMsgEngine::onReceiptReceived(ReceiptNum receipt)
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
if (receivedReceipts.contains(receipt)) {
|
||||
qWarning() << "Receievd duplicate receipt" << receipt.get() << "from friend" << f->getId();
|
||||
return;
|
||||
}
|
||||
receivedReceipts.append(receipt);
|
||||
checkForCompleteMessages(receipt);
|
||||
receiptResolver.notifyReceiptReceived(receipt);
|
||||
}
|
||||
|
||||
void OfflineMsgEngine::onExtendedReceiptReceived(ExtendedReceiptNum receipt)
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
extendedReceiptResolver.notifyReceiptReceived(receipt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,7 +62,7 @@ void OfflineMsgEngine::onReceiptReceived(ReceiptNum receipt)
|
|||
void OfflineMsgEngine::addUnsentMessage(Message const& message, CompletionFn completionCallback)
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
unsentMessages.append(OfflineMessage{message, std::chrono::steady_clock::now(), completionCallback});
|
||||
unsentMessages.push_back(OfflineMessage{message, std::chrono::steady_clock::now(), completionCallback});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,79 +75,51 @@ void OfflineMsgEngine::addUnsentMessage(Message const& message, CompletionFn com
|
|||
* @param[in] messageID database RowId of the message, used to eventually mark messages as received in history
|
||||
* @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp
|
||||
*/
|
||||
void OfflineMsgEngine::addSentMessage(ReceiptNum receipt, Message const& message,
|
||||
void OfflineMsgEngine::addSentCoreMessage(ReceiptNum receipt, Message const& message,
|
||||
CompletionFn completionCallback)
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
assert(!sentMessages.contains(receipt));
|
||||
sentMessages.insert(receipt, {message, std::chrono::steady_clock::now(), completionCallback});
|
||||
checkForCompleteMessages(receipt);
|
||||
receiptResolver.notifyMessageSent(receipt, {message, std::chrono::steady_clock::now(), completionCallback});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Deliver all messages, used when a friend comes online.
|
||||
*/
|
||||
void OfflineMsgEngine::deliverOfflineMsgs()
|
||||
void OfflineMsgEngine::addSentExtendedMessage(ExtendedReceiptNum receipt, Message const& message,
|
||||
CompletionFn completionCallback)
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
|
||||
if (!Status::isOnline(f->getStatus())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sentMessages.empty() && unsentMessages.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<OfflineMessage> messages = sentMessages.values().toVector() + unsentMessages;
|
||||
// order messages by authorship time to resend in same order as they were written
|
||||
std::sort(messages.begin(), messages.end(), [](const OfflineMessage& lhs, const OfflineMessage& rhs) {
|
||||
return lhs.authorshipTime < rhs.authorshipTime;
|
||||
});
|
||||
removeAllMessages();
|
||||
|
||||
for (const auto& message : messages) {
|
||||
QString messageText = message.message.content;
|
||||
ReceiptNum receipt;
|
||||
bool messageSent{false};
|
||||
if (message.message.isAction) {
|
||||
messageSent = messageSender->sendAction(f->getId(), messageText, receipt);
|
||||
} else {
|
||||
messageSent = messageSender->sendMessage(f->getId(), messageText, receipt);
|
||||
}
|
||||
if (messageSent) {
|
||||
addSentMessage(receipt, message.message, message.completionFn);
|
||||
} else {
|
||||
qCritical() << "deliverOfflineMsgs failed to send message";
|
||||
addUnsentMessage(message.message, message.completionFn);
|
||||
}
|
||||
}
|
||||
extendedReceiptResolver.notifyMessageSent(receipt, {message, std::chrono::steady_clock::now(), completionCallback});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all messages which are being tracked.
|
||||
*/
|
||||
void OfflineMsgEngine::removeAllMessages()
|
||||
std::vector<OfflineMsgEngine::RemovedMessage> OfflineMsgEngine::removeAllMessages()
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
receivedReceipts.clear();
|
||||
sentMessages.clear();
|
||||
auto messages = receiptResolver.clear();
|
||||
auto extendedMessages = extendedReceiptResolver.clear();
|
||||
|
||||
messages.insert(
|
||||
messages.end(),
|
||||
std::make_move_iterator(extendedMessages.begin()),
|
||||
std::make_move_iterator(extendedMessages.end()));
|
||||
|
||||
messages.insert(
|
||||
messages.end(),
|
||||
std::make_move_iterator(unsentMessages.begin()),
|
||||
std::make_move_iterator(unsentMessages.end()));
|
||||
|
||||
unsentMessages.clear();
|
||||
}
|
||||
|
||||
void OfflineMsgEngine::completeMessage(QMap<ReceiptNum, OfflineMessage>::iterator msgIt)
|
||||
{
|
||||
msgIt->completionFn();
|
||||
receivedReceipts.removeOne(msgIt.key());
|
||||
sentMessages.erase(msgIt);
|
||||
}
|
||||
std::sort(messages.begin(), messages.end(), [] (const OfflineMessage& a, const OfflineMessage& b) {
|
||||
return a.authorshipTime < b.authorshipTime;
|
||||
});
|
||||
|
||||
void OfflineMsgEngine::checkForCompleteMessages(ReceiptNum receipt)
|
||||
{
|
||||
auto msgIt = sentMessages.find(receipt);
|
||||
const bool receiptReceived = receivedReceipts.contains(receipt);
|
||||
if (!receiptReceived || msgIt == sentMessages.end()) {
|
||||
return;
|
||||
}
|
||||
completeMessage(msgIt);
|
||||
auto ret = std::vector<RemovedMessage>();
|
||||
ret.reserve(messages.size());
|
||||
|
||||
std::transform(messages.begin(), messages.end(), std::back_inserter(ret), [](const OfflineMessage& msg) {
|
||||
return RemovedMessage{msg.message, msg.completionFn};
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -30,23 +30,27 @@
|
|||
#include <QSet>
|
||||
#include <chrono>
|
||||
|
||||
class Friend;
|
||||
class ICoreFriendMessageSender;
|
||||
|
||||
class OfflineMsgEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineMsgEngine(Friend* f, ICoreFriendMessageSender* messageSender);
|
||||
|
||||
using CompletionFn = std::function<void()>;
|
||||
using CompletionFn = std::function<void(bool)>;
|
||||
OfflineMsgEngine();
|
||||
void addUnsentMessage(Message const& message, CompletionFn completionCallback);
|
||||
void addSentMessage(ReceiptNum receipt, Message const& message, CompletionFn completionCallback);
|
||||
void deliverOfflineMsgs();
|
||||
void addSentCoreMessage(ReceiptNum receipt, Message const& message, CompletionFn completionCallback);
|
||||
void addSentExtendedMessage(ExtendedReceiptNum receipt, Message const& message, CompletionFn completionCallback);
|
||||
|
||||
struct RemovedMessage
|
||||
{
|
||||
Message message;
|
||||
CompletionFn callback;
|
||||
};
|
||||
std::vector<RemovedMessage> removeAllMessages();
|
||||
|
||||
public slots:
|
||||
void removeAllMessages();
|
||||
void onReceiptReceived(ReceiptNum receipt);
|
||||
void onExtendedReceiptReceived(ExtendedReceiptNum receipt);
|
||||
|
||||
private:
|
||||
struct OfflineMessage
|
||||
|
@ -56,16 +60,58 @@ private:
|
|||
CompletionFn completionFn;
|
||||
};
|
||||
|
||||
private slots:
|
||||
void completeMessage(QMap<ReceiptNum, OfflineMessage>::iterator msgIt);
|
||||
|
||||
private:
|
||||
void checkForCompleteMessages(ReceiptNum receipt);
|
||||
|
||||
QMutex mutex;
|
||||
const Friend* f;
|
||||
ICoreFriendMessageSender* messageSender;
|
||||
QVector<ReceiptNum> receivedReceipts;
|
||||
QMap<ReceiptNum, OfflineMessage> sentMessages;
|
||||
QVector<OfflineMessage> unsentMessages;
|
||||
|
||||
template <class ReceiptT>
|
||||
class ReceiptResolver
|
||||
{
|
||||
public:
|
||||
void notifyMessageSent(ReceiptT receipt, OfflineMessage const& message)
|
||||
{
|
||||
auto receivedReceiptIt = std::find(
|
||||
receivedReceipts.begin(), receivedReceipts.end(), receipt);
|
||||
|
||||
if (receivedReceiptIt != receivedReceipts.end()) {
|
||||
receivedReceipts.erase(receivedReceiptIt);
|
||||
message.completionFn(true);
|
||||
return;
|
||||
}
|
||||
|
||||
unAckedMessages[receipt] = message;
|
||||
}
|
||||
|
||||
void notifyReceiptReceived(ReceiptT receipt)
|
||||
{
|
||||
auto unackedMessageIt = unAckedMessages.find(receipt);
|
||||
if (unackedMessageIt != unAckedMessages.end()) {
|
||||
unackedMessageIt->second.completionFn(true);
|
||||
unAckedMessages.erase(unackedMessageIt);
|
||||
return;
|
||||
}
|
||||
|
||||
receivedReceipts.push_back(receipt);
|
||||
}
|
||||
|
||||
std::vector<OfflineMessage> clear()
|
||||
{
|
||||
auto ret = std::vector<OfflineMessage>();
|
||||
std::transform(
|
||||
std::make_move_iterator(unAckedMessages.begin()), std::make_move_iterator(unAckedMessages.end()),
|
||||
std::back_inserter(ret),
|
||||
[] (const std::pair<ReceiptT, OfflineMessage>& pair) {
|
||||
return std::move(pair.second);
|
||||
});
|
||||
|
||||
receivedReceipts.clear();
|
||||
unAckedMessages.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<ReceiptT> receivedReceipts;
|
||||
std::map<ReceiptT, OfflineMessage> unAckedMessages;
|
||||
};
|
||||
|
||||
ReceiptResolver<ReceiptNum> receiptResolver;
|
||||
ReceiptResolver<ExtendedReceiptNum> extendedReceiptResolver;
|
||||
std::vector<OfflineMessage> unsentMessages;
|
||||
};
|
||||
|
|
|
@ -712,7 +712,7 @@ void Profile::onRequestSent(const ToxPk& friendPk, const QString& message)
|
|||
const ToxPk selfPk = core->getSelfPublicKey();
|
||||
const QDateTime datetime = QDateTime::currentDateTime();
|
||||
const QString name = core->getUsername();
|
||||
history->addNewMessage(friendPk, inviteStr, selfPk, datetime, true, name);
|
||||
history->addNewMessage(friendPk, inviteStr, selfPk, datetime, true, ExtensionSet(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
*/
|
||||
|
||||
#include "chatformheader.h"
|
||||
#include "extensionstatus.h"
|
||||
|
||||
#include "src/model/status.h"
|
||||
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/widget/maskablepixmapwidget.h"
|
||||
|
@ -117,6 +120,11 @@ ChatFormHeader::ChatFormHeader(QWidget* parent)
|
|||
avatar = new MaskablePixmapWidget(this, AVATAR_SIZE, ":/img/avatar_mask.svg");
|
||||
avatar->setObjectName("avatar");
|
||||
|
||||
nameLine = new QHBoxLayout();
|
||||
nameLine->setSpacing(3);
|
||||
|
||||
extensionStatus = new ExtensionStatus();
|
||||
|
||||
nameLabel = new CroppingLabel();
|
||||
nameLabel->setObjectName("nameLabel");
|
||||
nameLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize());
|
||||
|
@ -124,9 +132,12 @@ ChatFormHeader::ChatFormHeader(QWidget* parent)
|
|||
nameLabel->setTextFormat(Qt::PlainText);
|
||||
connect(nameLabel, &CroppingLabel::editFinished, this, &ChatFormHeader::nameChanged);
|
||||
|
||||
nameLine->addWidget(extensionStatus);
|
||||
nameLine->addWidget(nameLabel);
|
||||
|
||||
headTextLayout = new QVBoxLayout();
|
||||
headTextLayout->addStretch();
|
||||
headTextLayout->addWidget(nameLabel);
|
||||
headTextLayout->addLayout(nameLine);
|
||||
headTextLayout->addStretch();
|
||||
|
||||
micButton = createButton("micButton", this, &ChatFormHeader::micMuteToggle);
|
||||
|
@ -223,6 +234,11 @@ void ChatFormHeader::removeCallConfirm()
|
|||
callConfirm.reset(nullptr);
|
||||
}
|
||||
|
||||
void ChatFormHeader::updateExtensionSupport(ExtensionSet extensions)
|
||||
{
|
||||
extensionStatus->onExtensionSetUpdate(extensions);
|
||||
}
|
||||
|
||||
void ChatFormHeader::updateCallButtons(bool online, bool audio, bool video)
|
||||
{
|
||||
const bool audioAvaliable = online && (mode & Mode::Audio);
|
||||
|
|
|
@ -21,14 +21,19 @@
|
|||
|
||||
#include <QWidget>
|
||||
|
||||
#include "src/core/extension.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class MaskablePixmapWidget;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
class CroppingLabel;
|
||||
class QPushButton;
|
||||
class QToolButton;
|
||||
class CallConfirmWidget;
|
||||
class QLabel;
|
||||
class ExtensionStatus;
|
||||
|
||||
class ChatFormHeader : public QWidget
|
||||
{
|
||||
|
@ -64,6 +69,7 @@ public:
|
|||
void showCallConfirm();
|
||||
void removeCallConfirm();
|
||||
|
||||
void updateExtensionSupport(ExtensionSet extensions);
|
||||
void updateCallButtons(bool online, bool audio, bool video = false);
|
||||
void updateMuteMicButton(bool active, bool inputMuted);
|
||||
void updateMuteVolButton(bool active, bool outputMuted);
|
||||
|
@ -98,6 +104,8 @@ private:
|
|||
Mode mode;
|
||||
MaskablePixmapWidget* avatar;
|
||||
QVBoxLayout* headTextLayout;
|
||||
QHBoxLayout* nameLine;
|
||||
ExtensionStatus* extensionStatus;
|
||||
CroppingLabel* nameLabel;
|
||||
|
||||
QPushButton* callButton;
|
||||
|
|
54
src/widget/extensionstatus.cpp
Normal file
54
src/widget/extensionstatus.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright © 2019-2020 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "extensionstatus.h"
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
ExtensionStatus::ExtensionStatus(QWidget* parent)
|
||||
: QLabel(parent)
|
||||
{
|
||||
// Initialize with 0 extensions
|
||||
onExtensionSetUpdate(ExtensionSet());
|
||||
}
|
||||
|
||||
void ExtensionStatus::onExtensionSetUpdate(ExtensionSet extensionSet)
|
||||
{
|
||||
QString iconName;
|
||||
QString hoverText;
|
||||
if (extensionSet.all()) {
|
||||
iconName = ":/img/status/extensions_available.svg";
|
||||
hoverText = tr("All extensions supported");
|
||||
} else if (extensionSet.none()) {
|
||||
iconName = ":/img/status/extensions_unavailable.svg";
|
||||
hoverText = tr("No extensions supported");
|
||||
} else {
|
||||
iconName = ":/img/status/extensions_partial.svg";
|
||||
hoverText = tr("Not all extensions supported");
|
||||
}
|
||||
|
||||
hoverText += "\n";
|
||||
hoverText += tr("Multipart Messages: ");
|
||||
hoverText += extensionSet[ExtensionType::messages] ? "✔" : "❌";
|
||||
|
||||
auto pixmap = QIcon(iconName).pixmap(QSize(16, 16));
|
||||
|
||||
setPixmap(pixmap);
|
||||
setToolTip(hoverText);
|
||||
}
|
34
src/widget/extensionstatus.h
Normal file
34
src/widget/extensionstatus.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright © 2019-2020 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "src/core/extension.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
class ExtensionStatus : public QLabel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExtensionStatus(QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void onExtensionSetUpdate(ExtensionSet extensionSet);
|
||||
};
|
|
@ -142,9 +142,10 @@ ChatForm::ChatForm(Profile& profile, Friend* chatFriend, IChatLog& chatLog, IMes
|
|||
connect(coreFile, &CoreFile::fileReceiveRequested, this, &ChatForm::updateFriendActivityForFile);
|
||||
connect(coreFile, &CoreFile::fileSendStarted, this, &ChatForm::updateFriendActivityForFile);
|
||||
connect(&core, &Core::friendTypingChanged, this, &ChatForm::onFriendTypingChanged);
|
||||
connect(&core, &Core::friendStatusChanged, this, &ChatForm::onFriendStatusChanged);
|
||||
connect(coreFile, &CoreFile::fileNameChanged, this, &ChatForm::onFileNameChanged);
|
||||
|
||||
connect(chatFriend, &Friend::statusChanged, this, &ChatForm::onFriendStatusChanged);
|
||||
|
||||
const CoreAV* av = core.getAv();
|
||||
connect(av, &CoreAV::avInvite, this, &ChatForm::onAvInvite);
|
||||
connect(av, &CoreAV::avStart, this, &ChatForm::onAvStart);
|
||||
|
@ -180,6 +181,8 @@ ChatForm::ChatForm(Profile& profile, Friend* chatFriend, IChatLog& chatLog, IMes
|
|||
|
||||
connect(bodySplitter, &QSplitter::splitterMoved, this, &ChatForm::onSplitterMoved);
|
||||
|
||||
connect(f, &Friend::extensionSupportChanged, this, &ChatForm::onExtensionSupportChanged);
|
||||
|
||||
updateCallButtons();
|
||||
|
||||
setAcceptDrops(true);
|
||||
|
@ -223,6 +226,11 @@ void ChatForm::onFileNameChanged(const ToxPk& friendPk)
|
|||
"so you can save the file on Windows."));
|
||||
}
|
||||
|
||||
void ChatForm::onExtensionSupportChanged(ExtensionSet extensions)
|
||||
{
|
||||
headWidget->updateExtensionSupport(extensions);
|
||||
}
|
||||
|
||||
void ChatForm::onTextEditChanged()
|
||||
{
|
||||
if (!Settings::getInstance().getTypingNotification()) {
|
||||
|
@ -423,12 +431,10 @@ void ChatForm::onVolMuteToggle()
|
|||
updateMuteVolButton();
|
||||
}
|
||||
|
||||
void ChatForm::onFriendStatusChanged(uint32_t friendId, Status::Status status)
|
||||
void ChatForm::onFriendStatusChanged(const ToxPk& friendPk, Status::Status status)
|
||||
{
|
||||
// Disable call buttons if friend is offline
|
||||
if (friendId != f->getId()) {
|
||||
return;
|
||||
}
|
||||
assert(friendPk == f->getPublicKey());
|
||||
|
||||
if (!Status::isOnline(f->getStatus())) {
|
||||
// Hide the "is typing" message when a friend goes offline
|
||||
|
|
|
@ -72,6 +72,7 @@ public slots:
|
|||
void onAvEnd(uint32_t friendId, bool error);
|
||||
void onAvatarChanged(const ToxPk& friendPk, const QPixmap& pic);
|
||||
void onFileNameChanged(const ToxPk& friendPk);
|
||||
void onExtensionSupportChanged(ExtensionSet extensions);
|
||||
void clearChatArea();
|
||||
void onShowMessagesClicked();
|
||||
void onSplitterMoved(int pos, int index);
|
||||
|
@ -90,7 +91,7 @@ private slots:
|
|||
void onMicMuteToggle();
|
||||
void onVolMuteToggle();
|
||||
|
||||
void onFriendStatusChanged(quint32 friendId, Status::Status status);
|
||||
void onFriendStatusChanged(const ToxPk& friendPk, Status::Status status);
|
||||
void onFriendTypingChanged(quint32 friendId, bool isTyping);
|
||||
void onFriendNameChanged(const QString& name);
|
||||
void onStatusMessage(const QString& message);
|
||||
|
|
|
@ -190,6 +190,8 @@ void renderMessageRaw(const QString& displayName, bool isSelf, bool colorizeName
|
|||
if (chatMessage) {
|
||||
if (chatLogMessage.state == MessageState::complete) {
|
||||
chatMessage->markAsDelivered(chatLogMessage.message.timestamp);
|
||||
} else if (chatLogMessage.state == MessageState::broken) {
|
||||
chatMessage->markAsBroken();
|
||||
}
|
||||
} else {
|
||||
chatMessage = createMessage(displayName, isSelf, colorizeNames, chatLogMessage);
|
||||
|
|
|
@ -343,6 +343,8 @@ QString FriendWidget::getStatusString() const
|
|||
tr("Away"),
|
||||
tr("Busy"),
|
||||
tr("Offline"),
|
||||
tr("Blocked"),
|
||||
tr("Negotiating")
|
||||
};
|
||||
|
||||
return event ? tr("New message") : names.value(status);
|
||||
|
|
|
@ -248,6 +248,9 @@ void Widget::init()
|
|||
ui->searchContactFilterBox->setMenu(filterMenu);
|
||||
|
||||
core = &profile.getCore();
|
||||
auto coreExt = core->getExt();
|
||||
|
||||
sharedMessageProcessorParams.reset(new MessageProcessor::SharedParams(core->getMaxMessageSize(), coreExt->getMaxExtendedMessageSize()));
|
||||
|
||||
contactListWidget = new FriendListWidget(*core, this, settings.getGroupchatPosition());
|
||||
connect(contactListWidget, &FriendListWidget::searchCircle, this, &Widget::searchCircle);
|
||||
|
@ -679,7 +682,7 @@ void Widget::onCoreChanged(Core& core)
|
|||
connect(&core, &Core::friendAdded, this, &Widget::addFriend);
|
||||
connect(&core, &Core::failedToAddFriend, this, &Widget::addFriendFailed);
|
||||
connect(&core, &Core::friendUsernameChanged, this, &Widget::onFriendUsernameChanged);
|
||||
connect(&core, &Core::friendStatusChanged, this, &Widget::onFriendStatusChanged);
|
||||
connect(&core, &Core::friendStatusChanged, this, &Widget::onCoreFriendStatusChanged);
|
||||
connect(&core, &Core::friendStatusMessageChanged, this, &Widget::onFriendStatusMessageChanged);
|
||||
connect(&core, &Core::friendRequestReceived, this, &Widget::onFriendRequestReceived);
|
||||
connect(&core, &Core::friendMessageReceived, this, &Widget::onFriendMessageReceived);
|
||||
|
@ -695,12 +698,19 @@ void Widget::onCoreChanged(Core& core)
|
|||
connect(&core, &Core::friendTypingChanged, this, &Widget::onFriendTypingChanged);
|
||||
connect(&core, &Core::groupSentFailed, this, &Widget::onGroupSendFailed);
|
||||
connect(&core, &Core::usernameSet, this, &Widget::refreshPeerListsLocal);
|
||||
|
||||
auto coreExt = core.getExt();
|
||||
|
||||
connect(coreExt, &CoreExt::extendedMessageReceived, this, &Widget::onFriendExtMessageReceived);
|
||||
connect(coreExt, &CoreExt::extendedReceiptReceived, this, &Widget::onExtReceiptReceived);
|
||||
connect(coreExt, &CoreExt::extendedMessageSupport, this, &Widget::onExtendedMessageSupport);
|
||||
|
||||
connect(this, &Widget::statusSet, &core, &Core::setStatus);
|
||||
connect(this, &Widget::friendRequested, &core, &Core::requestFriendship);
|
||||
connect(this, &Widget::friendRequestAccepted, &core, &Core::acceptFriendRequest);
|
||||
connect(this, &Widget::changeGroupTitle, &core, &Core::changeGroupTitle);
|
||||
|
||||
sharedMessageProcessorParams.setPublicKey(core.getSelfPublicKey().toString());
|
||||
sharedMessageProcessorParams->setPublicKey(core.getSelfPublicKey().toString());
|
||||
}
|
||||
|
||||
void Widget::onConnected()
|
||||
|
@ -992,7 +1002,7 @@ void Widget::setUsername(const QString& username)
|
|||
Qt::convertFromPlainText(username, Qt::WhiteSpaceNormal)); // for overlength names
|
||||
}
|
||||
|
||||
sharedMessageProcessorParams.onUserNameSet(username);
|
||||
sharedMessageProcessorParams->onUserNameSet(username);
|
||||
}
|
||||
|
||||
void Widget::onStatusMessageChanged(const QString& newStatusMessage)
|
||||
|
@ -1144,9 +1154,9 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk)
|
|||
connectFriendWidget(*widget);
|
||||
auto history = profile.getHistory();
|
||||
|
||||
auto messageProcessor = MessageProcessor(sharedMessageProcessorParams);
|
||||
auto messageProcessor = MessageProcessor(*sharedMessageProcessorParams);
|
||||
auto friendMessageDispatcher =
|
||||
std::make_shared<FriendMessageDispatcher>(*newfriend, std::move(messageProcessor), *core);
|
||||
std::make_shared<FriendMessageDispatcher>(*newfriend, std::move(messageProcessor), *core, *core->getExt());
|
||||
|
||||
// Note: We do not have to connect the message dispatcher signals since
|
||||
// ChatHistory hooks them up in a very specific order
|
||||
|
@ -1183,6 +1193,7 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk)
|
|||
friendAlertConnections.insert(friendPk, notifyReceivedConnection);
|
||||
connect(newfriend, &Friend::aliasChanged, this, &Widget::onFriendAliasChanged);
|
||||
connect(newfriend, &Friend::displayedNameChanged, this, &Widget::onFriendDisplayedNameChanged);
|
||||
connect(newfriend, &Friend::statusChanged, this, &Widget::onFriendStatusChanged);
|
||||
|
||||
connect(friendForm, &ChatForm::incomingNotification, this, &Widget::incomingNotification);
|
||||
connect(friendForm, &ChatForm::outgoingNotification, this, &Widget::outgoingNotification);
|
||||
|
@ -1222,7 +1233,7 @@ void Widget::addFriendFailed(const ToxPk&, const QString& errorInfo)
|
|||
QMessageBox::critical(nullptr, "Error", info);
|
||||
}
|
||||
|
||||
void Widget::onFriendStatusChanged(int friendId, Status::Status status)
|
||||
void Widget::onCoreFriendStatusChanged(int friendId, Status::Status status)
|
||||
{
|
||||
const auto& friendPk = FriendList::id2Key(friendId);
|
||||
Friend* f = FriendList::findFriend(friendPk);
|
||||
|
@ -1230,18 +1241,35 @@ void Widget::onFriendStatusChanged(int friendId, Status::Status status)
|
|||
return;
|
||||
}
|
||||
|
||||
bool isActualChange = f->getStatus() != status;
|
||||
auto const oldStatus = f->getStatus();
|
||||
f->setStatus(status);
|
||||
auto const newStatus = f->getStatus();
|
||||
|
||||
FriendWidget* widget = friendWidgets[f->getPublicKey()];
|
||||
if (isActualChange) {
|
||||
if (!Status::isOnline(f->getStatus())) {
|
||||
contactListWidget->moveWidget(widget, Status::Status::Online);
|
||||
} else if (status == Status::Status::Offline) {
|
||||
contactListWidget->moveWidget(widget, Status::Status::Offline);
|
||||
}
|
||||
auto const startedNegotiating = (newStatus == Status::Status::Negotiating && oldStatus != newStatus);
|
||||
if (startedNegotiating) {
|
||||
constexpr auto negotiationTimeoutMs = 1000;
|
||||
auto timer = std::unique_ptr<QTimer>(new QTimer);
|
||||
timer->setSingleShot(true);
|
||||
timer->setInterval(negotiationTimeoutMs);
|
||||
connect(timer.get(), &QTimer::timeout, f, &Friend::onNegotiationComplete);
|
||||
timer->start();
|
||||
negotiateTimers[friendPk] = std::move(timer);
|
||||
}
|
||||
|
||||
// Any widget behavior will be triggered based off of the status
|
||||
// transformations done by the Friend class
|
||||
}
|
||||
|
||||
void Widget::onFriendStatusChanged(const ToxPk& friendPk, Status::Status status)
|
||||
{
|
||||
FriendWidget* widget = friendWidgets[friendPk];
|
||||
|
||||
if (Status::isOnline(status)) {
|
||||
contactListWidget->moveWidget(widget, Status::Status::Online);
|
||||
} else {
|
||||
contactListWidget->moveWidget(widget, Status::Status::Offline);
|
||||
}
|
||||
|
||||
f->setStatus(status);
|
||||
widget->updateStatusLight();
|
||||
if (widget->isActive()) {
|
||||
setWindowTitle(widget->getTitle());
|
||||
|
@ -1401,6 +1429,29 @@ void Widget::onReceiptReceived(int friendId, ReceiptNum receipt)
|
|||
friendMessageDispatchers[f->getPublicKey()]->onReceiptReceived(receipt);
|
||||
}
|
||||
|
||||
void Widget::onExtendedMessageSupport(uint32_t friendNumber, bool compatible)
|
||||
{
|
||||
const auto& friendKey = FriendList::id2Key(friendNumber);
|
||||
Friend* f = FriendList::findFriend(friendKey);
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
|
||||
f->setExtendedMessageSupport(compatible);
|
||||
}
|
||||
|
||||
void Widget::onFriendExtMessageReceived(uint32_t friendNumber, const QString& message)
|
||||
{
|
||||
const auto& friendKey = FriendList::id2Key(friendNumber);
|
||||
friendMessageDispatchers[friendKey]->onExtMessageReceived(message);
|
||||
}
|
||||
|
||||
void Widget::onExtReceiptReceived(uint32_t friendNumber, uint64_t receiptId)
|
||||
{
|
||||
const auto& friendKey = FriendList::id2Key(friendNumber);
|
||||
friendMessageDispatchers[friendKey]->onExtReceiptReceived(receiptId);
|
||||
}
|
||||
|
||||
void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog)
|
||||
{
|
||||
const ToxPk& friendPk = frnd->getPublicKey();
|
||||
|
@ -2079,7 +2130,7 @@ Group* Widget::createGroup(uint32_t groupnumber, const GroupId& groupId)
|
|||
|
||||
const auto compact = settings.getCompactLayout();
|
||||
auto widget = new GroupWidget(chatroom, compact);
|
||||
auto messageProcessor = MessageProcessor(sharedMessageProcessorParams);
|
||||
auto messageProcessor = MessageProcessor(*sharedMessageProcessorParams);
|
||||
auto messageDispatcher =
|
||||
std::make_shared<GroupMessageDispatcher>(*newgroup, std::move(messageProcessor), *core,
|
||||
*core, settings);
|
||||
|
@ -2091,6 +2142,8 @@ Group* Widget::createGroup(uint32_t groupnumber, const GroupId& groupId)
|
|||
&SessionChatLog::onMessageSent);
|
||||
connect(messageDispatcher.get(), &IMessageDispatcher::messageComplete, groupChatLog.get(),
|
||||
&SessionChatLog::onMessageComplete);
|
||||
connect(messageDispatcher.get(), &IMessageDispatcher::messageBroken, groupChatLog.get(),
|
||||
&SessionChatLog::onMessageBroken);
|
||||
|
||||
auto notifyReceivedCallback = [this, groupId](const ToxPk& author, const Message& message) {
|
||||
auto isTargeted = std::any_of(message.metadata.begin(), message.metadata.end(),
|
||||
|
|
|
@ -165,13 +165,17 @@ public slots:
|
|||
void setStatusMessage(const QString& statusMessage);
|
||||
void addFriend(uint32_t friendId, const ToxPk& friendPk);
|
||||
void addFriendFailed(const ToxPk& userId, const QString& errorInfo = QString());
|
||||
void onFriendStatusChanged(int friendId, Status::Status status);
|
||||
void onCoreFriendStatusChanged(int friendId, Status::Status status);
|
||||
void onFriendStatusChanged(const ToxPk& friendPk, Status::Status status);
|
||||
void onFriendStatusMessageChanged(int friendId, const QString& message);
|
||||
void onFriendDisplayedNameChanged(const QString& displayed);
|
||||
void onFriendUsernameChanged(int friendId, const QString& username);
|
||||
void onFriendAliasChanged(const ToxPk& friendId, const QString& alias);
|
||||
void onFriendMessageReceived(uint32_t friendnumber, const QString& message, bool isAction);
|
||||
void onReceiptReceived(int friendId, ReceiptNum receipt);
|
||||
void onExtendedMessageSupport(uint32_t friendNumber, bool supported);
|
||||
void onFriendExtMessageReceived(uint32_t friendNumber, const QString& message);
|
||||
void onExtReceiptReceived(uint32_t friendNumber, uint64_t receiptId);
|
||||
void onFriendRequestReceived(const ToxPk& friendPk, const QString& message);
|
||||
void onFileReceiveRequested(const ToxFile& file);
|
||||
void onEmptyGroupCreated(uint32_t groupnumber, const GroupId& groupId, const QString& title);
|
||||
|
@ -344,6 +348,7 @@ private:
|
|||
QMap<ToxPk, std::shared_ptr<ChatHistory>> friendChatLogs;
|
||||
QMap<ToxPk, std::shared_ptr<FriendChatroom>> friendChatrooms;
|
||||
QMap<ToxPk, ChatForm*> chatForms;
|
||||
std::map<ToxPk, std::unique_ptr<QTimer>> negotiateTimers;
|
||||
|
||||
QMap<GroupId, GroupWidget*> groupWidgets;
|
||||
QMap<GroupId, std::shared_ptr<GroupMessageDispatcher>> groupMessageDispatchers;
|
||||
|
@ -359,7 +364,7 @@ private:
|
|||
Core* core = nullptr;
|
||||
|
||||
|
||||
MessageProcessor::SharedParams sharedMessageProcessorParams;
|
||||
std::unique_ptr<MessageProcessor::SharedParams> sharedMessageProcessorParams;
|
||||
#if DESKTOP_NOTIFICATIONS
|
||||
std::unique_ptr<NotificationGenerator> notificationGenerator;
|
||||
DesktopNotify notifier;
|
||||
|
|
|
@ -25,8 +25,50 @@
|
|||
#include <QObject>
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
static constexpr uint64_t testMaxExtendedMessageSize = 10 * 1024 * 1024;
|
||||
|
||||
|
||||
class MockCoreExtPacket : public ICoreExtPacket
|
||||
{
|
||||
public:
|
||||
|
||||
MockCoreExtPacket(uint64_t& numSentMessages, uint64_t& currentReceiptId)
|
||||
: numSentMessages(numSentMessages)
|
||||
, currentReceiptId(currentReceiptId)
|
||||
{}
|
||||
|
||||
uint64_t addExtendedMessage(QString message) override
|
||||
{
|
||||
this->message = message;
|
||||
return currentReceiptId++;
|
||||
}
|
||||
|
||||
bool send() override
|
||||
{
|
||||
numSentMessages++;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t& numSentMessages;
|
||||
uint64_t& currentReceiptId;
|
||||
QDateTime senderTimestamp;
|
||||
QString message;
|
||||
};
|
||||
|
||||
class MockCoreExtPacketAllocator : public ICoreExtPacketAllocator
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<ICoreExtPacket> getPacket(uint32_t friendId) override
|
||||
{
|
||||
return std::unique_ptr<MockCoreExtPacket>(new MockCoreExtPacket(numSentMessages, currentReceiptId));
|
||||
}
|
||||
|
||||
uint64_t numSentMessages;
|
||||
uint64_t currentReceiptId;
|
||||
};
|
||||
|
||||
class MockFriendMessageSender : public ICoreFriendMessageSender
|
||||
{
|
||||
|
@ -69,6 +111,11 @@ private slots:
|
|||
void testMessageSending();
|
||||
void testOfflineMessages();
|
||||
void testFailedMessage();
|
||||
void testNegotiationFailure();
|
||||
void testNegotiationSuccess();
|
||||
void testOfflineExtensionMessages();
|
||||
void testSentMessageExtensionSetReduced();
|
||||
void testActionMessagesSplitWithExtensions();
|
||||
|
||||
void onMessageSent(DispatchedMessageId id, Message message)
|
||||
{
|
||||
|
@ -89,14 +136,21 @@ private slots:
|
|||
receivedMessages.push_back(std::move(message));
|
||||
}
|
||||
|
||||
void onMessageBroken(DispatchedMessageId id, BrokenMessageReason)
|
||||
{
|
||||
brokenMessages.insert(id);
|
||||
}
|
||||
|
||||
private:
|
||||
// All unique_ptrs to make construction/init() easier to manage
|
||||
std::unique_ptr<Friend> f;
|
||||
std::unique_ptr<MockFriendMessageSender> messageSender;
|
||||
std::unique_ptr<MockCoreExtPacketAllocator> coreExtPacketAllocator;
|
||||
std::unique_ptr<MessageProcessor::SharedParams> sharedProcessorParams;
|
||||
std::unique_ptr<MessageProcessor> messageProcessor;
|
||||
std::unique_ptr<FriendMessageDispatcher> friendMessageDispatcher;
|
||||
std::map<DispatchedMessageId, Message> outgoingMessages;
|
||||
std::set<DispatchedMessageId> brokenMessages;
|
||||
std::deque<Message> receivedMessages;
|
||||
};
|
||||
|
||||
|
@ -109,12 +163,15 @@ void TestFriendMessageDispatcher::init()
|
|||
{
|
||||
f = std::unique_ptr<Friend>(new Friend(0, ToxPk()));
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->onNegotiationComplete();
|
||||
messageSender = std::unique_ptr<MockFriendMessageSender>(new MockFriendMessageSender());
|
||||
coreExtPacketAllocator = std::unique_ptr<MockCoreExtPacketAllocator>(new MockCoreExtPacketAllocator());
|
||||
sharedProcessorParams =
|
||||
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams());
|
||||
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams(tox_max_message_length(), testMaxExtendedMessageSize));
|
||||
|
||||
messageProcessor = std::unique_ptr<MessageProcessor>(new MessageProcessor(*sharedProcessorParams));
|
||||
friendMessageDispatcher = std::unique_ptr<FriendMessageDispatcher>(
|
||||
new FriendMessageDispatcher(*f, *messageProcessor, *messageSender));
|
||||
new FriendMessageDispatcher(*f, *messageProcessor, *messageSender, *coreExtPacketAllocator));
|
||||
|
||||
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this,
|
||||
&TestFriendMessageDispatcher::onMessageSent);
|
||||
|
@ -122,9 +179,12 @@ void TestFriendMessageDispatcher::init()
|
|||
&TestFriendMessageDispatcher::onMessageComplete);
|
||||
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageReceived, this,
|
||||
&TestFriendMessageDispatcher::onMessageReceived);
|
||||
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageBroken, this,
|
||||
&TestFriendMessageDispatcher::onMessageBroken);
|
||||
|
||||
outgoingMessages = std::map<DispatchedMessageId, Message>();
|
||||
receivedMessages = std::deque<Message>();
|
||||
brokenMessages = std::set<DispatchedMessageId>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,6 +254,7 @@ void TestFriendMessageDispatcher::testOfflineMessages()
|
|||
QVERIFY(outgoingMessages.size() == 3);
|
||||
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
QVERIFY(messageSender->numSentActions == 1);
|
||||
QVERIFY(messageSender->numSentMessages == 2);
|
||||
|
@ -223,9 +284,112 @@ void TestFriendMessageDispatcher::testFailedMessage()
|
|||
messageSender->canSend = true;
|
||||
f->setStatus(Status::Status::Offline);
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
QVERIFY(messageSender->numSentMessages == 1);
|
||||
}
|
||||
|
||||
void TestFriendMessageDispatcher::testNegotiationFailure()
|
||||
{
|
||||
f->setStatus(Status::Status::Offline);
|
||||
f->setStatus(Status::Status::Online);
|
||||
|
||||
QVERIFY(f->getStatus() == Status::Status::Negotiating);
|
||||
|
||||
friendMessageDispatcher->sendMessage(false, "test");
|
||||
|
||||
QVERIFY(messageSender->numSentMessages == 0);
|
||||
|
||||
f->onNegotiationComplete();
|
||||
|
||||
QVERIFY(messageSender->numSentMessages == 1);
|
||||
}
|
||||
|
||||
void TestFriendMessageDispatcher::testNegotiationSuccess()
|
||||
{
|
||||
f->setStatus(Status::Status::Offline);
|
||||
f->setStatus(Status::Status::Online);
|
||||
|
||||
f->setExtendedMessageSupport(true);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
friendMessageDispatcher->sendMessage(false, "test");
|
||||
|
||||
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
|
||||
|
||||
friendMessageDispatcher->sendMessage(false, "test");
|
||||
QVERIFY(coreExtPacketAllocator->numSentMessages == 2);
|
||||
QVERIFY(messageSender->numSentMessages == 0);
|
||||
}
|
||||
|
||||
void TestFriendMessageDispatcher::testOfflineExtensionMessages()
|
||||
{
|
||||
f->setStatus(Status::Status::Offline);
|
||||
|
||||
auto requiredExtensions = ExtensionSet();
|
||||
requiredExtensions[ExtensionType::messages] = true;
|
||||
|
||||
friendMessageDispatcher->sendExtendedMessage("Test", requiredExtensions);
|
||||
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->setExtendedMessageSupport(true);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
// Ensure that when our friend came online with the desired extensions we
|
||||
// were able to send them our message over the extended message path
|
||||
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
|
||||
|
||||
f->setStatus(Status::Status::Offline);
|
||||
|
||||
friendMessageDispatcher->sendExtendedMessage("Test", requiredExtensions);
|
||||
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->setExtendedMessageSupport(false);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
// Here we want to make sure that when they do _not_ support extensions
|
||||
// we discard the message instead of attempting to send it over either
|
||||
// channel
|
||||
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
|
||||
QVERIFY(messageSender->numSentMessages == 0);
|
||||
}
|
||||
|
||||
void TestFriendMessageDispatcher::testSentMessageExtensionSetReduced()
|
||||
{
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->setExtendedMessageSupport(true);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
friendMessageDispatcher->sendMessage(false, "Test");
|
||||
|
||||
f->setStatus(Status::Status::Offline);
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->setExtendedMessageSupport(false);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
// Ensure that when we reduce our extension set we correctly emit the
|
||||
// "messageBroken" signal
|
||||
QVERIFY(brokenMessages.size() == 1);
|
||||
}
|
||||
|
||||
void TestFriendMessageDispatcher::testActionMessagesSplitWithExtensions()
|
||||
{
|
||||
f->setStatus(Status::Status::Online);
|
||||
f->setExtendedMessageSupport(true);
|
||||
f->onNegotiationComplete();
|
||||
|
||||
auto reallyLongMessage = QString("a");
|
||||
|
||||
for (uint64_t i = 0; i < testMaxExtendedMessageSize + 50; ++i) {
|
||||
reallyLongMessage += i;
|
||||
}
|
||||
|
||||
friendMessageDispatcher->sendMessage(true, reallyLongMessage);
|
||||
|
||||
QVERIFY(coreExtPacketAllocator->numSentMessages == 0);
|
||||
QVERIFY(messageSender->numSentMessages == 0);
|
||||
QVERIFY(messageSender->numSentActions > 1);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TestFriendMessageDispatcher)
|
||||
#include "friendmessagedispatcher_test.moc"
|
||||
|
|
|
@ -131,7 +131,7 @@ void TestGroupMessageDispatcher::init()
|
|||
new Group(0, GroupId(), "TestGroup", false, "me", *groupQuery, *coreIdHandler));
|
||||
messageSender = std::unique_ptr<MockGroupMessageSender>(new MockGroupMessageSender());
|
||||
sharedProcessorParams =
|
||||
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams());
|
||||
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams(tox_max_message_length(), 10 * 1024 * 1024));
|
||||
messageProcessor = std::unique_ptr<MessageProcessor>(new MessageProcessor(*sharedProcessorParams));
|
||||
groupMessageDispatcher = std::unique_ptr<GroupMessageDispatcher>(
|
||||
new GroupMessageDispatcher(*g, *messageProcessor, *coreIdHandler, *messageSender,
|
||||
|
|
|
@ -53,7 +53,7 @@ private slots:
|
|||
*/
|
||||
void TestMessageProcessor::testSelfMention()
|
||||
{
|
||||
MessageProcessor::SharedParams sharedParams;
|
||||
MessageProcessor::SharedParams sharedParams(tox_max_message_length(), 10 * 1024 * 1024);;
|
||||
const QLatin1String testUserName{"MyUserName"};
|
||||
const QLatin1String testToxPk{
|
||||
"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"};
|
||||
|
@ -66,55 +66,55 @@ void TestMessageProcessor::testSelfMention()
|
|||
for (const auto& str : {testUserName, testToxPk}) {
|
||||
|
||||
// Using my name or public key should match
|
||||
auto processedMessage = messageProcessor.processIncomingMessage(false, str % " hi");
|
||||
auto processedMessage = messageProcessor.processIncomingCoreMessage(false, str % " hi");
|
||||
QVERIFY(messageHasSelfMention(processedMessage));
|
||||
|
||||
// Action messages should match too
|
||||
processedMessage = messageProcessor.processIncomingMessage(true, str % " hi");
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(true, str % " hi");
|
||||
QVERIFY(messageHasSelfMention(processedMessage));
|
||||
|
||||
// Too much text shouldn't match
|
||||
processedMessage = messageProcessor.processIncomingMessage(false, str % "2");
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(false, str % "2");
|
||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||
|
||||
// Unless it's a colon
|
||||
processedMessage = messageProcessor.processIncomingMessage(false, str % ": test");
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(false, str % ": test");
|
||||
QVERIFY(messageHasSelfMention(processedMessage));
|
||||
|
||||
// remove last character
|
||||
QString chopped = str;
|
||||
chopped.chop(1);
|
||||
// Too little text shouldn't match
|
||||
processedMessage = messageProcessor.processIncomingMessage(false, chopped);
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(false, chopped);
|
||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||
|
||||
// make lower case
|
||||
QString lower = QString(str).toLower();
|
||||
|
||||
// The regex should be case insensitive
|
||||
processedMessage = messageProcessor.processIncomingMessage(false, lower % " hi");
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(false, lower % " hi");
|
||||
QVERIFY(messageHasSelfMention(processedMessage));
|
||||
}
|
||||
|
||||
// New user name changes should be detected
|
||||
sharedParams.onUserNameSet("NewUserName");
|
||||
auto processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi");
|
||||
auto processedMessage = messageProcessor.processIncomingCoreMessage(false, "NewUserName: hi");
|
||||
QVERIFY(messageHasSelfMention(processedMessage));
|
||||
|
||||
// Special characters should be removed
|
||||
sharedParams.onUserNameSet("New\nUserName");
|
||||
processedMessage = messageProcessor.processIncomingMessage(false, "NewUserName: hi");
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(false, "NewUserName: hi");
|
||||
QVERIFY(messageHasSelfMention(processedMessage));
|
||||
|
||||
// Regression tests for: https://github.com/qTox/qTox/issues/2119
|
||||
{
|
||||
// Empty usernames should not match
|
||||
sharedParams.onUserNameSet("");
|
||||
processedMessage = messageProcessor.processIncomingMessage(false, "");
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(false, "");
|
||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||
|
||||
// Empty usernames matched on everything, ensure this is not the case
|
||||
processedMessage = messageProcessor.processIncomingMessage(false, "a");
|
||||
processedMessage = messageProcessor.processIncomingCoreMessage(false, "a");
|
||||
QVERIFY(!messageHasSelfMention(processedMessage));
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ void TestMessageProcessor::testSelfMention()
|
|||
*/
|
||||
void TestMessageProcessor::testOutgoingMessage()
|
||||
{
|
||||
auto sharedParams = MessageProcessor::SharedParams();
|
||||
auto sharedParams = MessageProcessor::SharedParams(tox_max_message_length(), 10 * 1024 * 1024);
|
||||
auto messageProcessor = MessageProcessor(sharedParams);
|
||||
|
||||
QString testStr;
|
||||
|
@ -133,10 +133,17 @@ void TestMessageProcessor::testOutgoingMessage()
|
|||
testStr += "a";
|
||||
}
|
||||
|
||||
auto messages = messageProcessor.processOutgoingMessage(false, testStr);
|
||||
auto messages = messageProcessor.processOutgoingMessage(false, testStr, ExtensionSet());
|
||||
|
||||
// The message processor should split our messages
|
||||
QVERIFY(messages.size() == 2);
|
||||
|
||||
auto extensionSet = ExtensionSet();
|
||||
extensionSet[ExtensionType::messages] = true;
|
||||
messages = messageProcessor.processOutgoingMessage(false, testStr, extensionSet);
|
||||
|
||||
// If we have multipart messages we shouldn't split our messages
|
||||
QVERIFY(messages.size() == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,9 +152,9 @@ void TestMessageProcessor::testOutgoingMessage()
|
|||
void TestMessageProcessor::testIncomingMessage()
|
||||
{
|
||||
// Nothing too special happening on the incoming side if we aren't looking for self mentions
|
||||
auto sharedParams = MessageProcessor::SharedParams();
|
||||
auto sharedParams = MessageProcessor::SharedParams(tox_max_message_length(), 10 * 1024 * 1024);
|
||||
auto messageProcessor = MessageProcessor(sharedParams);
|
||||
auto message = messageProcessor.processIncomingMessage(false, "test");
|
||||
auto message = messageProcessor.processIncomingCoreMessage(false, "test");
|
||||
|
||||
QVERIFY(message.isAction == false);
|
||||
QVERIFY(message.content == "test");
|
||||
|
|
|
@ -51,6 +51,7 @@ private slots:
|
|||
void test2to3();
|
||||
void test3to4();
|
||||
void test4to5();
|
||||
void test5to6();
|
||||
void cleanupTestCase();
|
||||
private:
|
||||
bool initSucess{false};
|
||||
|
@ -66,7 +67,8 @@ const QString testFileList[] = {
|
|||
"test1to2.db",
|
||||
"test2to3.db",
|
||||
"test3to4.db",
|
||||
"test4to5.db"
|
||||
"test4to5.db",
|
||||
"test5to6.db"
|
||||
};
|
||||
|
||||
// db schemas can be select with "SELECT name, sql FROM sqlite_master;" on the database.
|
||||
|
@ -122,6 +124,17 @@ const std::vector<SqliteMasterEntry> schema5 {
|
|||
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"}
|
||||
};
|
||||
|
||||
// added toxext extensions
|
||||
const std::vector<SqliteMasterEntry> schema6 {
|
||||
{"aliases", "CREATE TABLE aliases (id INTEGER PRIMARY KEY, owner INTEGER, display_name BLOB NOT NULL, UNIQUE(owner, display_name), FOREIGN KEY (owner) REFERENCES peers(id))"},
|
||||
{"faux_offline_pending", "CREATE TABLE faux_offline_pending (id INTEGER PRIMARY KEY, required_extensions INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (id) REFERENCES history(id))"},
|
||||
{"file_transfers", "CREATE TABLE file_transfers (id INTEGER PRIMARY KEY, chat_id INTEGER NOT NULL, file_restart_id BLOB NOT NULL, file_name BLOB NOT NULL, file_path BLOB NOT NULL, file_hash BLOB NOT NULL, file_size INTEGER NOT NULL, direction INTEGER NOT NULL, file_state INTEGER NOT NULL)"},
|
||||
{"history", "CREATE TABLE history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, message BLOB NOT NULL, file_id INTEGER, FOREIGN KEY (file_id) REFERENCES file_transfers(id), FOREIGN KEY (chat_id) REFERENCES peers(id), FOREIGN KEY (sender_alias) REFERENCES aliases(id))"},
|
||||
{"peers", "CREATE TABLE peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE)"},
|
||||
{"broken_messages", "CREATE TABLE broken_messages (id INTEGER PRIMARY KEY, reason INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (id) REFERENCES history(id))"},
|
||||
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"}
|
||||
};
|
||||
|
||||
void TestDbSchema::initTestCase()
|
||||
{
|
||||
for (const auto& path : testFileList) {
|
||||
|
@ -176,7 +189,7 @@ void TestDbSchema::testCreation()
|
|||
QVector<RawDatabase::Query> queries;
|
||||
auto db = std::shared_ptr<RawDatabase>{new RawDatabase{"testCreation.db", {}, {}}};
|
||||
QVERIFY(createCurrentSchema(*db));
|
||||
verifyDb(db, schema5);
|
||||
verifyDb(db, schema6);
|
||||
}
|
||||
|
||||
void TestDbSchema::testIsNewDb()
|
||||
|
@ -374,5 +387,13 @@ void TestDbSchema::test4to5()
|
|||
verifyDb(db, schema5);
|
||||
}
|
||||
|
||||
void TestDbSchema::test5to6()
|
||||
{
|
||||
auto db = std::shared_ptr<RawDatabase>{new RawDatabase{"test5to6.db", {}, {}}};
|
||||
createSchemaAtVersion(db, schema5);
|
||||
QVERIFY(dbSchema5to6(*db));
|
||||
verifyDb(db, schema6);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TestDbSchema)
|
||||
#include "dbschema_test.moc"
|
||||
|
|
|
@ -24,171 +24,164 @@
|
|||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
struct MockFriendMessageSender : public QObject, public ICoreFriendMessageSender
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MockFriendMessageSender(Friend* f)
|
||||
: f(f){}
|
||||
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override
|
||||
{
|
||||
if (Status::isOnline(f->getStatus())) {
|
||||
receipt.get() = receiptNum++;
|
||||
if (!dropReceipts) {
|
||||
msgs.push_back(message);
|
||||
emit receiptReceived(receipt);
|
||||
}
|
||||
numMessagesSent++;
|
||||
} else {
|
||||
numMessagesFailed++;
|
||||
}
|
||||
return Status::isOnline(f->getStatus());
|
||||
}
|
||||
|
||||
signals:
|
||||
void receiptReceived(ReceiptNum receipt);
|
||||
|
||||
public:
|
||||
Friend* f;
|
||||
bool dropReceipts = false;
|
||||
size_t numMessagesSent = 0;
|
||||
size_t numMessagesFailed = 0;
|
||||
int receiptNum = 0;
|
||||
std::vector<QString> msgs;
|
||||
};
|
||||
|
||||
class TestOfflineMsgEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testReceiptResolution();
|
||||
void testOfflineFriend();
|
||||
void testSentUnsentCoordination();
|
||||
void testReceiptBeforeMessage();
|
||||
void testReceiptAfterMessage();
|
||||
void testResendWorkflow();
|
||||
void testTypeCoordination();
|
||||
void testCallback();
|
||||
void testExtendedMessageCoordination();
|
||||
};
|
||||
|
||||
class OfflineMsgEngineFixture
|
||||
{
|
||||
public:
|
||||
OfflineMsgEngineFixture()
|
||||
: f(0, ToxPk(QByteArray(32, 0)))
|
||||
, friendMessageSender(&f)
|
||||
, offlineMsgEngine(&f, &friendMessageSender)
|
||||
{
|
||||
f.setStatus(Status::Status::Online);
|
||||
QObject::connect(&friendMessageSender, &MockFriendMessageSender::receiptReceived,
|
||||
&offlineMsgEngine, &OfflineMsgEngine::onReceiptReceived);
|
||||
}
|
||||
void completionFn(bool) {}
|
||||
|
||||
Friend f;
|
||||
MockFriendMessageSender friendMessageSender;
|
||||
void TestOfflineMsgEngine::testReceiptBeforeMessage()
|
||||
{
|
||||
OfflineMsgEngine offlineMsgEngine;
|
||||
};
|
||||
|
||||
void completionFn() {}
|
||||
|
||||
void TestOfflineMsgEngine::testReceiptResolution()
|
||||
{
|
||||
OfflineMsgEngineFixture fixture;
|
||||
|
||||
Message msg{false, QString(), QDateTime()};
|
||||
|
||||
ReceiptNum receipt;
|
||||
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||
fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn);
|
||||
auto const receipt = ReceiptNum(0);
|
||||
offlineMsgEngine.onReceiptReceived(receipt);
|
||||
offlineMsgEngine.addSentCoreMessage(receipt, Message(), completionFn);
|
||||
|
||||
// We should have no offline messages to deliver if we resolved our receipt
|
||||
// correctly
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
auto const removedMessages = offlineMsgEngine.removeAllMessages();
|
||||
|
||||
QVERIFY(fixture.friendMessageSender.numMessagesSent == 1);
|
||||
|
||||
// If we drop receipts we should keep trying to send messages every time we
|
||||
// "deliverOfflineMsgs"
|
||||
fixture.friendMessageSender.dropReceipts = true;
|
||||
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||
fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn);
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
|
||||
QVERIFY(fixture.friendMessageSender.numMessagesSent == 5);
|
||||
|
||||
// And once we stop dropping and try one more time we should run out of
|
||||
// messages to send again
|
||||
fixture.friendMessageSender.dropReceipts = false;
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
QVERIFY(fixture.friendMessageSender.numMessagesSent == 6);
|
||||
QVERIFY(removedMessages.empty());
|
||||
}
|
||||
|
||||
void TestOfflineMsgEngine::testOfflineFriend()
|
||||
void TestOfflineMsgEngine::testReceiptAfterMessage()
|
||||
{
|
||||
OfflineMsgEngineFixture fixture;
|
||||
OfflineMsgEngine offlineMsgEngine;
|
||||
|
||||
Message msg{false, QString(), QDateTime()};
|
||||
auto const receipt = ReceiptNum(0);
|
||||
offlineMsgEngine.addSentCoreMessage(receipt, Message(), completionFn);
|
||||
offlineMsgEngine.onReceiptReceived(receipt);
|
||||
|
||||
fixture.f.setStatus(Status::Status::Offline);
|
||||
auto const removedMessages = offlineMsgEngine.removeAllMessages();
|
||||
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||
|
||||
fixture.f.setStatus(Status::Status::Online);
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
|
||||
|
||||
QVERIFY(fixture.friendMessageSender.numMessagesFailed == 0);
|
||||
QVERIFY(fixture.friendMessageSender.numMessagesSent == 5);
|
||||
QVERIFY(removedMessages.empty());
|
||||
}
|
||||
|
||||
void TestOfflineMsgEngine::testSentUnsentCoordination()
|
||||
void TestOfflineMsgEngine::testResendWorkflow()
|
||||
{
|
||||
OfflineMsgEngineFixture fixture;
|
||||
Message msg{false, QString("a"), QDateTime()};
|
||||
ReceiptNum receipt;
|
||||
OfflineMsgEngine offlineMsgEngine;
|
||||
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||
msg.content = "b";
|
||||
fixture.friendMessageSender.dropReceipts = true;
|
||||
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||
fixture.friendMessageSender.dropReceipts = false;
|
||||
fixture.offlineMsgEngine.addSentMessage(receipt, msg, completionFn);
|
||||
msg.content = "c";
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, completionFn);
|
||||
auto const receipt = ReceiptNum(0);
|
||||
offlineMsgEngine.addSentCoreMessage(receipt, Message(), completionFn);
|
||||
auto messagesToResend = offlineMsgEngine.removeAllMessages();
|
||||
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
QVERIFY(messagesToResend.size() == 1);
|
||||
|
||||
auto expectedResponseOrder = std::vector<QString>{"a", "b", "c"};
|
||||
QVERIFY(fixture.friendMessageSender.msgs == expectedResponseOrder);
|
||||
offlineMsgEngine.addSentCoreMessage(receipt, Message(), completionFn);
|
||||
offlineMsgEngine.onReceiptReceived(receipt);
|
||||
|
||||
messagesToResend = offlineMsgEngine.removeAllMessages();
|
||||
QVERIFY(messagesToResend.size() == 0);
|
||||
|
||||
auto const nullMsg = Message();
|
||||
auto msg2 = Message();
|
||||
auto msg3 = Message();
|
||||
msg2.content = "msg2";
|
||||
msg3.content = "msg3";
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(0), nullMsg, completionFn);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(1), nullMsg, completionFn);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(2), msg2, completionFn);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(3), msg3, completionFn);
|
||||
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(0));
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(1));
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(3));
|
||||
|
||||
messagesToResend = offlineMsgEngine.removeAllMessages();
|
||||
QVERIFY(messagesToResend.size() == 1);
|
||||
QVERIFY(messagesToResend[0].message.content == "msg2");
|
||||
}
|
||||
|
||||
|
||||
void TestOfflineMsgEngine::testTypeCoordination()
|
||||
{
|
||||
OfflineMsgEngine offlineMsgEngine;
|
||||
|
||||
auto msg1 = Message();
|
||||
auto msg2 = Message();
|
||||
auto msg3 = Message();
|
||||
auto msg4 = Message();
|
||||
auto msg5 = Message();
|
||||
|
||||
msg1.content = "msg1";
|
||||
msg2.content = "msg2";
|
||||
msg3.content = "msg3";
|
||||
msg4.content = "msg4";
|
||||
msg5.content = "msg5";
|
||||
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(1), msg1, completionFn);
|
||||
offlineMsgEngine.addUnsentMessage(msg2, completionFn);
|
||||
offlineMsgEngine.addSentExtendedMessage(ExtendedReceiptNum(1), msg3, completionFn);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(2), msg4, completionFn);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(3), msg5, completionFn);
|
||||
|
||||
const auto messagesToResend = offlineMsgEngine.removeAllMessages();
|
||||
|
||||
QVERIFY(messagesToResend[0].message.content == "msg1");
|
||||
QVERIFY(messagesToResend[1].message.content == "msg2");
|
||||
QVERIFY(messagesToResend[2].message.content == "msg3");
|
||||
QVERIFY(messagesToResend[3].message.content == "msg4");
|
||||
QVERIFY(messagesToResend[4].message.content == "msg5");
|
||||
}
|
||||
|
||||
void TestOfflineMsgEngine::testCallback()
|
||||
{
|
||||
OfflineMsgEngineFixture fixture;
|
||||
OfflineMsgEngine offlineMsgEngine;
|
||||
|
||||
size_t numCallbacks = 0;
|
||||
auto callback = [&numCallbacks] { numCallbacks++; };
|
||||
auto callback = [&numCallbacks] (bool) { numCallbacks++; };
|
||||
Message msg{false, QString(), QDateTime()};
|
||||
ReceiptNum receipt;
|
||||
|
||||
fixture.friendMessageSender.sendMessage(0, msg.content, receipt);
|
||||
fixture.offlineMsgEngine.addSentMessage(receipt, msg, callback);
|
||||
fixture.offlineMsgEngine.addUnsentMessage(msg, callback);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(1), Message(), callback);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(2), Message(), callback);
|
||||
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(1));
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(2));
|
||||
|
||||
fixture.offlineMsgEngine.deliverOfflineMsgs();
|
||||
QVERIFY(numCallbacks == 2);
|
||||
}
|
||||
|
||||
void TestOfflineMsgEngine::testExtendedMessageCoordination()
|
||||
{
|
||||
OfflineMsgEngine offlineMsgEngine;
|
||||
|
||||
size_t numCallbacks = 0;
|
||||
auto callback = [&numCallbacks] (bool) { numCallbacks++; };
|
||||
|
||||
auto msg1 = Message();
|
||||
auto msg2 = Message();
|
||||
auto msg3 = Message();
|
||||
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(1), msg1, callback);
|
||||
offlineMsgEngine.addSentExtendedMessage(ExtendedReceiptNum(1), msg1, callback);
|
||||
offlineMsgEngine.addSentCoreMessage(ReceiptNum(2), msg1, callback);
|
||||
|
||||
offlineMsgEngine.onExtendedReceiptReceived(ExtendedReceiptNum(2));
|
||||
QVERIFY(numCallbacks == 0);
|
||||
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(1));
|
||||
QVERIFY(numCallbacks == 1);
|
||||
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(1));
|
||||
QVERIFY(numCallbacks == 1);
|
||||
|
||||
offlineMsgEngine.onExtendedReceiptReceived(ExtendedReceiptNum(1));
|
||||
QVERIFY(numCallbacks == 2);
|
||||
|
||||
offlineMsgEngine.onReceiptReceived(ReceiptNum(2));
|
||||
QVERIFY(numCallbacks == 3);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TestOfflineMsgEngine)
|
||||
#include "offlinemsgengine_test.moc"
|
||||
|
|
|
@ -1030,6 +1030,109 @@ else
|
|||
echo "Using cached build of Toxcore `cat $TOXCORE_PREFIX_DIR/done`"
|
||||
fi
|
||||
|
||||
# toxext
|
||||
TOXEXT_PREFIX_DIR="$DEP_DIR/toxext"
|
||||
TOXEXT_VERSION=0.0.2
|
||||
TOXEXT_HASH="047093eeed396ea9b4a3f0cd0a6bc4e0e09b339e2b03ba4b676e30888fe6acde"
|
||||
TOXEXT_FILENAME="toxext-$TOXEXT_VERSION.tar.gz"
|
||||
if [ ! -f "$TOXEXT_PREFIX_DIR/done" ]
|
||||
then
|
||||
rm -rf "$TOXEXT_PREFIX_DIR"
|
||||
mkdir -p "$TOXEXT_PREFIX_DIR"
|
||||
|
||||
curl $CURL_OPTIONS https://github.com/toxext/toxext/archive/v$TOXEXT_VERSION.tar.gz -o $TOXEXT_FILENAME
|
||||
check_sha256 "$TOXEXT_HASH" "$TOXEXT_FILENAME"
|
||||
bsdtar --no-same-owner --no-same-permissions -xf "$TOXEXT_FILENAME"
|
||||
rm "$TOXEXT_FILENAME"
|
||||
cd toxext*
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
export PKG_CONFIG_PATH="$OPUS_PREFIX_DIR/lib/pkgconfig:$SODIUM_PREFIX_DIR/lib/pkgconfig:$VPX_PREFIX_DIR/lib/pkgconfig:$TOXCORE_PREFIX_DIR/lib/pkgconfig"
|
||||
export PKG_CONFIG_LIBDIR="/usr/$ARCH-w64-mingw32"
|
||||
|
||||
echo "
|
||||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
SET(CMAKE_C_COMPILER $ARCH-w64-mingw32-gcc)
|
||||
SET(CMAKE_CXX_COMPILER $ARCH-w64-mingw32-g++)
|
||||
SET(CMAKE_RC_COMPILER $ARCH-w64-mingw32-windres)
|
||||
|
||||
SET(CMAKE_FIND_ROOT_PATH /usr/$ARCH-w64-mingw32 $OPUS_PREFIX_DIR $SODIUM_PREFIX_DIR $VPX_PREFIX_DIR $TOXCORE_PREFIX_DIR)
|
||||
" > toolchain.cmake
|
||||
|
||||
cmake -DCMAKE_INSTALL_PREFIX=$TOXEXT_PREFIX_DIR \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
|
||||
..
|
||||
|
||||
make
|
||||
make install
|
||||
echo -n $TOXEXT_VERSION > $TOXEXT_PREFIX_DIR/done
|
||||
|
||||
unset PKG_CONFIG_PATH
|
||||
unset PKG_CONFIG_LIBDIR
|
||||
|
||||
cd ..
|
||||
|
||||
cd ..
|
||||
rm -rf ./toxext*
|
||||
else
|
||||
echo "Using cached build of ToxExt `cat $TOXEXT_PREFIX_DIR/done`"
|
||||
fi
|
||||
|
||||
# tox_extension_messages
|
||||
TOX_EXTENSION_MESSAGES_PREFIX_DIR="$DEP_DIR/tox_extension_messages"
|
||||
TOX_EXTENSION_MESSAGES_VERSION=0.0.2
|
||||
TOX_EXTENSION_MESSAGES_HASH="95e8cdd1de6cc7ba561620716f340e9606a06b3c2ff9c9020af4784c22fd0d7f"
|
||||
TOX_EXTENSION_MESSAGES_FILENAME="tox_extension_messages-$TOX_EXTENSION_MESSAGES_VERSION.tar.gz"
|
||||
if [ ! -f "$TOX_EXTENSION_MESSAGES_PREFIX_DIR/done" ]
|
||||
then
|
||||
rm -rf "$TOX_EXTENSION_MESSAGES_PREFIX_DIR"
|
||||
mkdir -p "$TOX_EXTENSION_MESSAGES_PREFIX_DIR"
|
||||
|
||||
curl $CURL_OPTIONS https://github.com/toxext/tox_extension_messages/archive/v$TOX_EXTENSION_MESSAGES_VERSION.tar.gz -o $TOX_EXTENSION_MESSAGES_FILENAME
|
||||
check_sha256 "$TOX_EXTENSION_MESSAGES_HASH" "$TOX_EXTENSION_MESSAGES_FILENAME"
|
||||
bsdtar --no-same-owner --no-same-permissions -xf "$TOX_EXTENSION_MESSAGES_FILENAME"
|
||||
rm "$TOX_EXTENSION_MESSAGES_FILENAME"
|
||||
cd tox_extension_messages*
|
||||
|
||||
mkdir -p build
|
||||
cd build
|
||||
|
||||
export PKG_CONFIG_PATH="$OPUS_PREFIX_DIR/lib/pkgconfig:$SODIUM_PREFIX_DIR/lib/pkgconfig:$VPX_PREFIX_DIR/lib/pkgconfig:$TOXCORE_PREFIX_DIR/lib/pkgconfig"
|
||||
export PKG_CONFIG_LIBDIR="/usr/$ARCH-w64-mingw32"
|
||||
|
||||
echo "
|
||||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
SET(CMAKE_C_COMPILER $ARCH-w64-mingw32-gcc)
|
||||
SET(CMAKE_CXX_COMPILER $ARCH-w64-mingw32-g++)
|
||||
SET(CMAKE_RC_COMPILER $ARCH-w64-mingw32-windres)
|
||||
|
||||
SET(CMAKE_FIND_ROOT_PATH /usr/$ARCH-w64-mingw32 $OPUS_PREFIX_DIR $SODIUM_PREFIX_DIR $VPX_PREFIX_DIR $TOXCORE_PREFIX_DIR $TOXEXT_PREFIX_DIR)
|
||||
" > toolchain.cmake
|
||||
|
||||
cmake -DCMAKE_INSTALL_PREFIX=$TOX_EXTENSION_MESSAGES_PREFIX_DIR \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
|
||||
..
|
||||
|
||||
make
|
||||
make install
|
||||
echo -n $TOX_EXTENSION_MESSAGES_VERSION > $TOX_EXTENSION_MESSAGES_PREFIX_DIR/done
|
||||
|
||||
unset PKG_CONFIG_PATH
|
||||
unset PKG_CONFIG_LIBDIR
|
||||
|
||||
cd ..
|
||||
|
||||
cd ..
|
||||
rm -rf ./tox_extension_messages*
|
||||
else
|
||||
echo "Using cached build of tox_extension_messages `cat $TOX_EXTENSION_MESSAGES_PREFIX_DIR/done`"
|
||||
fi
|
||||
|
||||
set +u
|
||||
if [[ -n "$TRAVIS_CI_STAGE" ]] || [[ "$BUILD_TYPE" == "debug" ]]
|
||||
|
|
Loading…
Reference in New Issue
Block a user